diff --git a/Classes/Builder/ContentBlockBuilder.php b/Classes/Builder/ContentBlockBuilder.php index 676024d1..3bfc6238 100644 --- a/Classes/Builder/ContentBlockBuilder.php +++ b/Classes/Builder/ContentBlockBuilder.php @@ -31,13 +31,13 @@ /** * @internal Not part of TYPO3's public API. */ -class ContentBlockBuilder +readonly class ContentBlockBuilder { public function __construct( - protected readonly HtmlTemplateCodeGenerator $htmlTemplateCodeGenerator, - protected readonly LanguageFileGenerator $languageFileGenerator, - protected readonly ContentBlockRegistry $contentBlockRegistry, - protected readonly TableDefinitionCollectionFactory $tableDefinitionCollectionFactory, + protected HtmlTemplateCodeGenerator $htmlTemplateCodeGenerator, + protected LanguageFileGenerator $languageFileGenerator, + protected ContentBlockRegistry $contentBlockRegistry, + protected TableDefinitionCollectionFactory $tableDefinitionCollectionFactory, ) {} /** @@ -80,7 +80,7 @@ public function create(LoadedContentBlock $contentBlock): void protected function initializeRegistries(LoadedContentBlock $contentBlock): void { $this->contentBlockRegistry->register($contentBlock); - $tableDefinitionCollection = $this->tableDefinitionCollectionFactory->createUncached(); + $tableDefinitionCollection = $this->tableDefinitionCollectionFactory->createUncached($this->contentBlockRegistry); $automaticLanguageKeysRegistry = $tableDefinitionCollection->getAutomaticLanguageKeysRegistry(); $this->languageFileGenerator->setAutomaticLanguageKeysRegistry($automaticLanguageKeysRegistry); } diff --git a/Classes/Definition/Factory/CompilationResult.php b/Classes/Definition/Factory/CompilationResult.php new file mode 100644 index 00000000..09b075bc --- /dev/null +++ b/Classes/Definition/Factory/CompilationResult.php @@ -0,0 +1,47 @@ + + */ + private readonly array $parentReferences, + private readonly array $mergedTableDefinitions, + ) {} + + public function getAutomaticLanguageKeys(): AutomaticLanguageKeysRegistry + { + return $this->automaticLanguageKeys; + } + + public function getParentReferences(): array + { + return $this->parentReferences; + } + + public function getMergedTableDefinitions(): array + { + return $this->mergedTableDefinitions; + } +} diff --git a/Classes/Definition/Factory/ContentBlockCompiler.php b/Classes/Definition/Factory/ContentBlockCompiler.php new file mode 100644 index 00000000..24bfdc22 --- /dev/null +++ b/Classes/Definition/Factory/ContentBlockCompiler.php @@ -0,0 +1,998 @@ +create(). The resulting + * immutable object is used as an interchange format for multiple generators + * as read-only access. As such, this class does not have knowledge about real + * TCA, only about the YAML definition and how to transform it into the + * internal object format. + * + * List of what this compiler does: + * - Builds the CompilationResult object for use in ContentBlockDefinitionCollectionFactory->create(). + * - Validates the YAML-schema and errors out in case of incorrect definition. + * - Assigns and tracks unique identifiers for use in database column names. + * - Collects automatic language keys, which can be used to generate the + * Labels.xlf file with a command. + * - Recursively resolves Collections and creates additional anonymous Record + * Types based on them. Meaning one Content Block can indeed define more than + * one Content Type with the use of Collections. + * - Tracks parent references which are used to automatically add `parent_uid` + * fields. This way a Record Type can be an aggregate root or not depending on + * whether it is used in a foreign_table relation or not. + * - Replaces identifiers referenced in configuration options with the prefixed + * identifier (e.g. for the labelField). + * - Dynamically adds new fields depending on config. For example the type field + * or the description column. + * - (YAML-defined) FlexForm parsing / preparation. + * + * @internal Not part of TYPO3's public API. + */ +final class ContentBlockCompiler +{ + private AutomaticLanguageKeysRegistry $automaticLanguageKeysRegistry; + + /** + * This property tracks Collection foreign_table parent references. + * It is needed, because there can be a references to a Content Block, + * which isn't processed yet. Thus, it needs to be collected throughout the run. + * + * @var array + */ + private array $parentReferences = []; + + public function compile(ContentBlockRegistry $contentBlockRegistry): CompilationResult + { + $this->automaticLanguageKeysRegistry = new AutomaticLanguageKeysRegistry(); + $tableDefinitionList = []; + foreach ($contentBlockRegistry->getAll() as $contentBlock) { + $table = $contentBlock->getYaml()['table']; + $languagePath = $this->buildBaseLanguagePath($contentBlock); + $processingInput = new ProcessingInput( + yaml: $contentBlock->getYaml(), + contentBlock: $contentBlock, + table: $table, + rootTable: $table, + languagePath: $languagePath, + contentType: $contentBlock->getContentType(), + tableDefinitionList: $tableDefinitionList, + ); + $tableDefinitionList = $this->processRootFields($processingInput); + } + $mergedTableDefinitionList = $this->mergeProcessingResult($tableDefinitionList); + $compilationResult = new CompilationResult( + $this->automaticLanguageKeysRegistry, + $this->parentReferences, + $mergedTableDefinitionList, + ); + $this->resetState(); + return $compilationResult; + } + + private function resetState(): void + { + $this->parentReferences = []; + unset($this->automaticLanguageKeysRegistry); + } + + private function mergeProcessingResult(array $tableDefinitionList): array + { + $mergedResult = []; + foreach ($tableDefinitionList as $table => $definition) { + $mergedResult[$table] = array_replace_recursive(...array_reverse($definition['tableDefinitions'])); + $mergedResult[$table]['typeDefinitions'] = $definition['typeDefinitions']; + } + return $mergedResult; + } + + private function processRootFields(ProcessingInput $input): array + { + $result = new ProcessedFieldsResult($input); + $this->initializeContentTypeLabelAndDescription($input, $result); + $yaml = $input->yaml; + $yaml = $this->prepareYaml($result, $yaml); + $yamlFields = $yaml['fields'] ?? []; + foreach ($yamlFields as $rootField) { + $fields = $this->handleRootField($rootField, $input, $result); + $this->processFields($input, $result, $fields); + } + $this->prefixTcaConfigFields($input, $result); + $this->collectOverrideColumns($result); + $this->collectDefinitions($input, $result); + return $result->tableDefinitionList; + } + + private function processFields(ProcessingInput $input, ProcessedFieldsResult $result, array $fields): void + { + foreach ($fields as $field) { + $this->initializeField($input, $result, $field); + $input->languagePath->addPathSegment($result->identifier); + $field = $this->initializeFieldLabelAndDescription($input, $result, $field); + if ($result->fieldType === FieldType::FLEXFORM) { + $field = $this->processFlexForm($input, $field); + } + if ($result->fieldType->hasItems()) { + $field = $this->collectItemLabels($input, $result, $field); + } + $result->tcaFieldDefinition = $this->buildTcaFieldDefinitionArray($input, $result, $field); + if ($result->fieldType === FieldType::COLLECTION) { + $this->processCollection($input, $result, $field); + } + $this->collectProcessedField($result); + $input->languagePath->popSegment(); + $result->resetTemporaryState(); + } + } + + private function initializeField(ProcessingInput $input, ProcessedFieldsResult $result, array $field): void + { + $result->identifier = (string)$field['identifier']; + $this->assertUniqueFieldIdentifier($result, $input->contentBlock); + $result->uniqueFieldIdentifiers[] = $result->identifier; + $result->fieldType = $this->resolveType($input, $field); + $result->uniqueIdentifier = $this->chooseIdentifier($input, $field); + $result->identifierToUniqueMap[$result->identifier] = $result->uniqueIdentifier; + } + + private function prepareYaml(ProcessedFieldsResult $result, array $yaml): array + { + $contentType = $result->tableDefinition->contentType; + if ($contentType === ContentType::RECORD_TYPE && $result->tableDefinition->hasTypeField()) { + $yamlFields = $yaml['fields'] ?? []; + $yamlFields = $this->prependTypeFieldForRecordType($yamlFields, $result); + $yaml['fields'] = $yamlFields; + } + $hasInternalDescription = $yaml['internalDescription'] ?? false; + if ($contentType === ContentType::RECORD_TYPE && $hasInternalDescription) { + $yamlFields = $yaml['fields'] ?? []; + $yamlFields = $this->appendInternalDescription($yamlFields); + $yaml['fields'] = $yamlFields; + } + if ($contentType === ContentType::PAGE_TYPE) { + $yamlFields = $yaml['fields'] ?? []; + $yamlFields = $this->prependPagesTitlePalette($yamlFields); + $yaml['fields'] = $yamlFields; + } + return $yaml; + } + + private function initializeContentTypeLabelAndDescription(ProcessingInput $input, ProcessedFieldsResult $result): void + { + $languagePathTitle = $input->languagePath->getCurrentPath(); + $languagePathDescription = $input->languagePath->getCurrentPath(); + $title = (string)($input->yaml['title'] ?? ''); + $description = (string)($input->yaml['description'] ?? ''); + if ($input->isRootTable()) { + // Ensure there is always a title for a Content Type. + $title = $title !== '' ? $title : $input->contentBlock->getName(); + $result->contentType->title = $title; + $result->contentType->description = $description; + $languagePathTitle = $languagePathTitle . 'title'; + $languagePathDescription = $languagePathDescription . 'description'; + $languagePathSource = new AutomaticLanguageSource($languagePathTitle, $title); + $descriptionPathSource = new AutomaticLanguageSource($languagePathDescription, $description); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $languagePathSource); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); + } else { + $languagePathTitle = $languagePathTitle . '.label'; + $languagePathDescription = $languagePathDescription . '.description'; + $result->contentType->title = $title; + $result->contentType->description = $description; + } + $result->contentType->languagePathTitle = $languagePathTitle; + $result->contentType->languagePathDescription = $languagePathDescription; + } + + private function initializeFieldLabelAndDescription(ProcessingInput $input, ProcessedFieldsResult $result, array $field): array + { + $labelPath = $this->getFieldLabelPath($input->languagePath); + $descriptionPath = $this->getFieldDescriptionPath($input->languagePath); + $title = (string)($field['label'] ?? ''); + // Never fall back to identifiers for existing fields. They have their standard translation. + $title = ($title !== '' || $this->isExistingField($field)) ? $title : $result->identifier; + $field['label'] = $title; + $description = $field['description'] ?? ''; + $labelPathSource = new AutomaticLanguageSource($labelPath, $title); + $descriptionPathSource = new AutomaticLanguageSource($descriptionPath, $description); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); + return $field; + } + + private function collectItemLabels(ProcessingInput $input, ProcessedFieldsResult $result, array $field): array + { + $items = $field['items'] ?? []; + $fieldType = $result->fieldType; + foreach ($items as $index => $item) { + $label = (string)($item['label'] ?? ''); + $currentPath = $input->languagePath->getCurrentPath(); + if ($fieldType === FieldType::CHECKBOX) { + $labelPath = $currentPath . '.items.' . $index . '.label'; + } else { + $value = (string)($item['value'] ?? ''); + if ($value === '') { + $labelPath = $currentPath . '.items.label'; + } else { + $labelPath = $currentPath . '.items.' . $value . '.label'; + } + } + $field['items'][$index]['labelPath'] = $labelPath; + $labelPathSource = new AutomaticLanguageSource($labelPath, $label); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); + } + return $field; + } + + private function buildTcaFieldDefinitionArray( + ProcessingInput $input, + ProcessedFieldsResult $result, + array $field, + ): array { + $tcaFieldDefinition = [ + 'parentTable' => $input->contentBlock->getContentType()->getTable(), + 'uniqueIdentifier' => $result->uniqueIdentifier, + 'config' => $field, + 'type' => $result->fieldType, + 'labelPath' => $this->getFieldLabelPath($input->languagePath), + 'descriptionPath' => $this->getFieldDescriptionPath($input->languagePath), + ]; + return $tcaFieldDefinition; + } + + private function getFieldLabelPath(LanguagePath $languagePath): string + { + return $languagePath->getCurrentPath() . '.label'; + } + + private function getFieldDescriptionPath(LanguagePath $languagePath): string + { + return $languagePath->getCurrentPath() . '.description'; + } + + private function prefixTcaConfigFields(ProcessingInput $input, ProcessedFieldsResult $result): void + { + $this->prefixSortFieldIfNecessary($input, $result); + $this->prefixLabelFieldIfNecessary($input, $result); + $this->prefixFallbackLabelFieldsIfNecessary($input, $result); + $this->prefixDisplayCondFieldsIfNecessary($result); + } + + private function prependTypeFieldForRecordType(array $yamlFields, ProcessedFieldsResult $result): array + { + $typeFieldDefinition = [ + 'identifier' => $result->tableDefinition->typeField, + 'type' => FieldType::SELECT->value, + 'renderType' => 'selectSingle', + 'prefixField' => false, + 'default' => $result->contentType->typeName, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.type', + 'items' => [], + ]; + // Prepend type field. + array_unshift($yamlFields, $typeFieldDefinition); + return $yamlFields; + } + + private function appendInternalDescription(array $yamlFields): array + { + $tab = [ + 'identifier' => 'internal_description_tab', + 'type' => 'Tab', + 'label' => 'LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes', + ]; + $internalDescription = [ + 'identifier' => 'internal_description', + 'type' => 'Textarea', + 'prefixField' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.description', + 'rows' => 5, + 'cols' => 30, + ]; + $yamlFields[] = $tab; + $yamlFields[] = $internalDescription; + return $yamlFields; + } + + private function prependPagesTitlePalette(array $yamlFields): array + { + $titlePalette = [ + 'identifier' => 'content_blocks_titleonly', + 'type' => 'Palette', + 'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.title', + 'prefixField' => false, + 'fields' => [ + [ + 'identifier' => 'title', + 'useExistingField' => true, + 'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.title_formlabel', + ], + [ + 'type' => 'Linebreak', + ], + [ + 'identifier' => 'slug', + 'useExistingField' => true, + ], + [ + 'type' => 'Linebreak', + ], + [ + 'identifier' => 'nav_title', + 'useExistingField' => true, + 'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.nav_title_formlabel', + ], + ], + ]; + array_unshift($yamlFields, $titlePalette); + return $yamlFields; + } + + private function handleRootField(array $rootField, ProcessingInput $input, ProcessedFieldsResult $result): array + { + $rootFieldType = $this->resolveType($input, $rootField); + $this->assertNoLinebreakOutsideOfPalette($rootFieldType, $input->contentBlock); + $fields = match ($rootFieldType) { + Fieldtype::PALETTE => $this->handlePalette($input, $result, $rootField), + FieldType::TAB => $this->handleTab($input, $result, $rootField), + default => $this->handleDefault($input, $result, $rootField) + }; + return $fields; + } + + private function handleDefault(ProcessingInput $input, ProcessedFieldsResult $result, array $field): array + { + $result->contentType->showItems[] = $this->chooseIdentifier($input, $field); + return [$field]; + } + + private function handlePalette(ProcessingInput $input, ProcessedFieldsResult $result, array $rootPalette): array + { + $rootPaletteIdentifier = $rootPalette['identifier']; + $this->assertUniquePaletteIdentifier($rootPaletteIdentifier, $result, $input->contentBlock); + $result->uniquePaletteIdentifiers[] = $rootPaletteIdentifier; + // Ignore empty Palettes. + if (($rootPalette['fields'] ?? []) === []) { + return []; + } + $fields = []; + $paletteItems = []; + foreach ($rootPalette['fields'] as $paletteField) { + $paletteFieldType = $this->resolveType($input, $paletteField); + if ($paletteFieldType === FieldType::LINEBREAK) { + $paletteItems[] = new LinebreakDefinition(); + } else { + $this->assertNoPaletteInPalette($paletteFieldType, $paletteField['identifier'], $rootPaletteIdentifier, $input->contentBlock); + $this->assertNoTabInPalette($paletteFieldType, $paletteField['identifier'], $rootPaletteIdentifier, $input->contentBlock); + $fields[] = $paletteField; + $paletteItems[] = $this->chooseIdentifier($input, $paletteField); + } + } + + $input->languagePath->addPathSegment('palettes.' . $rootPaletteIdentifier); + $label = (string)($rootPalette['label'] ?? ''); + $description = $rootPalette['description'] ?? ''; + $languagePathLabel = $input->languagePath->getCurrentPath() . '.label'; + $languagePathDescription = $input->languagePath->getCurrentPath() . '.description'; + $labelPathSource = new AutomaticLanguageSource($languagePathLabel, $label); + $descriptionPathSource = new AutomaticLanguageSource($languagePathDescription, $description); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); + $input->languagePath->popSegment(); + + $paletteIdentifier = $this->chooseIdentifier($input, $rootPalette); + $palette = [ + 'contentBlockName' => $input->contentBlock->getName(), + 'identifier' => $paletteIdentifier, + 'label' => $label, + 'description' => $description, + 'languagePathLabel' => $languagePathLabel, + 'languagePathDescription' => $languagePathDescription, + 'items' => $paletteItems, + ]; + $result->tableDefinition->palettes[$paletteIdentifier] = $palette; + $result->contentType->showItems[] = PaletteDefinition::createFromArray($palette); + return $fields; + } + + private function handleTab(ProcessingInput $input, ProcessedFieldsResult $result, array $field): array + { + $identifier = $field['identifier']; + $this->assertUniqueTabIdentifier($identifier, $result, $input->contentBlock); + $result->uniqueTabIdentifiers[] = $identifier; + + $input->languagePath->addPathSegment('tabs.' . $identifier); + $label = (string)($field['label'] ?? ''); + $label = $label !== '' ? $label : $identifier; + $languagePathLabel = $input->languagePath->getCurrentPath(); + $languagePathSource = new AutomaticLanguageSource($languagePathLabel, $label); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $languagePathSource); + $input->languagePath->popSegment(); + + $tabDefinitionArray = [ + 'identifier' => $identifier, + 'contentBlockName' => $input->contentBlock->getName(), + 'label' => $label, + 'languagePathLabel' => $languagePathLabel, + ]; + $tabDefinition = TabDefinition::createFromArray($tabDefinitionArray); + $result->contentType->showItems[] = $tabDefinition; + return []; + } + + private function collectProcessedField(ProcessedFieldsResult $result): void + { + $result->tableDefinition->fields[$result->uniqueIdentifier] = $result->tcaFieldDefinition; + $result->contentType->columns[] = $result->uniqueIdentifier; + } + + private function collectOverrideColumns(ProcessedFieldsResult $result): void + { + foreach ($result->tableDefinition->fields as $uniqueIdentifier => $tcaFieldDefinition) { + $isTypeField = $uniqueIdentifier === $result->tableDefinition->typeField; + if (!$isTypeField) { + $overrideColumn = TcaFieldDefinition::createFromArray($tcaFieldDefinition); + $result->contentType->overrideColumns[] = $overrideColumn; + } + } + } + + /** + * Collect table definitions and Content Types and carry them over to the next stack. + * This factory will merge the table definitions and type definitions at the very end. + */ + private function collectDefinitions(ProcessingInput $input, ProcessedFieldsResult $result): void + { + $table = $input->table; + $tableDefinition = $result->tableDefinition->toArray(); + $result->tableDefinitionList[$table]['tableDefinitions'][] = $tableDefinition; + $isRootTable = $input->isRootTable(); + $identifier = $input->yaml['identifier'] ?? ''; + $typeDefinition = $result->contentType->toArray($isRootTable, $identifier); + $result->tableDefinitionList[$table]['typeDefinitions'][] = $typeDefinition; + } + + private function processCollection(ProcessingInput $input, ProcessedFieldsResult $result, array $field): void + { + $isExternalCollection = array_key_exists('foreign_table', $field); + $this->assignRelationConfigToCollectionField($field, $result); + $foreignTable = $result->tcaFieldDefinition['config']['foreign_table']; + $this->parentReferences[$foreignTable][] = $result->tcaFieldDefinition; + $fields = $field['fields'] ?? []; + if ($isExternalCollection || $fields === []) { + return; + } + $field['title'] = $field['label']; + $newInput = new ProcessingInput( + yaml: $field, + contentBlock: $input->contentBlock, + table: $foreignTable, + rootTable: $input->rootTable, + languagePath: $input->languagePath, + contentType: ContentType::RECORD_TYPE, + tableDefinitionList: $result->tableDefinitionList, + ); + $result->tableDefinitionList = $this->processRootFields($newInput); + } + + private function assignRelationConfigToCollectionField(array $field, ProcessedFieldsResult $result): void + { + $isExternalCollection = array_key_exists('foreign_table', $field); + $result->tcaFieldDefinition['config']['foreign_field'] ??= 'foreign_table_parent_uid'; + if ($isExternalCollection) { + if ($field['shareAcrossTables'] ?? false) { + $result->tcaFieldDefinition['config']['foreign_table_field'] ??= 'tablenames'; + } + if ($field['shareAcrossFields'] ?? false) { + $result->tcaFieldDefinition['config']['foreign_match_fields']['fieldname'] = $result->uniqueIdentifier; + } + } else { + $result->tcaFieldDefinition['config']['foreign_table'] = $field['table'] ?? $result->uniqueIdentifier; + } + } + + private function processFlexForm(ProcessingInput $input, array $field): array + { + $this->validateFlexFormHasOnlySheetsOrNoSheet($field, $input->contentBlock); + $this->validateFlexFormContainsValidFieldTypes($field, $input->contentBlock); + $flexFormDefinition = new FlexFormDefinition(); + $flexFormTypeName = $input->getTypeField() !== null ? $input->getTypeName() : 'default'; + $flexFormDefinition->setTypeName($flexFormTypeName); + $flexFormDefinition->setContentBlockName($input->contentBlock->getName()); + $sheetDefinition = new SheetDefinition(); + $fields = $field['fields'] ?? []; + if ($this->flexFormDefinitionHasDefaultSheet($fields)) { + foreach ($fields as $flexFormField) { + $sheetDefinition->addFieldOrSection($this->resolveFlexFormField($input, $flexFormField)); + } + $flexFormDefinition->addSheet($sheetDefinition); + } else { + foreach ($fields as $sheet) { + $sheetDefinition = new SheetDefinition(); + $identifier = $sheet['identifier']; + $sheetDefinition->setIdentifier($identifier); + + $input->languagePath->addPathSegment('sheets.' . $sheetDefinition->getIdentifier()); + $languagePathLabel = $input->languagePath->getCurrentPath() . '.label'; + $descriptionPathLabel = $input->languagePath->getCurrentPath() . '.description'; + $linkTitlePathLabel = $input->languagePath->getCurrentPath() . '.linkTitle'; + $sheetDefinition->setLanguagePathLabel($languagePathLabel); + $sheetDefinition->setLanguagePathDescription($descriptionPathLabel); + $sheetDefinition->setLanguagePathLinkTitle($linkTitlePathLabel); + $label = (string)($sheet['label'] ?? ''); + $label = $label !== '' ? $label : $identifier; + $description = $sheet['description'] ?? ''; + $linkTitle = $sheet['linkTitle'] ?? ''; + $sheetDefinition->setLabel($label); + $sheetDefinition->setDescription($description); + $sheetDefinition->setLinkTitle($linkTitle); + $languagePathSource = new AutomaticLanguageSource($languagePathLabel, $label); + $descriptionPathSource = new AutomaticLanguageSource($descriptionPathLabel, $description); + $linkTitlePathSource = new AutomaticLanguageSource($linkTitlePathLabel, $linkTitle); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $languagePathSource); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $linkTitlePathSource); + $input->languagePath->popSegment(); + + foreach ($sheet['fields'] ?? [] as $sheetField) { + $sheetDefinition->addFieldOrSection($this->resolveFlexFormField($input, $sheetField)); + } + $flexFormDefinition->addSheet($sheetDefinition); + } + } + + if ($input->getTypeField() !== null) { + $field['ds_pointerField'] = $input->getTypeField(); + } + $field['flexFormDefinitions'][$flexFormDefinition->getTypeName()] = $flexFormDefinition; + return $field; + } + + private function flexFormDefinitionHasDefaultSheet(array $fields): bool + { + foreach ($fields as $flexFormField) { + return FlexFormSubType::tryFrom($flexFormField['type']) !== FlexFormSubType::SHEET; + } + return true; + } + + private function resolveFlexFormField(ProcessingInput $input, array $flexFormField): TcaFieldDefinition|SectionDefinition + { + if (FlexFormSubType::tryFrom($flexFormField['type']) === FlexFormSubType::SECTION) { + return $this->processFlexFormSection($input, $flexFormField); + } + $identifier = $flexFormField['identifier']; + + $input->languagePath->addPathSegment($identifier); + $labelPath = $input->languagePath->getCurrentPath() . '.label'; + $descriptionPath = $input->languagePath->getCurrentPath() . '.description'; + $label = (string)($flexFormField['label'] ?? ''); + $label = $label !== '' ? $label : $identifier; + $flexFormField['label'] = $label; + $description = $flexFormField['description'] ?? ''; + $languagePathSource = new AutomaticLanguageSource($labelPath, $label); + $descriptionPathSource = new AutomaticLanguageSource($descriptionPath, $description); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $languagePathSource); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); + + $flexFormFieldArray = [ + 'uniqueIdentifier' => $identifier, + 'config' => $flexFormField, + 'type' => FieldType::from($flexFormField['type']), + 'labelPath' => $labelPath, + 'descriptionPath' => $descriptionPath, + ]; + $input->languagePath->popSegment(); + $flexFormTcaDefinition = TcaFieldDefinition::createFromArray($flexFormFieldArray); + return $flexFormTcaDefinition; + } + + private function processFlexFormSection(ProcessingInput $input, array $section): SectionDefinition + { + $sectionDefinition = new SectionDefinition(); + $sectionIdentifier = $section['identifier']; + $sectionDefinition->setIdentifier($sectionIdentifier); + + $input->languagePath->addPathSegment('sections.' . $sectionDefinition->getIdentifier()); + $sectionTitle = $input->languagePath->getCurrentPath() . '.title'; + $label = (string)($section['label'] ?? ''); + $label = $label !== '' ? $label : $sectionIdentifier; + $sectionDefinition->setLanguagePathLabel($sectionTitle); + $sectionDefinition->setLabel($label); + $labelPathSource = new AutomaticLanguageSource($sectionTitle, $label); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); + + foreach ($section['container'] as $container) { + $containerIdentifier = $container['identifier']; + $containerDefinition = new ContainerDefinition(); + $containerDefinition->setIdentifier($containerIdentifier); + + $input->languagePath->addPathSegment('container.' . $containerDefinition->getIdentifier()); + $containerTitle = $input->languagePath->getCurrentPath() . '.title'; + $label = (string)($container['label'] ?? ''); + $label = $label !== '' ? $label : $containerIdentifier; + $containerDefinition->setLanguagePathLabel($containerTitle); + $containerDefinition->setLabel($label); + $labelPathSource = new AutomaticLanguageSource($containerTitle, $label); + $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); + + foreach ($container['fields'] as $containerField) { + $containerDefinition->addField($this->resolveFlexFormField($input, $containerField)); + } + $sectionDefinition->addContainer($containerDefinition); + $input->languagePath->popSegment(); + } + $input->languagePath->popSegment(); + return $sectionDefinition; + } + + private function prefixSortFieldIfNecessary(ProcessingInput $input, ProcessedFieldsResult $result): void + { + $sortField = $input->yaml['sortField'] ?? null; + if ($sortField === null) { + return; + } + $sortFieldArray = []; + if (is_string($sortField)) { + $sortFieldArray = [['identifier' => $sortField]]; + $result->tableDefinition->raw['sortField'] = []; + } + if (is_array($sortField)) { + $sortFieldArray = $sortField; + } + if ($sortFieldArray === []) { + return; + } + for ($i = 0; $i < count($sortFieldArray); $i++) { + $sortFieldItem = $sortFieldArray[$i]; + $sortFieldIdentifier = $sortFieldItem['identifier']; + $order = ''; + if (array_key_exists('order', $sortFieldItem)) { + $order = strtolower((string)$sortFieldItem['order']); + if (!in_array($order, ['asc', 'desc'], true)) { + throw new \InvalidArgumentException( + 'order for sortField.order must be one of "asc" or "desc". "' . $order . '" provided.', + 1694014891 + ); + } + } + if ($sortFieldIdentifier !== '' && in_array($sortFieldIdentifier, $result->uniqueFieldIdentifiers, true)) { + $result->tableDefinition->raw['sortField'][$i]['identifier'] = $result->identifierToUniqueMap[$sortFieldIdentifier]; + $result->tableDefinition->raw['sortField'][$i]['order'] = strtoupper($order); + } + } + } + + private function prefixDisplayCondFieldsIfNecessary(ProcessedFieldsResult $result): void + { + foreach ($result->tableDefinition->fields as $currentIdentifier => $tcaFieldDefinition) { + $field = $tcaFieldDefinition['config']; + $displayCond = $field['displayCond'] ?? null; + if ($displayCond === null) { + continue; + } + $isCorrectType = is_string($displayCond) || is_array($displayCond); + $isEmpty = $displayCond === [] || $displayCond === ''; + if (!$isCorrectType || $isEmpty) { + continue; + } + $field['displayCond'] = $this->prefixDisplayCondFieldRecursive($displayCond, $tcaFieldDefinition, $result); + $tcaFieldDefinition['config'] = $field; + $result->tableDefinition->fields[$currentIdentifier] = $tcaFieldDefinition; + } + } + + private function prefixDisplayCondFieldRecursive(string|array $displayCond, array $field, ProcessedFieldsResult $result): string|array + { + if (is_string($displayCond)) { + $displayCond = $this->prefixDisplayCondRule($displayCond, $result); + } else { + foreach ($displayCond as $indexOrOperator => $ruleOrGroup) { + $displayCond[$indexOrOperator] = $this->prefixDisplayCondFieldRecursive($ruleOrGroup, $field, $result); + } + } + return $displayCond; + } + + private function prefixDisplayCondRule(string $displayCond, ProcessedFieldsResult $result): string + { + if (!str_starts_with($displayCond, 'FIELD:')) { + return $displayCond; + } + $parts = explode(':', $displayCond); + $identifier = $parts[1]; + if (!in_array($identifier, $result->uniqueFieldIdentifiers, true)) { + return $displayCond; + } + $parts[1] = $result->identifierToUniqueMap[$identifier]; + $replacedDisplayCond = implode(':', $parts); + return $replacedDisplayCond; + } + + private function prefixLabelFieldIfNecessary(ProcessingInput $input, ProcessedFieldsResult $result): void + { + $labelCapability = LabelCapability::createFromArray($input->yaml); + if (!$labelCapability->hasLabelField()) { + return; + } + $labelFields = $labelCapability->getLabelFieldsAsArray(); + for ($i = 0; $i < count($labelFields); $i++) { + $currentLabelField = $labelFields[$i]; + if (in_array($currentLabelField, $result->uniqueFieldIdentifiers, true)) { + if (is_string($result->tableDefinition->raw['labelField'])) { + $result->tableDefinition->raw['labelField'] = []; + } + $result->tableDefinition->raw['labelField'][$i] = $result->identifierToUniqueMap[$currentLabelField]; + } + } + } + + private function prefixFallbackLabelFieldsIfNecessary(ProcessingInput $input, ProcessedFieldsResult $result): void + { + $labelCapability = LabelCapability::createFromArray($input->yaml); + if (!$labelCapability->hasFallbackLabelFields()) { + return; + } + $fallbackLabelFields = $labelCapability->getFallbackLabelFields(); + for ($i = 0; $i < count($fallbackLabelFields); $i++) { + $currentLabelField = $fallbackLabelFields[$i]; + if (in_array($currentLabelField, $result->uniqueFieldIdentifiers, true)) { + $result->tableDefinition->raw['fallbackLabelFields'][$i] = $result->identifierToUniqueMap[$currentLabelField]; + } + } + } + + private function isPrefixEnabledForField(LoadedContentBlock $contentBlock, array $fieldConfiguration): bool + { + if ($this->isExistingField($fieldConfiguration)) { + return false; + } + if (array_key_exists('prefixField', $fieldConfiguration)) { + return (bool)$fieldConfiguration['prefixField']; + } + return $contentBlock->prefixFields(); + } + + private function isExistingField(array $fieldConfiguration): bool + { + return (bool)($fieldConfiguration['useExistingField'] ?? false); + } + + private function getPrefixType(LoadedContentBlock $contentBlock, array $fieldConfiguration): PrefixType + { + if (array_key_exists('prefixType', $fieldConfiguration)) { + return PrefixType::from($fieldConfiguration['prefixType']); + } + return $contentBlock->getPrefixType(); + } + + private function resolveType(ProcessingInput $input, array $field): FieldType + { + $isExistingField = $this->isExistingField($field); + if ($isExistingField) { + $this->assertIdentifierExists($field, $input); + $identifier = $field['identifier']; + // Check if the field is defined as a "base" TCA field (NOT defined in TCA/Overrides). + if (($GLOBALS['TCA'][$input->table]['columns'][$identifier] ?? []) !== []) { + $fieldType = TypeResolver::resolve($field['identifier'], $input->table); + return $fieldType; + } + } + $this->assertTypeExists($field, $input); + $fieldType = FieldType::tryFrom($field['type']); + if ($fieldType === null) { + $validTypesList = array_map(fn(FieldType $fieldType) => $fieldType->value, FieldType::cases()); + $validTypes = implode(', ', $validTypesList); + throw new \InvalidArgumentException( + 'The type "' . $field['type'] . '" is not a valid type in Content Block "' . $input->contentBlock->getName() . '". Valid types are: ' . $validTypes . '.', + 1697625849 + ); + } + if ($fieldType !== FieldType::LINEBREAK) { + $this->assertIdentifierExists($field, $input); + } + return $fieldType; + } + + private function chooseIdentifier(ProcessingInput $input, array $field): string + { + if (!$input->isRootTable()) { + return $field['identifier']; + } + $prefixEnabled = $this->isPrefixEnabledForField($input->contentBlock, $field); + if (!$prefixEnabled) { + return $field['identifier']; + } + $prefixType = $this->getPrefixType($input->contentBlock, $field); + $uniqueIdentifier = UniqueIdentifierCreator::prefixIdentifier($input->contentBlock, $prefixType, $field['identifier']); + return $uniqueIdentifier; + } + + private function buildBaseLanguagePath(LoadedContentBlock $contentBlock): LanguagePath + { + $baseExtPath = 'LLL:' . $contentBlock->getExtPath(); + $languagePathString = $baseExtPath . '/' . ContentBlockPathUtility::getLanguageFilePath(); + $languagePath = new LanguagePath($languagePathString); + return $languagePath; + } + + private function assertNoLinebreakOutsideOfPalette(FieldType $fieldType, LoadedContentBlock $contentBlock): void + { + if ($fieldType === FieldType::LINEBREAK) { + throw new \InvalidArgumentException( + 'Linebreaks are only allowed within Palettes in Content Block "' . $contentBlock->getName() . '".', + 1679224392 + ); + } + } + + private function assertIdentifierExists(array $field, ProcessingInput $input): void + { + if (!isset($field['identifier'])) { + throw new \InvalidArgumentException( + 'A field is missing the required "identifier" in Content Block "' . $input->contentBlock->getName() . '".', + 1679226075 + ); + } + } + + private function assertTypeExists(array $field, ProcessingInput $input): void + { + if (!isset($field['type'])) { + throw new \InvalidArgumentException( + 'The field "' . ($field['identifier'] ?? '') . '" is missing the required "type" in Content Block "' . $input->contentBlock->getName() . '".', + 1694768937 + ); + } + } + + private function assertUniquePaletteIdentifier(string $identifier, ProcessedFieldsResult $result, LoadedContentBlock $contentBlock): void + { + if (in_array($identifier, $result->uniquePaletteIdentifiers, true)) { + throw new \InvalidArgumentException( + 'The palette identifier "' . $identifier . '" in Content Block "' . $contentBlock->getName() . '" does exist more than once. Please choose unique identifiers.', + 1679168022 + ); + } + } + + private function assertNoPaletteInPalette(FieldType $fieldType, string $identifier, string $rootFieldIdentifier, LoadedContentBlock $contentBlock): void + { + if ($fieldType === FieldType::PALETTE) { + throw new \InvalidArgumentException( + 'Palette "' . $identifier . '" is not allowed inside palette "' . $rootFieldIdentifier . '" in Content Block "' . $contentBlock->getName() . '".', + 1679168602 + ); + } + } + + private function assertNoTabInPalette(FieldType $fieldType, string $identifier, string $rootFieldIdentifier, LoadedContentBlock $contentBlock): void + { + if ($fieldType === FieldType::TAB) { + throw new \InvalidArgumentException( + 'Tab "' . $identifier . '" is not allowed inside palette "' . $rootFieldIdentifier . '" in Content Block "' . $contentBlock->getName() . '".', + 1679245193 + ); + } + } + + private function assertUniqueTabIdentifier(string $identifier, ProcessedFieldsResult $result, LoadedContentBlock $contentBlock): void + { + if (in_array($identifier, $result->uniqueTabIdentifiers, true)) { + throw new \InvalidArgumentException( + 'The tab identifier "' . $identifier . '" in Content Block "' . $contentBlock->getName() . '" does exist more than once. Please choose unique identifiers.', + 1679243686 + ); + } + } + + private function assertUniqueFieldIdentifier(ProcessedFieldsResult $result, LoadedContentBlock $contentBlock): void + { + if (in_array($result->identifier, $result->uniqueFieldIdentifiers, true)) { + throw new \InvalidArgumentException( + 'The identifier "' . $result->identifier . '" in Content Block "' . $contentBlock->getName() . '" does exist more than once. Please choose unique identifiers.', + 1677407942 + ); + } + } + + private function validateFlexFormHasOnlySheetsOrNoSheet(array $field, LoadedContentBlock $contentBlock): void + { + foreach ($field['fields'] ?? [] as $flexField) { + $flexFormType = FlexFormSubType::tryFrom($flexField['type']); + if ($flexFormType !== FlexFormSubType::SHEET) { + $flexFormType = 'nonSheet'; + } + $currentType ??= $flexFormType; + $isValid = $currentType === $flexFormType; + if (!$isValid) { + throw new \InvalidArgumentException( + 'You must not mix Sheets with normal fields inside the FlexForm definition "' . $field['identifier'] . '" in Content Block "' . $contentBlock->getName() . '".', + 1685217163 + ); + } + $currentType = $flexFormType; + } + } + + private function validateFlexFormContainsValidFieldTypes(array $field, LoadedContentBlock $contentBlock): void + { + foreach ($field['fields'] ?? [] as $flexField) { + if (FlexFormSubType::tryFrom($flexField['type']) === FlexFormSubType::SHEET) { + $this->validateFlexFormContainsValidFieldTypes($flexField, $contentBlock); + continue; + } + if (FlexFormSubType::tryFrom($flexField['type']) === FlexFormSubType::SECTION) { + if (empty($flexField['container'])) { + throw new \InvalidArgumentException( + 'FlexForm field "' . $field['identifier'] . '" has a Section "' . $flexField['identifier'] . '" without "container" defined. This is invalid, please add at least one item to "container" in Content Block "' . $contentBlock->getName() . '".', + 1686330220 + ); + } + foreach ($flexField['container'] as $container) { + if (empty($container['fields'])) { + throw new \InvalidArgumentException( + 'FlexForm field "' . $field['identifier'] . '" has a Container in Section "' . $flexField['identifier'] . '" without "fields" defined. This is invalid, please add at least one field to "fields" in Content Block "' . $contentBlock->getName() . '".', + 1686331469 + ); + } + foreach ($container['fields'] as $containerField) { + $containerType = FieldType::from($containerField['type']); + if (!FieldType::isValidFlexFormField($containerType)) { + throw new \InvalidArgumentException( + 'FlexForm field "' . $field['identifier'] . '" has an invalid field of type "' . $containerType->value . '" inside of a "container" item. Please use valid field types in Content Block "' . $contentBlock->getName() . '".', + 1686330594 + ); + } + } + } + continue; + } + $type = FieldType::from($flexField['type']); + if (!FieldType::isValidFlexFormField($type)) { + throw new \InvalidArgumentException( + 'Field type "' . $type->value . '" with identifier "' . $flexField['identifier'] . '" is not allowed inside FlexForm in Content Block "' . $contentBlock->getName() . '".', + 1685220309 + ); + } + } + } +} diff --git a/Classes/Definition/Factory/InitializeContentBlockCache.php b/Classes/Definition/Factory/InitializeContentBlockCache.php new file mode 100644 index 00000000..e9dabb74 --- /dev/null +++ b/Classes/Definition/Factory/InitializeContentBlockCache.php @@ -0,0 +1,34 @@ +tableDefinitionCollectionFactory->initializeCache(); + } +} diff --git a/Classes/Definition/Factory/TableDefinitionCollectionFactory.php b/Classes/Definition/Factory/TableDefinitionCollectionFactory.php index c70b2c6a..8adcc577 100644 --- a/Classes/Definition/Factory/TableDefinitionCollectionFactory.php +++ b/Classes/Definition/Factory/TableDefinitionCollectionFactory.php @@ -17,126 +17,69 @@ namespace TYPO3\CMS\ContentBlocks\Definition\Factory; -use TYPO3\CMS\ContentBlocks\Definition\Capability\LabelCapability; +use Symfony\Component\VarExporter\LazyObjectInterface; use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentType; -use TYPO3\CMS\ContentBlocks\Definition\Factory\Processing\ProcessedFieldsResult; -use TYPO3\CMS\ContentBlocks\Definition\Factory\Processing\ProcessingInput; -use TYPO3\CMS\ContentBlocks\Definition\FlexForm\ContainerDefinition; -use TYPO3\CMS\ContentBlocks\Definition\FlexForm\FlexFormDefinition; -use TYPO3\CMS\ContentBlocks\Definition\FlexForm\SectionDefinition; -use TYPO3\CMS\ContentBlocks\Definition\FlexForm\SheetDefinition; -use TYPO3\CMS\ContentBlocks\Definition\PaletteDefinition; use TYPO3\CMS\ContentBlocks\Definition\TableDefinition; use TYPO3\CMS\ContentBlocks\Definition\TableDefinitionCollection; -use TYPO3\CMS\ContentBlocks\Definition\TCA\LinebreakDefinition; -use TYPO3\CMS\ContentBlocks\Definition\TCA\TabDefinition; -use TYPO3\CMS\ContentBlocks\Definition\TcaFieldDefinition; use TYPO3\CMS\ContentBlocks\Definition\TcaFieldDefinitionCollection; -use TYPO3\CMS\ContentBlocks\FieldConfiguration\FieldType; -use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock; -use TYPO3\CMS\ContentBlocks\Registry\AutomaticLanguageKeysRegistry; -use TYPO3\CMS\ContentBlocks\Registry\AutomaticLanguageSource; use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry; -use TYPO3\CMS\ContentBlocks\Utility\ContentBlockPathUtility; -use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; /** - * This class does the main heavy-lifting of parsing and preparing loaded - * Content Blocks and builds the hierarchy of table definitions, TCA - * record types (here called Content Types) and other TCA specialties like - * Palettes, Tabs or FlexForm. The result `TableDefinitionCollection` contains - * the processed immutable state and is used as an interchange format for - * multiple generators as read-only access. As such, this class does not have - * knowledge about real TCA, only about the YAML definition and how to transform - * it into the internal object format. - * - * List of what this factory does: - * - Builds the shared TableDefinitionCollection object for use in generators. - * - Validates the YAML-schema and errors out in case of incorrect definition. - * - Assigns and tracks unique identifiers for use in database column names. - * - Collects automatic language keys, which can be used to generate the - * Labels.xlf file with a command. - * - Recursively resolves Collections and creates additional anonymous Record - * Types based on them. Meaning one Content Block can indeed define more than - * one Content Type with the use of Collections. - * - Tracks parent references which are used to automatically add `parent_uid` - * fields. This way a Record Type can be an aggregate root or not depending on - * whether it is used in a foreign_table relation or not. - * - Replaces identifiers referenced in configuration options with the prefixed - * identifier (e.g. for the labelField). - * - Dynamically adds new fields depending on config. For example the type field - * or the description column. - * - (YAML-defined) FlexForm parsing / preparation. - * * @internal Not part of TYPO3's public API. */ -final class TableDefinitionCollectionFactory implements SingletonInterface +final class TableDefinitionCollectionFactory { - private TableDefinitionCollection $tableDefinitionCollection; - private AutomaticLanguageKeysRegistry $automaticLanguageKeysRegistry; - public function __construct( - protected readonly ContentBlockRegistry $contentBlockRegistry, + protected readonly LazyObjectInterface|FrontendInterface $cache, + protected readonly ContentBlockCompiler $contentBlockCompiler, ) {} - /** - * This property tracks Collection foreign_table parent references. - * It is needed, because there can be a references to a Content Block, - * which isn't processed yet. Thus, it needs to be collected throughout the run. - * - * @var array - */ - private array $parentReferences = []; - - public function create(): TableDefinitionCollection + public function create(ContentBlockRegistry $contentBlockRegistry): TableDefinitionCollection { - if (isset($this->tableDefinitionCollection)) { - return $this->tableDefinitionCollection; + if (!$this->cache->isLazyObjectInitialized()) { + return $this->createUncached($contentBlockRegistry); + } + $tableDefinitionCollection = $this->cache->get('Compiled_ContentBlocks'); + if ($tableDefinitionCollection === false) { + $tableDefinitionCollection = $this->createUncached($contentBlockRegistry); + $this->cache->set('Compiled_ContentBlocks', $tableDefinitionCollection); } - return $this->createUncached(); + return $tableDefinitionCollection; } - public function createUncached(): TableDefinitionCollection + public function initializeCache(): void { - $this->automaticLanguageKeysRegistry = new AutomaticLanguageKeysRegistry(); - $tableDefinitionList = []; - foreach ($this->contentBlockRegistry->getAll() as $contentBlock) { - $table = $contentBlock->getYaml()['table']; - $languagePath = $this->buildBaseLanguagePath($contentBlock); - $processingInput = new ProcessingInput( - yaml: $contentBlock->getYaml(), - contentBlock: $contentBlock, - table: $table, - rootTable: $table, - languagePath: $languagePath, - contentType: $contentBlock->getContentType(), - tableDefinitionList: $tableDefinitionList, - ); - $tableDefinitionList = $this->processRootFields($processingInput); + if (!$this->cache->isLazyObjectInitialized()) { + $this->cache->initializeLazyObject(); } - $tableDefinitionCollection = new TableDefinitionCollection($this->automaticLanguageKeysRegistry); - $mergedTableDefinitionList = $this->mergeProcessingResult($tableDefinitionList); - $this->enrichTableDefinitions($mergedTableDefinitionList, $tableDefinitionCollection); - $this->resetState(); - $this->tableDefinitionCollection = $tableDefinitionCollection; - return $this->tableDefinitionCollection; } - private function enrichTableDefinitions( - array $mergedTableDefinitionList, - TableDefinitionCollection $tableDefinitionCollection - ): void { - foreach ($mergedTableDefinitionList as $table => $tableDefinition) { + public function createUncached(ContentBlockRegistry $contentBlockRegistry): TableDefinitionCollection + { + $compiledContentBlocks = $this->contentBlockCompiler->compile($contentBlockRegistry); + $tableDefinitionCollection = $this->enrichTableDefinitions($compiledContentBlocks); + return $tableDefinitionCollection; + } + + private function enrichTableDefinitions(CompilationResult $compilationResult): TableDefinitionCollection + { + $automaticLanguageKeysRegistry = $compilationResult->getAutomaticLanguageKeys(); + $tableDefinitionCollection = new TableDefinitionCollection($automaticLanguageKeysRegistry); + foreach ($compilationResult->getMergedTableDefinitions() as $table => $tableDefinition) { $newTableDefinition = TableDefinition::createFromTableArray($table, $tableDefinition); - $newTableDefinition = $this->enrichCollectionsWithParentReference($newTableDefinition); + $newTableDefinition = $this->enrichCollectionsWithParentReference($compilationResult, $newTableDefinition); $tableDefinitionCollection->addTable($newTableDefinition); } + return $tableDefinitionCollection; } - private function enrichCollectionsWithParentReference(TableDefinition $newTableDefinition): TableDefinition - { - if (isset($this->parentReferences[$newTableDefinition->getTable()])) { - $references = $this->parentReferences[$newTableDefinition->getTable()]; + private function enrichCollectionsWithParentReference( + CompilationResult $compilationResult, + TableDefinition $newTableDefinition + ): TableDefinition { + if (isset($compilationResult->getParentReferences()[$newTableDefinition->getTable()])) { + $references = $compilationResult->getParentReferences()[$newTableDefinition->getTable()]; $tcaFieldDefinitionCollection = TcaFieldDefinitionCollection::createFromArray( $references, $newTableDefinition->getTable() @@ -146,22 +89,6 @@ private function enrichCollectionsWithParentReference(TableDefinition $newTableD return $newTableDefinition; } - private function resetState(): void - { - $this->parentReferences = []; - unset($this->automaticLanguageKeysRegistry); - } - - private function mergeProcessingResult(array $tableDefinitionList): array - { - $mergedResult = []; - foreach ($tableDefinitionList as $table => $definition) { - $mergedResult[$table] = array_replace_recursive(...array_reverse($definition['tableDefinitions'])); - $mergedResult[$table]['typeDefinitions'] = $definition['typeDefinitions']; - } - return $mergedResult; - } - private function enrichTableDefinition( TcaFieldDefinitionCollection $references, TableDefinition $newTableDefinition, @@ -177,876 +104,4 @@ private function enrichTableDefinition( } return $newTableDefinition; } - - private function processRootFields(ProcessingInput $input): array - { - $result = new ProcessedFieldsResult($input); - $this->initializeContentTypeLabelAndDescription($input, $result); - $yaml = $input->yaml; - $yaml = $this->prepareYaml($result, $yaml); - $yamlFields = $yaml['fields'] ?? []; - foreach ($yamlFields as $rootField) { - $fields = $this->handleRootField($rootField, $input, $result); - $this->processFields($input, $result, $fields); - } - $this->prefixTcaConfigFields($input, $result); - $this->collectOverrideColumns($result); - $this->collectDefinitions($input, $result); - return $result->tableDefinitionList; - } - - private function processFields(ProcessingInput $input, ProcessedFieldsResult $result, array $fields): void - { - foreach ($fields as $field) { - $this->initializeField($input, $result, $field); - $input->languagePath->addPathSegment($result->identifier); - $field = $this->initializeFieldLabelAndDescription($input, $result, $field); - if ($result->fieldType === FieldType::FLEXFORM) { - $field = $this->processFlexForm($input, $field); - } - if ($result->fieldType->hasItems()) { - $field = $this->collectItemLabels($input, $result, $field); - } - $result->tcaFieldDefinition = $this->buildTcaFieldDefinitionArray($input, $result, $field); - if ($result->fieldType === FieldType::COLLECTION) { - $this->processCollection($input, $result, $field); - } - $this->collectProcessedField($result); - $input->languagePath->popSegment(); - $result->resetTemporaryState(); - } - } - - private function initializeField(ProcessingInput $input, ProcessedFieldsResult $result, array $field): void - { - $result->identifier = (string)$field['identifier']; - $this->assertUniqueFieldIdentifier($result, $input->contentBlock); - $result->uniqueFieldIdentifiers[] = $result->identifier; - $result->fieldType = $this->resolveType($input, $field); - $result->uniqueIdentifier = $this->chooseIdentifier($input, $field); - $result->identifierToUniqueMap[$result->identifier] = $result->uniqueIdentifier; - } - - private function prepareYaml(ProcessedFieldsResult $result, array $yaml): array - { - $contentType = $result->tableDefinition->contentType; - if ($contentType === ContentType::RECORD_TYPE && $result->tableDefinition->hasTypeField()) { - $yamlFields = $yaml['fields'] ?? []; - $yamlFields = $this->prependTypeFieldForRecordType($yamlFields, $result); - $yaml['fields'] = $yamlFields; - } - $hasInternalDescription = $yaml['internalDescription'] ?? false; - if ($contentType === ContentType::RECORD_TYPE && $hasInternalDescription) { - $yamlFields = $yaml['fields'] ?? []; - $yamlFields = $this->appendInternalDescription($yamlFields); - $yaml['fields'] = $yamlFields; - } - if ($contentType === ContentType::PAGE_TYPE) { - $yamlFields = $yaml['fields'] ?? []; - $yamlFields = $this->prependPagesTitlePalette($yamlFields); - $yaml['fields'] = $yamlFields; - } - return $yaml; - } - - private function initializeContentTypeLabelAndDescription(ProcessingInput $input, ProcessedFieldsResult $result): void - { - $languagePathTitle = $input->languagePath->getCurrentPath(); - $languagePathDescription = $input->languagePath->getCurrentPath(); - $title = (string)($input->yaml['title'] ?? ''); - $description = (string)($input->yaml['description'] ?? ''); - if ($input->isRootTable()) { - // Ensure there is always a title for a Content Type. - $title = $title !== '' ? $title : $input->contentBlock->getName(); - $result->contentType->title = $title; - $result->contentType->description = $description; - $languagePathTitle = $languagePathTitle . 'title'; - $languagePathDescription = $languagePathDescription . 'description'; - $languagePathSource = new AutomaticLanguageSource($languagePathTitle, $title); - $descriptionPathSource = new AutomaticLanguageSource($languagePathDescription, $description); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $languagePathSource); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); - } else { - $languagePathTitle = $languagePathTitle . '.label'; - $languagePathDescription = $languagePathDescription . '.description'; - $result->contentType->title = $title; - $result->contentType->description = $description; - } - $result->contentType->languagePathTitle = $languagePathTitle; - $result->contentType->languagePathDescription = $languagePathDescription; - } - - private function initializeFieldLabelAndDescription(ProcessingInput $input, ProcessedFieldsResult $result, array $field): array - { - $labelPath = $this->getFieldLabelPath($input->languagePath); - $descriptionPath = $this->getFieldDescriptionPath($input->languagePath); - $title = (string)($field['label'] ?? ''); - // Never fall back to identifiers for existing fields. They have their standard translation. - $title = ($title !== '' || $this->isExistingField($field)) ? $title : $result->identifier; - $field['label'] = $title; - $description = $field['description'] ?? ''; - $labelPathSource = new AutomaticLanguageSource($labelPath, $title); - $descriptionPathSource = new AutomaticLanguageSource($descriptionPath, $description); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); - return $field; - } - - private function collectItemLabels(ProcessingInput $input, ProcessedFieldsResult $result, array $field): array - { - $items = $field['items'] ?? []; - $fieldType = $result->fieldType; - foreach ($items as $index => $item) { - $label = (string)($item['label'] ?? ''); - $currentPath = $input->languagePath->getCurrentPath(); - if ($fieldType === FieldType::CHECKBOX) { - $labelPath = $currentPath . '.items.' . $index . '.label'; - } else { - $value = (string)($item['value'] ?? ''); - if ($value === '') { - $labelPath = $currentPath . '.items.label'; - } else { - $labelPath = $currentPath . '.items.' . $value . '.label'; - } - } - $field['items'][$index]['labelPath'] = $labelPath; - $labelPathSource = new AutomaticLanguageSource($labelPath, $label); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); - } - return $field; - } - - private function buildTcaFieldDefinitionArray( - ProcessingInput $input, - ProcessedFieldsResult $result, - array $field, - ): array { - $tcaFieldDefinition = [ - 'parentTable' => $input->contentBlock->getContentType()->getTable(), - 'uniqueIdentifier' => $result->uniqueIdentifier, - 'config' => $field, - 'type' => $result->fieldType, - 'labelPath' => $this->getFieldLabelPath($input->languagePath), - 'descriptionPath' => $this->getFieldDescriptionPath($input->languagePath), - ]; - return $tcaFieldDefinition; - } - - private function getFieldLabelPath(LanguagePath $languagePath): string - { - return $languagePath->getCurrentPath() . '.label'; - } - - private function getFieldDescriptionPath(LanguagePath $languagePath): string - { - return $languagePath->getCurrentPath() . '.description'; - } - - private function prefixTcaConfigFields(ProcessingInput $input, ProcessedFieldsResult $result): void - { - $this->prefixSortFieldIfNecessary($input, $result); - $this->prefixLabelFieldIfNecessary($input, $result); - $this->prefixFallbackLabelFieldsIfNecessary($input, $result); - $this->prefixDisplayCondFieldsIfNecessary($result); - } - - private function prependTypeFieldForRecordType(array $yamlFields, ProcessedFieldsResult $result): array - { - $typeFieldDefinition = [ - 'identifier' => $result->tableDefinition->typeField, - 'type' => FieldType::SELECT->value, - 'renderType' => 'selectSingle', - 'prefixField' => false, - 'default' => $result->contentType->typeName, - 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.type', - 'items' => [], - ]; - // Prepend type field. - array_unshift($yamlFields, $typeFieldDefinition); - return $yamlFields; - } - - private function appendInternalDescription(array $yamlFields): array - { - $tab = [ - 'identifier' => 'internal_description_tab', - 'type' => 'Tab', - 'label' => 'LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes', - ]; - $internalDescription = [ - 'identifier' => 'internal_description', - 'type' => 'Textarea', - 'prefixField' => false, - 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.description', - 'rows' => 5, - 'cols' => 30, - ]; - $yamlFields[] = $tab; - $yamlFields[] = $internalDescription; - return $yamlFields; - } - - private function prependPagesTitlePalette(array $yamlFields): array - { - $titlePalette = [ - 'identifier' => 'content_blocks_titleonly', - 'type' => 'Palette', - 'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.palettes.title', - 'prefixField' => false, - 'fields' => [ - [ - 'identifier' => 'title', - 'useExistingField' => true, - 'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.title_formlabel', - ], - [ - 'type' => 'Linebreak', - ], - [ - 'identifier' => 'slug', - 'useExistingField' => true, - ], - [ - 'type' => 'Linebreak', - ], - [ - 'identifier' => 'nav_title', - 'useExistingField' => true, - 'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.nav_title_formlabel', - ], - ], - ]; - array_unshift($yamlFields, $titlePalette); - return $yamlFields; - } - - private function handleRootField(array $rootField, ProcessingInput $input, ProcessedFieldsResult $result): array - { - $rootFieldType = $this->resolveType($input, $rootField); - $this->assertNoLinebreakOutsideOfPalette($rootFieldType, $input->contentBlock); - $fields = match ($rootFieldType) { - Fieldtype::PALETTE => $this->handlePalette($input, $result, $rootField), - FieldType::TAB => $this->handleTab($input, $result, $rootField), - default => $this->handleDefault($input, $result, $rootField) - }; - return $fields; - } - - private function handleDefault(ProcessingInput $input, ProcessedFieldsResult $result, array $field): array - { - $result->contentType->showItems[] = $this->chooseIdentifier($input, $field); - return [$field]; - } - - private function handlePalette(ProcessingInput $input, ProcessedFieldsResult $result, array $rootPalette): array - { - $rootPaletteIdentifier = $rootPalette['identifier']; - $this->assertUniquePaletteIdentifier($rootPaletteIdentifier, $result, $input->contentBlock); - $result->uniquePaletteIdentifiers[] = $rootPaletteIdentifier; - // Ignore empty Palettes. - if (($rootPalette['fields'] ?? []) === []) { - return []; - } - $fields = []; - $paletteItems = []; - foreach ($rootPalette['fields'] as $paletteField) { - $paletteFieldType = $this->resolveType($input, $paletteField); - if ($paletteFieldType === FieldType::LINEBREAK) { - $paletteItems[] = new LinebreakDefinition(); - } else { - $this->assertNoPaletteInPalette($paletteFieldType, $paletteField['identifier'], $rootPaletteIdentifier, $input->contentBlock); - $this->assertNoTabInPalette($paletteFieldType, $paletteField['identifier'], $rootPaletteIdentifier, $input->contentBlock); - $fields[] = $paletteField; - $paletteItems[] = $this->chooseIdentifier($input, $paletteField); - } - } - - $input->languagePath->addPathSegment('palettes.' . $rootPaletteIdentifier); - $label = (string)($rootPalette['label'] ?? ''); - $description = $rootPalette['description'] ?? ''; - $languagePathLabel = $input->languagePath->getCurrentPath() . '.label'; - $languagePathDescription = $input->languagePath->getCurrentPath() . '.description'; - $labelPathSource = new AutomaticLanguageSource($languagePathLabel, $label); - $descriptionPathSource = new AutomaticLanguageSource($languagePathDescription, $description); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); - $input->languagePath->popSegment(); - - $paletteIdentifier = $this->chooseIdentifier($input, $rootPalette); - $palette = [ - 'contentBlockName' => $input->contentBlock->getName(), - 'identifier' => $paletteIdentifier, - 'label' => $label, - 'description' => $description, - 'languagePathLabel' => $languagePathLabel, - 'languagePathDescription' => $languagePathDescription, - 'items' => $paletteItems, - ]; - $result->tableDefinition->palettes[$paletteIdentifier] = $palette; - $result->contentType->showItems[] = PaletteDefinition::createFromArray($palette); - return $fields; - } - - private function handleTab(ProcessingInput $input, ProcessedFieldsResult $result, array $field): array - { - $identifier = $field['identifier']; - $this->assertUniqueTabIdentifier($identifier, $result, $input->contentBlock); - $result->uniqueTabIdentifiers[] = $identifier; - - $input->languagePath->addPathSegment('tabs.' . $identifier); - $label = (string)($field['label'] ?? ''); - $label = $label !== '' ? $label : $identifier; - $languagePathLabel = $input->languagePath->getCurrentPath(); - $languagePathSource = new AutomaticLanguageSource($languagePathLabel, $label); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $languagePathSource); - $input->languagePath->popSegment(); - - $tabDefinitionArray = [ - 'identifier' => $identifier, - 'contentBlockName' => $input->contentBlock->getName(), - 'label' => $label, - 'languagePathLabel' => $languagePathLabel, - ]; - $tabDefinition = TabDefinition::createFromArray($tabDefinitionArray); - $result->contentType->showItems[] = $tabDefinition; - return []; - } - - private function collectProcessedField(ProcessedFieldsResult $result): void - { - $result->tableDefinition->fields[$result->uniqueIdentifier] = $result->tcaFieldDefinition; - $result->contentType->columns[] = $result->uniqueIdentifier; - } - - private function collectOverrideColumns(ProcessedFieldsResult $result): void - { - foreach ($result->tableDefinition->fields as $uniqueIdentifier => $tcaFieldDefinition) { - $isTypeField = $uniqueIdentifier === $result->tableDefinition->typeField; - if (!$isTypeField) { - $overrideColumn = TcaFieldDefinition::createFromArray($tcaFieldDefinition); - $result->contentType->overrideColumns[] = $overrideColumn; - } - } - } - - /** - * Collect table definitions and Content Types and carry them over to the next stack. - * This factory will merge the table definitions and type definitions at the very end. - */ - private function collectDefinitions(ProcessingInput $input, ProcessedFieldsResult $result): void - { - $table = $input->table; - $tableDefinition = $result->tableDefinition->toArray(); - $result->tableDefinitionList[$table]['tableDefinitions'][] = $tableDefinition; - $isRootTable = $input->isRootTable(); - $identifier = $input->yaml['identifier'] ?? ''; - $typeDefinition = $result->contentType->toArray($isRootTable, $identifier); - $result->tableDefinitionList[$table]['typeDefinitions'][] = $typeDefinition; - } - - private function processCollection(ProcessingInput $input, ProcessedFieldsResult $result, array $field): void - { - $isExternalCollection = array_key_exists('foreign_table', $field); - $this->assignRelationConfigToCollectionField($field, $result); - $foreignTable = $result->tcaFieldDefinition['config']['foreign_table']; - $this->parentReferences[$foreignTable][] = $result->tcaFieldDefinition; - $fields = $field['fields'] ?? []; - if ($isExternalCollection || $fields === []) { - return; - } - $field['title'] = $field['label']; - $newInput = new ProcessingInput( - yaml: $field, - contentBlock: $input->contentBlock, - table: $foreignTable, - rootTable: $input->rootTable, - languagePath: $input->languagePath, - contentType: ContentType::RECORD_TYPE, - tableDefinitionList: $result->tableDefinitionList, - ); - $result->tableDefinitionList = $this->processRootFields($newInput); - } - - private function assignRelationConfigToCollectionField(array $field, ProcessedFieldsResult $result): void - { - $isExternalCollection = array_key_exists('foreign_table', $field); - $result->tcaFieldDefinition['config']['foreign_field'] ??= 'foreign_table_parent_uid'; - if ($isExternalCollection) { - if ($field['shareAcrossTables'] ?? false) { - $result->tcaFieldDefinition['config']['foreign_table_field'] ??= 'tablenames'; - } - if ($field['shareAcrossFields'] ?? false) { - $result->tcaFieldDefinition['config']['foreign_match_fields']['fieldname'] = $result->uniqueIdentifier; - } - } else { - $result->tcaFieldDefinition['config']['foreign_table'] = $field['table'] ?? $result->uniqueIdentifier; - } - } - - private function processFlexForm(ProcessingInput $input, array $field): array - { - $this->validateFlexFormHasOnlySheetsOrNoSheet($field, $input->contentBlock); - $this->validateFlexFormContainsValidFieldTypes($field, $input->contentBlock); - $flexFormDefinition = new FlexFormDefinition(); - $flexFormTypeName = $input->getTypeField() !== null ? $input->getTypeName() : 'default'; - $flexFormDefinition->setTypeName($flexFormTypeName); - $flexFormDefinition->setContentBlockName($input->contentBlock->getName()); - $sheetDefinition = new SheetDefinition(); - $fields = $field['fields'] ?? []; - if ($this->flexFormDefinitionHasDefaultSheet($fields)) { - foreach ($fields as $flexFormField) { - $sheetDefinition->addFieldOrSection($this->resolveFlexFormField($input, $flexFormField)); - } - $flexFormDefinition->addSheet($sheetDefinition); - } else { - foreach ($fields as $sheet) { - $sheetDefinition = new SheetDefinition(); - $identifier = $sheet['identifier']; - $sheetDefinition->setIdentifier($identifier); - - $input->languagePath->addPathSegment('sheets.' . $sheetDefinition->getIdentifier()); - $languagePathLabel = $input->languagePath->getCurrentPath() . '.label'; - $descriptionPathLabel = $input->languagePath->getCurrentPath() . '.description'; - $linkTitlePathLabel = $input->languagePath->getCurrentPath() . '.linkTitle'; - $sheetDefinition->setLanguagePathLabel($languagePathLabel); - $sheetDefinition->setLanguagePathDescription($descriptionPathLabel); - $sheetDefinition->setLanguagePathLinkTitle($linkTitlePathLabel); - $label = (string)($sheet['label'] ?? ''); - $label = $label !== '' ? $label : $identifier; - $description = $sheet['description'] ?? ''; - $linkTitle = $sheet['linkTitle'] ?? ''; - $sheetDefinition->setLabel($label); - $sheetDefinition->setDescription($description); - $sheetDefinition->setLinkTitle($linkTitle); - $languagePathSource = new AutomaticLanguageSource($languagePathLabel, $label); - $descriptionPathSource = new AutomaticLanguageSource($descriptionPathLabel, $description); - $linkTitlePathSource = new AutomaticLanguageSource($linkTitlePathLabel, $linkTitle); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $languagePathSource); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $linkTitlePathSource); - $input->languagePath->popSegment(); - - foreach ($sheet['fields'] ?? [] as $sheetField) { - $sheetDefinition->addFieldOrSection($this->resolveFlexFormField($input, $sheetField)); - } - $flexFormDefinition->addSheet($sheetDefinition); - } - } - - if ($input->getTypeField() !== null) { - $field['ds_pointerField'] = $input->getTypeField(); - } - $field['flexFormDefinitions'][$flexFormDefinition->getTypeName()] = $flexFormDefinition; - return $field; - } - - private function flexFormDefinitionHasDefaultSheet(array $fields): bool - { - foreach ($fields as $flexFormField) { - return FlexFormSubType::tryFrom($flexFormField['type']) !== FlexFormSubType::SHEET; - } - return true; - } - - private function resolveFlexFormField(ProcessingInput $input, array $flexFormField): TcaFieldDefinition|SectionDefinition - { - if (FlexFormSubType::tryFrom($flexFormField['type']) === FlexFormSubType::SECTION) { - return $this->processFlexFormSection($input, $flexFormField); - } - $identifier = $flexFormField['identifier']; - - $input->languagePath->addPathSegment($identifier); - $labelPath = $input->languagePath->getCurrentPath() . '.label'; - $descriptionPath = $input->languagePath->getCurrentPath() . '.description'; - $label = (string)($flexFormField['label'] ?? ''); - $label = $label !== '' ? $label : $identifier; - $flexFormField['label'] = $label; - $description = $flexFormField['description'] ?? ''; - $languagePathSource = new AutomaticLanguageSource($labelPath, $label); - $descriptionPathSource = new AutomaticLanguageSource($descriptionPath, $description); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $languagePathSource); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $descriptionPathSource); - - $flexFormFieldArray = [ - 'uniqueIdentifier' => $identifier, - 'config' => $flexFormField, - 'type' => FieldType::from($flexFormField['type']), - 'labelPath' => $labelPath, - 'descriptionPath' => $descriptionPath, - ]; - $input->languagePath->popSegment(); - $flexFormTcaDefinition = TcaFieldDefinition::createFromArray($flexFormFieldArray); - return $flexFormTcaDefinition; - } - - private function processFlexFormSection(ProcessingInput $input, array $section): SectionDefinition - { - $sectionDefinition = new SectionDefinition(); - $sectionIdentifier = $section['identifier']; - $sectionDefinition->setIdentifier($sectionIdentifier); - - $input->languagePath->addPathSegment('sections.' . $sectionDefinition->getIdentifier()); - $sectionTitle = $input->languagePath->getCurrentPath() . '.title'; - $label = (string)($section['label'] ?? ''); - $label = $label !== '' ? $label : $sectionIdentifier; - $sectionDefinition->setLanguagePathLabel($sectionTitle); - $sectionDefinition->setLabel($label); - $labelPathSource = new AutomaticLanguageSource($sectionTitle, $label); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); - - foreach ($section['container'] as $container) { - $containerIdentifier = $container['identifier']; - $containerDefinition = new ContainerDefinition(); - $containerDefinition->setIdentifier($containerIdentifier); - - $input->languagePath->addPathSegment('container.' . $containerDefinition->getIdentifier()); - $containerTitle = $input->languagePath->getCurrentPath() . '.title'; - $label = (string)($container['label'] ?? ''); - $label = $label !== '' ? $label : $containerIdentifier; - $containerDefinition->setLanguagePathLabel($containerTitle); - $containerDefinition->setLabel($label); - $labelPathSource = new AutomaticLanguageSource($containerTitle, $label); - $this->automaticLanguageKeysRegistry->addKey($input->contentBlock, $labelPathSource); - - foreach ($container['fields'] as $containerField) { - $containerDefinition->addField($this->resolveFlexFormField($input, $containerField)); - } - $sectionDefinition->addContainer($containerDefinition); - $input->languagePath->popSegment(); - } - $input->languagePath->popSegment(); - return $sectionDefinition; - } - - private function prefixSortFieldIfNecessary(ProcessingInput $input, ProcessedFieldsResult $result): void - { - $sortField = $input->yaml['sortField'] ?? null; - if ($sortField === null) { - return; - } - $sortFieldArray = []; - if (is_string($sortField)) { - $sortFieldArray = [['identifier' => $sortField]]; - $result->tableDefinition->raw['sortField'] = []; - } - if (is_array($sortField)) { - $sortFieldArray = $sortField; - } - if ($sortFieldArray === []) { - return; - } - for ($i = 0; $i < count($sortFieldArray); $i++) { - $sortFieldItem = $sortFieldArray[$i]; - $sortFieldIdentifier = $sortFieldItem['identifier']; - $order = ''; - if (array_key_exists('order', $sortFieldItem)) { - $order = strtolower((string)$sortFieldItem['order']); - if (!in_array($order, ['asc', 'desc'], true)) { - throw new \InvalidArgumentException( - 'order for sortField.order must be one of "asc" or "desc". "' . $order . '" provided.', - 1694014891 - ); - } - } - if ($sortFieldIdentifier !== '' && in_array($sortFieldIdentifier, $result->uniqueFieldIdentifiers, true)) { - $result->tableDefinition->raw['sortField'][$i]['identifier'] = $result->identifierToUniqueMap[$sortFieldIdentifier]; - $result->tableDefinition->raw['sortField'][$i]['order'] = strtoupper($order); - } - } - } - - private function prefixDisplayCondFieldsIfNecessary(ProcessedFieldsResult $result): void - { - foreach ($result->tableDefinition->fields as $currentIdentifier => $tcaFieldDefinition) { - $field = $tcaFieldDefinition['config']; - $displayCond = $field['displayCond'] ?? null; - if ($displayCond === null) { - continue; - } - $isCorrectType = is_string($displayCond) || is_array($displayCond); - $isEmpty = $displayCond === [] || $displayCond === ''; - if (!$isCorrectType || $isEmpty) { - continue; - } - $field['displayCond'] = $this->prefixDisplayCondFieldRecursive($displayCond, $tcaFieldDefinition, $result); - $tcaFieldDefinition['config'] = $field; - $result->tableDefinition->fields[$currentIdentifier] = $tcaFieldDefinition; - } - } - - private function prefixDisplayCondFieldRecursive(string|array $displayCond, array $field, ProcessedFieldsResult $result): string|array - { - if (is_string($displayCond)) { - $displayCond = $this->prefixDisplayCondRule($displayCond, $result); - } else { - foreach ($displayCond as $indexOrOperator => $ruleOrGroup) { - $displayCond[$indexOrOperator] = $this->prefixDisplayCondFieldRecursive($ruleOrGroup, $field, $result); - } - } - return $displayCond; - } - - private function prefixDisplayCondRule(string $displayCond, ProcessedFieldsResult $result): string - { - if (!str_starts_with($displayCond, 'FIELD:')) { - return $displayCond; - } - $parts = explode(':', $displayCond); - $identifier = $parts[1]; - if (!in_array($identifier, $result->uniqueFieldIdentifiers, true)) { - return $displayCond; - } - $parts[1] = $result->identifierToUniqueMap[$identifier]; - $replacedDisplayCond = implode(':', $parts); - return $replacedDisplayCond; - } - - private function prefixLabelFieldIfNecessary(ProcessingInput $input, ProcessedFieldsResult $result): void - { - $labelCapability = LabelCapability::createFromArray($input->yaml); - if (!$labelCapability->hasLabelField()) { - return; - } - $labelFields = $labelCapability->getLabelFieldsAsArray(); - for ($i = 0; $i < count($labelFields); $i++) { - $currentLabelField = $labelFields[$i]; - if (in_array($currentLabelField, $result->uniqueFieldIdentifiers, true)) { - if (is_string($result->tableDefinition->raw['labelField'])) { - $result->tableDefinition->raw['labelField'] = []; - } - $result->tableDefinition->raw['labelField'][$i] = $result->identifierToUniqueMap[$currentLabelField]; - } - } - } - - private function prefixFallbackLabelFieldsIfNecessary(ProcessingInput $input, ProcessedFieldsResult $result): void - { - $labelCapability = LabelCapability::createFromArray($input->yaml); - if (!$labelCapability->hasFallbackLabelFields()) { - return; - } - $fallbackLabelFields = $labelCapability->getFallbackLabelFields(); - for ($i = 0; $i < count($fallbackLabelFields); $i++) { - $currentLabelField = $fallbackLabelFields[$i]; - if (in_array($currentLabelField, $result->uniqueFieldIdentifiers, true)) { - $result->tableDefinition->raw['fallbackLabelFields'][$i] = $result->identifierToUniqueMap[$currentLabelField]; - } - } - } - - private function isPrefixEnabledForField(LoadedContentBlock $contentBlock, array $fieldConfiguration): bool - { - if ($this->isExistingField($fieldConfiguration)) { - return false; - } - if (array_key_exists('prefixField', $fieldConfiguration)) { - return (bool)$fieldConfiguration['prefixField']; - } - return $contentBlock->prefixFields(); - } - - private function isExistingField(array $fieldConfiguration): bool - { - return (bool)($fieldConfiguration['useExistingField'] ?? false); - } - - private function getPrefixType(LoadedContentBlock $contentBlock, array $fieldConfiguration): PrefixType - { - if (array_key_exists('prefixType', $fieldConfiguration)) { - return PrefixType::from($fieldConfiguration['prefixType']); - } - return $contentBlock->getPrefixType(); - } - - private function resolveType(ProcessingInput $input, array $field): FieldType - { - $isExistingField = $this->isExistingField($field); - if ($isExistingField) { - $this->assertIdentifierExists($field, $input); - $identifier = $field['identifier']; - // Check if the field is defined as a "base" TCA field (NOT defined in TCA/Overrides). - if (($GLOBALS['TCA'][$input->table]['columns'][$identifier] ?? []) !== []) { - $fieldType = TypeResolver::resolve($field['identifier'], $input->table); - return $fieldType; - } - } - $this->assertTypeExists($field, $input); - $fieldType = FieldType::tryFrom($field['type']); - if ($fieldType === null) { - $validTypesList = array_map(fn(FieldType $fieldType) => $fieldType->value, FieldType::cases()); - $validTypes = implode(', ', $validTypesList); - throw new \InvalidArgumentException( - 'The type "' . $field['type'] . '" is not a valid type in Content Block "' . $input->contentBlock->getName() . '". Valid types are: ' . $validTypes . '.', - 1697625849 - ); - } - if ($fieldType !== FieldType::LINEBREAK) { - $this->assertIdentifierExists($field, $input); - } - return $fieldType; - } - - private function chooseIdentifier(ProcessingInput $input, array $field): string - { - if (!$input->isRootTable()) { - return $field['identifier']; - } - $prefixEnabled = $this->isPrefixEnabledForField($input->contentBlock, $field); - if (!$prefixEnabled) { - return $field['identifier']; - } - $prefixType = $this->getPrefixType($input->contentBlock, $field); - $uniqueIdentifier = UniqueIdentifierCreator::prefixIdentifier($input->contentBlock, $prefixType, $field['identifier']); - return $uniqueIdentifier; - } - - private function buildBaseLanguagePath(LoadedContentBlock $contentBlock): LanguagePath - { - $baseExtPath = 'LLL:' . $contentBlock->getExtPath(); - $languagePathString = $baseExtPath . '/' . ContentBlockPathUtility::getLanguageFilePath(); - $languagePath = new LanguagePath($languagePathString); - return $languagePath; - } - - private function assertNoLinebreakOutsideOfPalette(FieldType $fieldType, LoadedContentBlock $contentBlock): void - { - if ($fieldType === FieldType::LINEBREAK) { - throw new \InvalidArgumentException( - 'Linebreaks are only allowed within Palettes in Content Block "' . $contentBlock->getName() . '".', - 1679224392 - ); - } - } - - private function assertIdentifierExists(array $field, ProcessingInput $input): void - { - if (!isset($field['identifier'])) { - throw new \InvalidArgumentException( - 'A field is missing the required "identifier" in Content Block "' . $input->contentBlock->getName() . '".', - 1679226075 - ); - } - } - - private function assertTypeExists(array $field, ProcessingInput $input): void - { - if (!isset($field['type'])) { - throw new \InvalidArgumentException( - 'The field "' . ($field['identifier'] ?? '') . '" is missing the required "type" in Content Block "' . $input->contentBlock->getName() . '".', - 1694768937 - ); - } - } - - private function assertUniquePaletteIdentifier(string $identifier, ProcessedFieldsResult $result, LoadedContentBlock $contentBlock): void - { - if (in_array($identifier, $result->uniquePaletteIdentifiers, true)) { - throw new \InvalidArgumentException( - 'The palette identifier "' . $identifier . '" in Content Block "' . $contentBlock->getName() . '" does exist more than once. Please choose unique identifiers.', - 1679168022 - ); - } - } - - private function assertNoPaletteInPalette(FieldType $fieldType, string $identifier, string $rootFieldIdentifier, LoadedContentBlock $contentBlock): void - { - if ($fieldType === FieldType::PALETTE) { - throw new \InvalidArgumentException( - 'Palette "' . $identifier . '" is not allowed inside palette "' . $rootFieldIdentifier . '" in Content Block "' . $contentBlock->getName() . '".', - 1679168602 - ); - } - } - - private function assertNoTabInPalette(FieldType $fieldType, string $identifier, string $rootFieldIdentifier, LoadedContentBlock $contentBlock): void - { - if ($fieldType === FieldType::TAB) { - throw new \InvalidArgumentException( - 'Tab "' . $identifier . '" is not allowed inside palette "' . $rootFieldIdentifier . '" in Content Block "' . $contentBlock->getName() . '".', - 1679245193 - ); - } - } - - private function assertUniqueTabIdentifier(string $identifier, ProcessedFieldsResult $result, LoadedContentBlock $contentBlock): void - { - if (in_array($identifier, $result->uniqueTabIdentifiers, true)) { - throw new \InvalidArgumentException( - 'The tab identifier "' . $identifier . '" in Content Block "' . $contentBlock->getName() . '" does exist more than once. Please choose unique identifiers.', - 1679243686 - ); - } - } - - private function assertUniqueFieldIdentifier(ProcessedFieldsResult $result, LoadedContentBlock $contentBlock): void - { - if (in_array($result->identifier, $result->uniqueFieldIdentifiers, true)) { - throw new \InvalidArgumentException( - 'The identifier "' . $result->identifier . '" in Content Block "' . $contentBlock->getName() . '" does exist more than once. Please choose unique identifiers.', - 1677407942 - ); - } - } - - private function validateFlexFormHasOnlySheetsOrNoSheet(array $field, LoadedContentBlock $contentBlock): void - { - foreach ($field['fields'] ?? [] as $flexField) { - $flexFormType = FlexFormSubType::tryFrom($flexField['type']); - if ($flexFormType !== FlexFormSubType::SHEET) { - $flexFormType = 'nonSheet'; - } - $currentType ??= $flexFormType; - $isValid = $currentType === $flexFormType; - if (!$isValid) { - throw new \InvalidArgumentException( - 'You must not mix Sheets with normal fields inside the FlexForm definition "' . $field['identifier'] . '" in Content Block "' . $contentBlock->getName() . '".', - 1685217163 - ); - } - $currentType = $flexFormType; - } - } - - private function validateFlexFormContainsValidFieldTypes(array $field, LoadedContentBlock $contentBlock): void - { - foreach ($field['fields'] ?? [] as $flexField) { - if (FlexFormSubType::tryFrom($flexField['type']) === FlexFormSubType::SHEET) { - $this->validateFlexFormContainsValidFieldTypes($flexField, $contentBlock); - continue; - } - if (FlexFormSubType::tryFrom($flexField['type']) === FlexFormSubType::SECTION) { - if (empty($flexField['container'])) { - throw new \InvalidArgumentException( - 'FlexForm field "' . $field['identifier'] . '" has a Section "' . $flexField['identifier'] . '" without "container" defined. This is invalid, please add at least one item to "container" in Content Block "' . $contentBlock->getName() . '".', - 1686330220 - ); - } - foreach ($flexField['container'] as $container) { - if (empty($container['fields'])) { - throw new \InvalidArgumentException( - 'FlexForm field "' . $field['identifier'] . '" has a Container in Section "' . $flexField['identifier'] . '" without "fields" defined. This is invalid, please add at least one field to "fields" in Content Block "' . $contentBlock->getName() . '".', - 1686331469 - ); - } - foreach ($container['fields'] as $containerField) { - $containerType = FieldType::from($containerField['type']); - if (!FieldType::isValidFlexFormField($containerType)) { - throw new \InvalidArgumentException( - 'FlexForm field "' . $field['identifier'] . '" has an invalid field of type "' . $containerType->value . '" inside of a "container" item. Please use valid field types in Content Block "' . $contentBlock->getName() . '".', - 1686330594 - ); - } - } - } - continue; - } - $type = FieldType::from($flexField['type']); - if (!FieldType::isValidFlexFormField($type)) { - throw new \InvalidArgumentException( - 'Field type "' . $type->value . '" with identifier "' . $flexField['identifier'] . '" is not allowed inside FlexForm in Content Block "' . $contentBlock->getName() . '".', - 1685220309 - ); - } - } - } } diff --git a/Classes/Generator/SqlGenerator.php b/Classes/Generator/SqlGenerator.php index 7aa02bce..01e29f1d 100644 --- a/Classes/Generator/SqlGenerator.php +++ b/Classes/Generator/SqlGenerator.php @@ -28,7 +28,8 @@ class SqlGenerator { public function __construct( - protected readonly ContentBlockLoader $contentBlockLoader + protected readonly ContentBlockLoader $contentBlockLoader, + protected readonly TableDefinitionCollectionFactory $tableDefinitionCollectionFactory, ) {} public function __invoke(AlterTableDefinitionStatementsEvent $event): void @@ -41,8 +42,7 @@ public function __invoke(AlterTableDefinitionStatementsEvent $event): void public function generate(): array { $contentBlockRegistry = $this->contentBlockLoader->loadUncached(); - $tableDefinitionFactory = new TableDefinitionCollectionFactory($contentBlockRegistry); - $tableDefinitionCollection = $tableDefinitionFactory->create(); + $tableDefinitionCollection = $this->tableDefinitionCollectionFactory->createUncached($contentBlockRegistry); $sql = []; foreach ($tableDefinitionCollection as $tableDefinition) { foreach ($tableDefinition->getSqlColumnDefinitionCollection() as $column) { diff --git a/Classes/ServiceProvider.php b/Classes/ServiceProvider.php index 04f04375..1ea767c4 100644 --- a/Classes/ServiceProvider.php +++ b/Classes/ServiceProvider.php @@ -22,7 +22,7 @@ use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentElementDefinition; use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentType; use TYPO3\CMS\ContentBlocks\Definition\ContentType\PageTypeDefinition; -use TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory; +use TYPO3\CMS\ContentBlocks\Definition\TableDefinitionCollection; use TYPO3\CMS\ContentBlocks\Generator\PageTsConfigGenerator; use TYPO3\CMS\ContentBlocks\Generator\TypoScriptGenerator; use TYPO3\CMS\ContentBlocks\Generator\UserTsConfigGenerator; @@ -142,8 +142,7 @@ public static function getContentWhere(ContainerInterface $container): ContentWh public static function getContentBlockIcons(ContainerInterface $container): \ArrayObject { $arrayObject = new \ArrayObject(); - $tableDefinitionCollectionFactory = $container->get(TableDefinitionCollectionFactory::class); - $tableDefinitionCollection = $tableDefinitionCollectionFactory->create(); + $tableDefinitionCollection = $container->get(TableDefinitionCollection::class); foreach ($tableDefinitionCollection as $tableDefinition) { foreach ($tableDefinition->getContentTypeDefinitionCollection() ?? [] as $typeDefinition) { $iconConfig = [ @@ -161,8 +160,7 @@ public static function getContentBlockIcons(ContainerInterface $container): \Arr public static function getContentBlockPageTypes(ContainerInterface $container): \ArrayObject { $arrayObject = new \ArrayObject(); - $tableDefinitionCollectionFactory = $container->get(TableDefinitionCollectionFactory::class); - $tableDefinitionCollection = $tableDefinitionCollectionFactory->create(); + $tableDefinitionCollection = $container->get(TableDefinitionCollection::class); if (!$tableDefinitionCollection->hasTable(ContentType::PAGE_TYPE->getTable())) { return $arrayObject; } @@ -183,9 +181,8 @@ public static function getContentBlockTypoScript(ContainerInterface $container): return $arrayObject; } - $tableDefinitionCollectionFactory = $container->get(TableDefinitionCollectionFactory::class); $contentBlockRegistry = $container->get(ContentBlockRegistry::class); - $tableDefinitionCollection = $tableDefinitionCollectionFactory->create(); + $tableDefinitionCollection = $container->get(TableDefinitionCollection::class); foreach ($tableDefinitionCollection as $tableDefinition) { foreach ($tableDefinition->getContentTypeDefinitionCollection() ?? [] as $typeDefinition) { if ($tableDefinition->getContentType() === ContentType::CONTENT_ELEMENT) { @@ -225,8 +222,7 @@ public static function getContentBlockUserTsConfig(ContainerInterface $container return $arrayObject; } - $tableDefinitionCollectionFactory = $container->get(TableDefinitionCollectionFactory::class); - $tableDefinitionCollection = $tableDefinitionCollectionFactory->create(); + $tableDefinitionCollection = $container->get(TableDefinitionCollection::class); foreach ($tableDefinitionCollection as $tableDefinition) { foreach ($tableDefinition->getContentTypeDefinitionCollection() ?? [] as $typeDefinition) { if ($typeDefinition instanceof PageTypeDefinition) { @@ -251,8 +247,7 @@ public static function getContentBlockPageTsConfig(ContainerInterface $container } $languageFileRegistry = $container->get(LanguageFileRegistry::class); - $tableDefinitionCollectionFactory = $container->get(TableDefinitionCollectionFactory::class); - $tableDefinitionCollection = $tableDefinitionCollectionFactory->create(); + $tableDefinitionCollection = $container->get(TableDefinitionCollection::class); foreach ($tableDefinitionCollection as $tableDefinition) { foreach ($tableDefinition->getContentTypeDefinitionCollection() ?? [] as $typeDefinition) { if ($typeDefinition instanceof ContentElementDefinition) { @@ -307,9 +302,7 @@ public static function getContentBlockParentFieldNames(ContainerInterface $conta return $arrayObject; } - $tableDefinitionCollectionFactory = $container->get(TableDefinitionCollectionFactory::class); - $tableDefinitionCollection = $tableDefinitionCollectionFactory->create(); - + $tableDefinitionCollection = $container->get(TableDefinitionCollection::class); $contentElementTable = ContentType::CONTENT_ELEMENT->getTable(); if ($tableDefinitionCollection->hasTable($contentElementTable)) { $fieldNames = []; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index fae3263e..1cd0e6c1 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -11,6 +11,12 @@ services: resource: '../Classes/UserFunction/*' public: true + TYPO3\CMS\ContentBlocks\Definition\Factory\InitializeContentBlockCache: + tags: + - name: event.listener + identifier: 'content-blocks-initialize-cache' + event: TYPO3\CMS\Core\Core\Event\BootCompletedEvent + # @todo change to BeforeTcaOverridesEvent for v13 TYPO3\CMS\ContentBlocks\Generator\TcaGenerator: public: true @@ -25,6 +31,7 @@ services: - name: event.listener identifier: 'content-blocks-typoscript' event: TYPO3\CMS\Core\Core\Event\BootCompletedEvent + after: 'content-blocks-initialize-cache' # @todo Use BeforeLoadedPageTsConfigEvent in v13 TYPO3\CMS\ContentBlocks\Generator\PageTsConfigGenerator: @@ -41,6 +48,7 @@ services: - name: event.listener identifier: 'content-blocks-user-tsconfig' event: TYPO3\CMS\Core\Core\Event\BootCompletedEvent + after: 'content-blocks-initialize-cache' TYPO3\CMS\ContentBlocks\Generator\SqlGenerator: public: true @@ -77,10 +85,22 @@ services: arguments: $cache: '@cache.core' + cache.content_blocks: + class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface + factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache'] + arguments: ['content_blocks'] + lazy: true + + TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory: + arguments: + $cache: '@cache.content_blocks' + TYPO3\CMS\ContentBlocks\Definition\TableDefinitionCollection: + public: true factory: - '@TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory' - 'create' + arguments: ['@TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry'] TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry: public: true diff --git a/Tests/Functional/DataProcessing/RelationResolverTest.php b/Tests/Functional/DataProcessing/RelationResolverTest.php index 8a150913..4f47d8a9 100644 --- a/Tests/Functional/DataProcessing/RelationResolverTest.php +++ b/Tests/Functional/DataProcessing/RelationResolverTest.php @@ -19,6 +19,7 @@ use TYPO3\CMS\ContentBlocks\DataProcessing\RelationResolver; use TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory; +use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\WorkspaceAspect; use TYPO3\CMS\Core\Resource\File; @@ -50,7 +51,8 @@ public function canResolveFileReferences(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/file_reference.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('image'); @@ -73,7 +75,8 @@ public function canResolveFilesFromFolder(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/folder_files.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_folder'); @@ -96,7 +99,8 @@ public function canResolveFilesFromFolderRecursive(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/folder_files.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_folder_recursive'); @@ -120,7 +124,8 @@ public function canResolveCollections(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/collections.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_collection'); @@ -144,7 +149,8 @@ public function canResolveCollectionsWithAlternativeTableName(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/collections_alternative_table_name.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_collection2'); @@ -168,7 +174,8 @@ public function canResolveCollectionsExternal(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/collections_external.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_collection_external'); @@ -192,7 +199,8 @@ public function canResolveCollectionsRecursively(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/collections_recursive.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_collection_recursive'); @@ -220,7 +228,8 @@ public function canResolveCollectionsInWorkspaces(): void $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/collections.csv'); $this->importCSVDataSet(__DIR__ . '/../../Fixtures/be_users.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_collection'); @@ -249,7 +258,8 @@ public function canResolveCategoriesManyToMany(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/category_many_to_many.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_categories_mm'); @@ -274,7 +284,8 @@ public function canResolveCategoriesManyToManyInWorkspaces(): void $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/category_many_to_many.csv'); $this->importCSVDataSet(__DIR__ . '/../../Fixtures/be_users.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_categories_mm'); @@ -303,7 +314,8 @@ public function canResolveCategoriesOneToOne(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/category_one_to_one.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_categories_11'); @@ -326,7 +338,8 @@ public function canResolveCategoriesOneToMany(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/category_one_to_many.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_categories_1m'); @@ -350,7 +363,8 @@ public function canResolveDbRelation(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/db_relation.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_pages_relation'); @@ -374,7 +388,8 @@ public function canResolveDbRelationRecursive(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/db_relation_recursive.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_record_relation_recursive'); @@ -402,7 +417,8 @@ public function canResolveDbRelationsInWorkspaces(): void $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/db_relation.csv'); $this->importCSVDataSet(__DIR__ . '/../../Fixtures/be_users.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_pages_relation'); @@ -431,7 +447,8 @@ public function canResolveMultipleDbRelations(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/db_relation_multiple.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_pages_content_relation'); @@ -457,7 +474,8 @@ public function canResolveDbRelationsMM(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/db_relation_mm.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_pages_mm'); @@ -479,7 +497,8 @@ public function canResolveDbRelationsMM(): void */ public function selectCheckboxCommaListConvertedToArray(): void { - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_select_checkbox'); @@ -499,7 +518,8 @@ public function selectCheckboxCommaListConvertedToArray(): void */ public function selectSingleBoxCommaListConvertedToArray(): void { - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_select_single_box'); @@ -519,7 +539,8 @@ public function selectSingleBoxCommaListConvertedToArray(): void */ public function selectMultipleSideBySideCommaListConvertedToArray(): void { - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_select_multiple'); @@ -540,7 +561,8 @@ public function selectMultipleSideBySideCommaListConvertedToArray(): void public function canResolveSelectForeignTableSingle(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/select_foreign.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_select_foreign'); @@ -561,7 +583,8 @@ public function canResolveSelectForeignTableSingle(): void public function canResolveSelectForeignTableMultiple(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/select_foreign.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_select_foreign_multiple'); @@ -584,7 +607,8 @@ public function canResolveSelectForeignTableMultiple(): void public function canResolveSelectForeignTableRecursive(): void { $this->importCSVDataSet(__DIR__ . '/Fixtures/DataSet/select_foreign_recursive.csv'); - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_select_foreign'); @@ -606,7 +630,8 @@ public function canResolveSelectForeignTableRecursive(): void */ public function canResolveFlexForm(): void { - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_flexfield'); @@ -641,7 +666,8 @@ public function canResolveFlexForm(): void */ public function canResolveFlexFormWithSheetsOtherThanDefault(): void { - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_flexfield'); @@ -688,7 +714,8 @@ public function canResolveFlexFormWithSheetsOtherThanDefault(): void */ public function canResolveJson(): void { - $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create(); + $contentBlockRegistry = $this->get(ContentBlockRegistry::class); + $tableDefinitionCollection = $this->get(TableDefinitionCollectionFactory::class)->create($contentBlockRegistry); $tableDefinition = $tableDefinitionCollection->getTable('tt_content'); $elementDefinition = $tableDefinition->getContentTypeDefinitionCollection()->getType('typo3tests_contentelementb'); $fieldDefinition = $tableDefinition->getTcaFieldDefinitionCollection()->getField('typo3tests_contentelementb_json'); diff --git a/Tests/Unit/Definition/Factory/DisplayCondPrefixEvaluationTest.php b/Tests/Unit/Definition/Factory/DisplayCondPrefixEvaluationTest.php index 397dc4e4..11411743 100644 --- a/Tests/Unit/Definition/Factory/DisplayCondPrefixEvaluationTest.php +++ b/Tests/Unit/Definition/Factory/DisplayCondPrefixEvaluationTest.php @@ -17,10 +17,12 @@ namespace TYPO3\CMS\ContentBlocks\Tests\Unit\Definition\Factory; +use TYPO3\CMS\ContentBlocks\Definition\Factory\ContentBlockCompiler; use TYPO3\CMS\ContentBlocks\Definition\Factory\PrefixType; use TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory; use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock; use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; final class DisplayCondPrefixEvaluationTest extends UnitTestCase @@ -54,7 +56,9 @@ public function displayCondIsPrefixedForStringSyntax(): void $contentBlockRegistry = new ContentBlockRegistry(); $contentBlockRegistry->register($contentBlock); - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $tcaFieldDefinition = $tableDefinitionCollection ->getTable('tt_content') ->getTcaFieldDefinitionCollection() @@ -161,7 +165,9 @@ public function displayCondIsPrefixedForArraySyntax(array $displayCond, array $e $contentBlockRegistry = new ContentBlockRegistry(); $contentBlockRegistry->register($contentBlock); - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $tcaFieldDefinition = $tableDefinitionCollection ->getTable('tt_content') ->getTcaFieldDefinitionCollection() diff --git a/Tests/Unit/Definition/Factory/TableDefinitionCollectionFactoryTest.php b/Tests/Unit/Definition/Factory/TableDefinitionCollectionFactoryTest.php index 397127c3..5d429b5a 100644 --- a/Tests/Unit/Definition/Factory/TableDefinitionCollectionFactoryTest.php +++ b/Tests/Unit/Definition/Factory/TableDefinitionCollectionFactoryTest.php @@ -17,9 +17,11 @@ namespace TYPO3\CMS\ContentBlocks\Tests\Unit\Definition\Factory; +use TYPO3\CMS\ContentBlocks\Definition\Factory\ContentBlockCompiler; use TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory; use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock; use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; final class TableDefinitionCollectionFactoryTest extends UnitTestCase @@ -70,7 +72,9 @@ public function notUniqueIdentifiersThrowAnException(array $contentBlocks): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } public static function notUniqueIdentifiersWithinCollectionThrowAnExceptionDataProvider(): iterable @@ -125,7 +129,9 @@ public function notUniqueIdentifiersWithinCollectionThrowAnException(array $cont foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -167,7 +173,9 @@ public function paletteInsidePaletteIsNotAllowed(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -215,7 +223,9 @@ public function paletteInsidePaletteInsideCollectionIsNotAllowed(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -274,7 +284,9 @@ public function paletteWithSameIdentifierIsNotAllowed(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -339,7 +351,9 @@ public function paletteWithSameIdentifierInsideCollectionIsNotAllowed(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -382,7 +396,9 @@ public function tabWithSameIdentifierIsNotAllowed(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -431,7 +447,9 @@ public function tabWithSameIdentifierInsideCollectionIsNotAllowed(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -472,7 +490,9 @@ public function tabInsidePaletteIsNotAllowed(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -519,7 +539,9 @@ public function tabInsidePaletteInsideCollectionIsNotAllowed(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -570,7 +592,9 @@ public function linebreaksAreOnlyAllowedWithinPalettes(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -627,7 +651,9 @@ public function linebreaksAreOnlyAllowedWithinPalettesInsideCollections(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -665,7 +691,9 @@ public function identifierIsRequired(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -699,7 +727,9 @@ public function typeIsRequired(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -743,7 +773,9 @@ public function identifierIsRequiredInsideCollections(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } /** @@ -800,7 +832,9 @@ public function flexFieldIsNotAllowedToMixNonSheetAndSheet(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } public static function structuralFieldTypesAreNotAllowedInFlexFormDataProvider(): iterable @@ -912,7 +946,9 @@ public function structuralFieldTypesAreNotAllowedInFlexForm(array $contentBlocks foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } public static function sectionsHaveAtLeastOneContainerExceptionIsThrownDataProvider(): iterable @@ -966,7 +1002,9 @@ public function sectionsHaveAtLeastOneContainerExceptionIsThrown(array $contentB foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } public static function containerHaveAtLeastOneFieldExceptionIsThrownDataProvider(): iterable @@ -1026,7 +1064,9 @@ public function containerHaveAtLeastOneFieldExceptionIsThrown(array $contentBloc foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); } public static function containerContainsValidFieldTypeExceptionIsThrownDataProvider(): iterable @@ -1091,7 +1131,8 @@ public function containerContainsValidFieldTypeExceptionIsThrown(array $contentB foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - (new TableDefinitionCollectionFactory($contentBlockRegistry))->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler))->createUncached($contentBlockRegistry); } public static function localCollectionsCanHaveTableOverriddenDataProvider(): iterable @@ -1136,8 +1177,9 @@ public function localCollectionsCanHaveTableOverridden(array $contentBlocks, str foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); self::assertTrue($tableDefinitionCollection->hasTable($expectedTable)); } diff --git a/Tests/Unit/Definition/TableDefinitionCollectionTest.php b/Tests/Unit/Definition/TableDefinitionCollectionTest.php index 2ea307e1..35397959 100644 --- a/Tests/Unit/Definition/TableDefinitionCollectionTest.php +++ b/Tests/Unit/Definition/TableDefinitionCollectionTest.php @@ -17,9 +17,11 @@ namespace TYPO3\CMS\ContentBlocks\Tests\Unit\Definition; +use TYPO3\CMS\ContentBlocks\Definition\Factory\ContentBlockCompiler; use TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory; use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock; use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; final class TableDefinitionCollectionTest extends UnitTestCase @@ -58,8 +60,9 @@ public function contentElementDefinitionIsFoundByCType(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $contentElementDefinition = $tableDefinitionCollection->getContentElementDefinition('t3ce_example'); self::assertSame('t3ce_example', $contentElementDefinition->getTypeName()); @@ -71,8 +74,9 @@ public function contentElementDefinitionIsFoundByCType(): void public function nonExistingTableThrowsException(): void { $contentBlockRegistry = new ContentBlockRegistry(); - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionCode(1702413869); @@ -103,8 +107,9 @@ public function nonExistingContentElementThrowsException(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionCode(1702413909); @@ -163,8 +168,9 @@ public function saveAndCloseIsAdded(array $contentBlocks, string $typeName, bool foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $contentElement = $tableDefinitionCollection->getContentElementDefinition($typeName); @@ -218,8 +224,9 @@ public function contentBlocksCanBeSortedByPriority(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $typeDefinitionCollection = $tableDefinitionCollection->getTable('tt_content')->getContentTypeDefinitionCollection(); $result = []; foreach ($typeDefinitionCollection as $typeDefinition) { diff --git a/Tests/Unit/Generator/SqlGeneratorTest.php b/Tests/Unit/Generator/SqlGeneratorTest.php index 067fc707..c5223f75 100644 --- a/Tests/Unit/Generator/SqlGeneratorTest.php +++ b/Tests/Unit/Generator/SqlGeneratorTest.php @@ -17,10 +17,13 @@ namespace TYPO3\CMS\ContentBlocks\Tests\Unit\Generator; +use TYPO3\CMS\ContentBlocks\Definition\Factory\ContentBlockCompiler; +use TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory; use TYPO3\CMS\ContentBlocks\Generator\SqlGenerator; use TYPO3\CMS\ContentBlocks\Loader\ContentBlockLoader; use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock; use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; final class SqlGeneratorTest extends UnitTestCase @@ -276,7 +279,9 @@ public function generateReturnsExpectedSqlStatements(array $contentBlocks, array } $loader = $this->createMock(ContentBlockLoader::class); $loader->method('loadUncached')->willReturn($contentBlockRegistry); - $sqlGenerator = new SqlGenerator($loader); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollectionFactory = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)); + $sqlGenerator = new SqlGenerator($loader, $tableDefinitionCollectionFactory); $result = $sqlGenerator->generate(); diff --git a/Tests/Unit/Generator/TcaGeneratorTest.php b/Tests/Unit/Generator/TcaGeneratorTest.php index 1d5682a5..402dc086 100644 --- a/Tests/Unit/Generator/TcaGeneratorTest.php +++ b/Tests/Unit/Generator/TcaGeneratorTest.php @@ -18,6 +18,7 @@ namespace TYPO3\CMS\ContentBlocks\Tests\Unit\Generator; use TYPO3\CMS\ContentBlocks\Backend\Preview\PreviewRenderer; +use TYPO3\CMS\ContentBlocks\Definition\Factory\ContentBlockCompiler; use TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory; use TYPO3\CMS\ContentBlocks\Generator\FlexFormGenerator; use TYPO3\CMS\ContentBlocks\Generator\TcaGenerator; @@ -25,6 +26,7 @@ use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry; use TYPO3\CMS\ContentBlocks\Tests\Unit\Fixtures\NoopLanguageFileRegistry; use TYPO3\CMS\ContentBlocks\Tests\Unit\Fixtures\TestSystemExtensionAvailability; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher; use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider; use TYPO3\CMS\Core\Preparations\TcaPreparation; @@ -2094,8 +2096,9 @@ public function checkTcaFieldTypes(array $contentBlocks, array $expected): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register($contentBlock); } - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $systemExtensionAvailability = new TestSystemExtensionAvailability(); $systemExtensionAvailability->addAvailableExtension('workspaces'); $languageFileRegistry = new NoopLanguageFileRegistry(); @@ -2262,8 +2265,9 @@ public function pageTypesGenerateCorrectTca(array $contentBlocks, bool $seoExten foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register($contentBlock); } - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $systemExtensionAvailability = new TestSystemExtensionAvailability(); $systemExtensionAvailability->addAvailableExtension('workspaces'); if ($seoExtensionLoaded) { @@ -2302,8 +2306,9 @@ public function missingLabelFieldThrowsException(): void $contentBlock = LoadedContentBlock::fromArray($yaml); $contentBlockRegistry = new ContentBlockRegistry(); $contentBlockRegistry->register($contentBlock); - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $systemExtensionAvailability = new TestSystemExtensionAvailability(); $systemExtensionAvailability->addAvailableExtension('workspaces'); $languageFileRegistry = new NoopLanguageFileRegistry(); @@ -2944,8 +2949,9 @@ public function checkFlexFormTca(array $contentBlocks, array $expected): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $systemExtensionAvailability = new TestSystemExtensionAvailability(); $systemExtensionAvailability->addAvailableExtension('workspaces'); $languageFileRegistry = new NoopLanguageFileRegistry(); @@ -2996,8 +3002,9 @@ public function displayCondIsPrefixedForStringSyntax(): void $contentBlockRegistry = new ContentBlockRegistry(); $contentBlockRegistry->register($contentBlock); - $tableDefinitionCollection = (new TableDefinitionCollectionFactory($contentBlockRegistry)) - ->create(); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $systemExtensionAvailability = new TestSystemExtensionAvailability(); $systemExtensionAvailability->addAvailableExtension('workspaces'); $languageFileRegistry = new NoopLanguageFileRegistry(); diff --git a/Tests/Unit/Service/ContentElementParentFieldServiceTest.php b/Tests/Unit/Service/ContentElementParentFieldServiceTest.php index b6d98948..a3196e92 100644 --- a/Tests/Unit/Service/ContentElementParentFieldServiceTest.php +++ b/Tests/Unit/Service/ContentElementParentFieldServiceTest.php @@ -18,7 +18,9 @@ namespace TYPO3\CMS\ContentBlocks\Tests\Unit\Service; use Symfony\Component\DependencyInjection\Container; +use TYPO3\CMS\ContentBlocks\Definition\Factory\ContentBlockCompiler; use TYPO3\CMS\ContentBlocks\Definition\Factory\TableDefinitionCollectionFactory; +use TYPO3\CMS\ContentBlocks\Definition\TableDefinitionCollection; use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock; use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry; use TYPO3\CMS\ContentBlocks\ServiceProvider; @@ -96,9 +98,11 @@ public function uniqueForeignFieldAreCollectedForTtContent(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - $tableDefinitionFactory = new TableDefinitionCollectionFactory($contentBlockRegistry); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $container = new Container(); - $container->set(TableDefinitionCollectionFactory::class, $tableDefinitionFactory); + $container->set(TableDefinitionCollection::class, $tableDefinitionCollection); $container->set('cache.core', new NullFrontend('test')); $result = ServiceProvider::getContentBlockParentFieldNames($container); @@ -154,9 +158,11 @@ public function emptyResultIfNoContentElementDefinition(): void foreach ($contentBlocks as $contentBlock) { $contentBlockRegistry->register(LoadedContentBlock::fromArray($contentBlock)); } - $tableDefinitionFactory = new TableDefinitionCollectionFactory($contentBlockRegistry); + $contentBlockCompiler = new ContentBlockCompiler(); + $tableDefinitionCollection = (new TableDefinitionCollectionFactory(new NullFrontend('test'), $contentBlockCompiler)) + ->createUncached($contentBlockRegistry); $container = new Container(); - $container->set(TableDefinitionCollectionFactory::class, $tableDefinitionFactory); + $container->set(TableDefinitionCollection::class, $tableDefinitionCollection); $container->set('cache.core', new NullFrontend('test')); $result = ServiceProvider::getContentBlockParentFieldNames($container); diff --git a/ext_localconf.php b/ext_localconf.php index 19ad7f1b..d4dcc1e8 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -2,6 +2,8 @@ defined('TYPO3') or die(); +use TYPO3\CMS\Core\Cache\Backend\SimpleFileBackend; +use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; ExtensionManagementUtility::addTypoScriptSetup(' @@ -13,4 +15,13 @@ } '); +$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['content_blocks'] = [ + 'frontend' => VariableFrontend::class, + 'backend' => SimpleFileBackend::class, + 'options' => [ + 'defaultLifetime' => 0, + ], + 'groups' => ['system'], +]; + $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['cb'][] = 'TYPO3\\CMS\\ContentBlocks\\ViewHelpers';