Files
cms-core/Classes/Configuration/Tca/TcaPreparation.php
T

625 lines
30 KiB
PHP

<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Configuration\Tca;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;
/**
* Prepare TCA. Used in bootstrap and Flex Form Data Structures,
* executed *after* TcaMigration.
* This is used to add *internal TCA details* needed by core.
* For instance, type=category fields receive all the relation
* details in order to work properly.
*
* @internal Class and API may change any time.
*/
readonly class TcaPreparation
{
/**
* Prepare TCA
*
* This class is typically called within bootstrap with empty caches after all TCA
* files from extensions have been loaded. The preparation is then applied and
* the prepared result is cached.
* For flex form TCA, this class is called dynamically if opening a record in the backend.
*
* See unit tests for details.
*/
public function prepare(array $tca, bool $isFlexForm = false): array
{
$tca = $this->configureCategoryRelations($tca, $isFlexForm);
$tca = $this->configureFileReferences($tca, $isFlexForm);
$tca = $this->configureEmailSoftReferences($tca);
$tca = $this->configureLinkSoftReferences($tca);
$tca = $this->configureSelectSingle($tca);
$tca = $this->configureRelationshipToOne($tca);
$tca = $this->addSystemFieldsToShowitemTypes($tca);
$tca = $this->addIgnoredPageTypeRestrictionRecords($tca);
return $tca;
}
/**
* Prepares TCA configuration of type='category' fields.
*
* It adds some TCA config settings so category fields end up with similar
* config as type='select' field, but in a more restricted way.
* Some settings could also be set in TCA directly, but some fields
* can not be overridden, e.g. foreign_table.
*
* This also sets necessary MM properties, in case relationship is
* set to "manyToMany", which is the default. Note it is "oneToMany"
* with flex forms, since flex forms do NOT support "manyToMany".
*
* Finally, all category fields with a "manyToMany" relationship are
* added to the MM_oppositeUsage of sys_category "items".
*
* Important: Since this method defines a "foreign_table_where", this
* must always be executed before prepareQuotingOfTableNamesAndColumnNames().
*/
protected function configureCategoryRelations(array $tca, bool $isFlexForm): array
{
foreach ($tca as $table => &$tableDefinition) {
if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
continue;
}
foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) {
if (($fieldConfig['config']['type'] ?? '') !== 'category') {
continue;
}
if (!isset($fieldConfig['label'])) {
$fieldConfig['label'] = 'LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.categories';
}
// Force foreign_table for type category
$fieldConfig['config']['foreign_table'] = 'sys_category';
// Initialize default column configuration and merge it with already defined
$fieldConfig['config']['size'] ??= 20;
$defaultTag = \Local\Multilanguage\Service\DefaultLanguageTagService::getTag();
$fieldConfig['config']['foreign_table_where'] ??= " AND {#sys_category}.{#language_tag} IN ('" . $defaultTag . "', '')";
if (empty($fieldConfig['config']['relationship'])) {
// In case no relationship is given, set "manyToMany" for non flex form, but "oneToMany" with flex form.
$fieldConfig['config']['relationship'] = $isFlexForm ? 'oneToMany' : 'manyToMany';
}
// Sanitize 'relationship'
if ($isFlexForm && !in_array($fieldConfig['config']['relationship'], ['oneToOne', 'oneToMany'], true)) {
throw new \UnexpectedValueException(
'"relationship" must be one of "oneToOne" or "oneToMany", "manyToMany" is not supported as "relationship"'
. ' for field ' . $fieldName . ' of type "category" in flexform.',
1627640208
);
}
if (!in_array($fieldConfig['config']['relationship'], ['oneToOne', 'oneToMany', 'manyToMany'], true)) {
throw new \RuntimeException(
$fieldName . ' of table ' . $table . ' is defined as type category with relationship "'
. $fieldConfig['config']['relationship'] . '", but only "oneToOne", "oneToMany" and "manyToMany"'
. ' are allowed.',
1627898896
);
}
// Set the maxitems value (necessary for DataHandling and FormEngine)
if ($fieldConfig['config']['relationship'] === 'oneToOne') {
// In case relationship is set to "oneToOne", the database column for this
// field will be an integer column. This means, only one uid can be stored.
// Therefore, maxitems must be 1. Sanitize for flex form fields as well.
if ((int)($fieldConfig['config']['maxitems'] ?? 0) > 1) {
throw new \RuntimeException(
$fieldName . ' of table ' . $table . ' is defined as type category with an oneToOne relationship. '
. 'Therefore maxitems must be 1. Otherwise, use oneToMany or manyToMany as relationship instead.',
1627335016
);
}
$fieldConfig['config']['maxitems'] = 1;
} elseif (!($fieldConfig['config']['maxitems'] ?? false)) {
// In case maxitems is not set or set to 0, set the default value "99999"
$fieldConfig['config']['maxitems'] = 99999;
} elseif ($fieldConfig['config']['relationship'] === 'oneToMany'
&& (int)($fieldConfig['config']['maxitems'] ?? 0) === 1
) {
throw new \RuntimeException(
$fieldName . ' of table ' . $table . ' is defined as type category with a ' . $fieldConfig['config']['relationship']
. ' relationship. Therefore, maxitems can not be set to 1. Use oneToOne as relationship instead.',
1627335017
);
}
// Add the default value if not set
if (!isset($fieldConfig['config']['default'])
&& $fieldConfig['config']['relationship'] !== 'oneToMany'
) {
// @todo: This is db wise not accurate: A oneToOne relation without a relation being assigned,
// @todo: should have NULL as value, not 0, since 0 looks like a uid, but it isn't.
// @todo: The field should be nullable and DH should handle that.
$fieldConfig['config']['default'] = 0;
}
// Add MM related properties in case relationship is set to "manyToMany".
// This will not be done for the sys_category table itself. Not relevant with flex forms.
if ($fieldConfig['config']['relationship'] === 'manyToMany' && $table !== 'sys_category') {
// Note these settings are hard coded here and can't be overridden.
$fieldConfig['config'] = array_replace_recursive($fieldConfig['config'], [
'MM' => 'sys_category_record_mm',
'MM_opposite_field' => 'items',
'MM_match_fields' => [
'tablenames' => $table,
'fieldname' => $fieldName,
],
]);
// Register opposite references for the foreign side of a category relation
if (empty($tca['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$table])) {
$tca['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$table] = [];
}
if (!in_array($fieldName, $tca['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$table], true)) {
$tca['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$table][] = $fieldName;
}
// Take specific value of exclude flag into account
if (!isset($fieldConfig['exclude'])) {
$fieldConfig['exclude'] = true;
}
}
}
}
return $tca;
}
protected function configureFileReferences(array $tca, bool $isFlexForm): array
{
foreach ($tca as $table => &$tableDefinition) {
if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
continue;
}
foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) {
if (($fieldConfig['config']['type'] ?? '') === 'file') {
// Set static values for this type. Most of them are not needed due to the
// dedicated TCA type. However a lot of underlying code in DataHandler and
// friends relies on those keys, especially "foreign_table" and "foreign_selector".
// @todo Check which of those values can be removed since only used by FormEngine
$fieldConfig['config'] = array_replace_recursive($fieldConfig['config'], [
'foreign_table' => 'sys_file_reference',
'foreign_field' => 'uid_foreign',
'foreign_sortby' => 'sorting_foreign',
'foreign_table_field' => 'tablenames',
'foreign_label' => 'uid_local',
'foreign_selector' => 'uid_local',
]);
if (!isset($fieldConfig['config']['foreign_match_fields']['fieldname'])) {
$fieldConfig['config']['foreign_match_fields']['fieldname'] = $fieldName;
}
if (!$isFlexForm) {
$fieldConfig['config']['foreign_match_fields']['tablenames'] = $table;
}
$fieldConfig['config'] = $this->configureAllowedDisallowedFileExtensions($fieldConfig['config']);
}
if (is_array($fieldConfig['config']['overrideChildTca'] ?? null)) {
$fieldConfig['config']['overrideChildTca'] = $this->configureAllowedDisallowedInOverrideChildTca($fieldConfig['config']['overrideChildTca']);
}
}
unset($fieldConfig);
if (is_array($tableDefinition['types'] ?? null)) {
foreach ($tableDefinition['types'] as &$typeConfig) {
if (!isset($typeConfig['columnsOverrides']) || !is_array($typeConfig['columnsOverrides'])) {
continue;
}
foreach ($typeConfig['columnsOverrides'] as &$columnsOverridesConfig) {
if (!isset($columnsOverridesConfig['config']) || !is_array($columnsOverridesConfig['config'])) {
continue;
}
$columnsOverridesConfig['config'] = $this->configureAllowedDisallowedFileExtensions($columnsOverridesConfig['config']);
if (is_array($columnsOverridesConfig['config']['overrideChildTca'] ?? null)) {
$columnsOverridesConfig['config']['overrideChildTca'] = $this->configureAllowedDisallowedInOverrideChildTca($columnsOverridesConfig['config']['overrideChildTca']);
}
}
}
}
}
return $tca;
}
/**
* configureFileReferences() helper
*/
protected function configureAllowedDisallowedInOverrideChildTca(array $overrideChildTcaConfig): array
{
if (is_array($overrideChildTcaConfig['columns'] ?? null)) {
foreach ($overrideChildTcaConfig['columns'] as &$overrideChildTcaColumnConfig) {
if (!isset($overrideChildTcaColumnConfig['config'])) {
continue;
}
$overrideChildTcaColumnConfig['config'] = $this->configureAllowedDisallowedFileExtensions($overrideChildTcaColumnConfig['config']);
}
unset($overrideChildTcaColumnConfig);
}
if (is_array($overrideChildTcaConfig['types'] ?? null)) {
foreach ($overrideChildTcaConfig['types'] as &$overrideChildTcaTypeConfig) {
if (!isset($overrideChildTcaTypeConfig['config'])) {
continue;
}
$overrideChildTcaTypeConfig['config'] = $this->configureAllowedDisallowedFileExtensions($overrideChildTcaTypeConfig['config']);
}
}
return $overrideChildTcaConfig;
}
/**
* configureFileReferences() helper
*/
protected function configureAllowedDisallowedFileExtensions(array $config): array
{
if (!empty($allowed = ($config['allowed'] ?? null))) {
$config['allowed'] = $this->prepareFileExtensions($allowed);
}
if (!empty($disallowed = ($config['disallowed'] ?? null))) {
$config['disallowed'] = $this->prepareFileExtensions($disallowed);
}
return $config;
}
/**
* configureFileReferences() helper: Ensures format, replaces placeholders and remove duplicates
*/
protected function prepareFileExtensions(mixed $fileExtensions): string
{
if (is_array($fileExtensions)) {
$fileExtensions = implode(',', $fileExtensions);
} else {
$fileExtensions = (string)$fileExtensions;
}
// Replace placeholders with the corresponding $GLOBALS value for now
if (preg_match_all('/common-(image|text|media)-types/', $fileExtensions, $matches)) {
foreach ($matches[1] as $key => $type) {
$fileExtensions = str_replace(
$matches[0][$key],
$GLOBALS['TYPO3_CONF_VARS'][$type === 'image' ? 'GFX' : 'SYS'][$type . 'file_ext'] ?? '',
$fileExtensions
);
}
}
return StringUtility::uniqueList($fileExtensions);
}
/**
* Add "'softref' = 'email[subst]'" to all 'type' = 'email' column fields.
*/
protected function configureEmailSoftReferences(array $tca): array
{
foreach ($tca as &$tableDefinition) {
if (!is_array($tableDefinition['columns'] ?? null)) {
continue;
}
foreach ($tableDefinition['columns'] as &$fieldConfig) {
if (($fieldConfig['config']['type'] ?? null) === 'email') {
// Hard set/override: 'softref' is not listed as property for type=email at all,
// there is little need to have this configurable.
$fieldConfig['config']['softref'] = 'email[subst]';
}
}
}
return $tca;
}
/**
* Add "'softref' = 'typolink'" to all 'type' = 'link' column fields.
*/
protected function configureLinkSoftReferences(array $tca): array
{
foreach ($tca as &$tableDefinition) {
if (!is_array($tableDefinition['columns'] ?? null)) {
continue;
}
foreach ($tableDefinition['columns'] as &$fieldConfig) {
if (($fieldConfig['config']['type'] ?? null) === 'link') {
// Hard set/override: 'softref' is not listed as property for type=link at all,
// there is little need to have this configurable.
$fieldConfig['config']['softref'] = 'typolink';
}
}
}
return $tca;
}
/**
* Add "'relationship' for TCA type "select" fields, having "selectSingle" set as renderType and are
* pointing to a "foreign_table". Depending on further configuration, this will set the "relationship"
* to either "manyToMany" (in case "MM" is set) or to "manyToOne".
* Already defined "relationship" is not overwritten!
*
* This is mainly done to prevent checks on the renderType, which should be avoided.
*/
protected function configureSelectSingle(array $tca): array
{
foreach ($tca as &$tableDefinition) {
if (!is_array($tableDefinition['columns'] ?? null)) {
continue;
}
foreach ($tableDefinition['columns'] as &$fieldConfig) {
if (($fieldConfig['config']['type'] ?? null) !== 'select'
|| ($fieldConfig['config']['renderType'] ?? null) !== 'selectSingle'
|| !isset($fieldConfig['config']['foreign_table'])
|| isset($fieldConfig['config']['relationship'])
) {
continue;
}
if (isset($fieldConfig['config']['MM'])) {
$fieldConfig['config']['relationship'] = 'manyToMany';
} else {
$fieldConfig['config']['relationship'] = 'manyToOne';
}
}
}
return $tca;
}
/**
* Add "'maxitems' => 1" to all relation type column fields with 'relationship' set to 'oneToOne' or 'manyToOne'.
*/
protected function configureRelationshipToOne(array $tca): array
{
foreach ($tca as &$tableDefinition) {
if (!is_array($tableDefinition['columns'] ?? null)) {
continue;
}
foreach ($tableDefinition['columns'] as &$fieldConfig) {
$type = $fieldConfig['config']['type'] ?? null;
if (in_array($type, ['select', 'inline', 'group', 'folder', 'file'], true)
&& in_array($fieldConfig['config']['relationship'] ?? null, ['oneToOne', 'manyToOne'], true)
) {
// Hard set/override: 'maxitems' to 1, since relationship [x]ToOne - as the name suggests -
// only allows a single item to be selected.
$fieldConfig['config']['maxitems'] = 1;
}
}
}
return $tca;
}
/**
* Ensure that all system fields (CType, colPos, hidden etc.) are automatically added
* to the showitem list of all CTypes in tt_content. As custom CTypes might have added
* the fields, the respective fields also need to be removed first.
*/
protected function addSystemFieldsToShowitemTypes(array $tca): array
{
// @todo Only deal with this for tt_content in v13, as other parts might be too intrusive
// might change in v14
if (!isset($tca['tt_content'])) {
return $tca;
}
// Only proceed in case the record type field is defined
$typeField = (string)($tca['tt_content']['ctrl']['type'] ?? '');
if ($typeField === '') {
return $tca;
}
// Build list of values (fields and palettes) which should be removed
// from custom palettes, because they will be added automatically.
$listOfValuesToRemove = [
'--div--;core.form.tabs:general',
'--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general',
'--div--;core.form.tabs:language',
'--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language',
'--div--;core.form.tabs:access',
'--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access',
'--div--;core.form.tabs:notes',
'--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes',
'--palette--;;general',
'--palette--;;language',
'--palette--;;access',
'--palette--;;hidden',
'colPos',
];
$listOfValuesToRemove[] = $typeField;
if (($languageField = (string)($tca['tt_content']['ctrl']['languageField'] ?? '')) !== '') {
$listOfValuesToRemove[] = $languageField;
}
if (($transOrigPointerField = (string)($tca['tt_content']['ctrl']['transOrigPointerField'] ?? '')) !== '') {
$listOfValuesToRemove[] = $transOrigPointerField;
}
$enablecolumns = $tca['tt_content']['ctrl']['enablecolumns'] ?? [];
foreach ($enablecolumns as $fieldName) {
$listOfValuesToRemove[] = $fieldName;
}
if (($editlock = (string)($tca['tt_content']['ctrl']['editlock'] ?? '')) !== '') {
$listOfValuesToRemove[] = $editlock;
}
if (($descriptionColumn = (string)($tca['tt_content']['ctrl']['descriptionColumn'] ?? '')) !== '') {
$listOfValuesToRemove[] = $descriptionColumn;
}
// Remove any system field from custom palettes
foreach ($tca['tt_content']['palettes'] as $paletteName => &$paletteConfig) {
if (in_array($paletteName, ['general', 'language', 'access', 'hidden'], true)) {
continue;
}
$showItemSplitted = GeneralUtility::trimExplode(',', $paletteConfig['showitem'], true);
$paletteConfig['showitem'] = implode(',', array_diff($this->removeCustomFieldLabels($showItemSplitted, $listOfValuesToRemove), $listOfValuesToRemove));
}
unset($paletteConfig);
// Process the content types
foreach ($tca['tt_content']['types'] as $type => $typeInformation) {
// Remove any of the special fields from the content type's current showitem
$showItemSplitted = GeneralUtility::trimExplode(',', $typeInformation['showitem'] ?? '', true);
$showItemFiltered = array_diff($this->removeCustomFieldLabels($showItemSplitted, $listOfValuesToRemove), $listOfValuesToRemove);
// Extract all fields of the extended tab to add it at the end
[$showItemList, $extendedParts] = $this->extractExtendedParts($showItemFiltered);
// Add record type field (usually "CType") and colPos either using the "general" palette
// or manually, in case the palette does not exist or does not contain the fields.
$generalPaletteItems = $this->removeCustomFieldLabels(GeneralUtility::trimExplode(',', $tca['tt_content']['palettes']['general']['showitem'] ?? '', true), $listOfValuesToRemove);
if (in_array($typeField, $generalPaletteItems, true) && in_array('colPos', $generalPaletteItems, true)) {
$showItemParts = ['--palette--;;general'];
} else {
$showItemParts = [
$typeField,
'colPos',
];
}
// Because FormEngine will add the general tab automatically, we will not do this here
// However, if the first item in the $showItemList is actually a tab (--div--), we need to
// add if before the "first fields"
if (str_starts_with($showItemList[0] ?? '', '--div--')) {
array_unshift($showItemParts, $showItemList[0]);
unset($showItemList[0]);
}
$showItemParts = array_merge($showItemParts, $showItemList);
// Add language field either using the "language" palette or manually,
// in case the palette does not exist or does not contain the field.
if ($languageField !== '') {
$showItemParts[] = '--div--;core.form.tabs:language';
$languagePaletteItems = $this->removeCustomFieldLabels(GeneralUtility::trimExplode(',', $tca['tt_content']['palettes']['language']['showitem'] ?? '', true), $listOfValuesToRemove);
if (in_array($languageField, $languagePaletteItems, true)
&& ($transOrigPointerField === '' || in_array($transOrigPointerField, $languagePaletteItems, true))
) {
$showItemParts[] = '--palette--;;language';
} else {
$showItemParts[] = $languageField;
if ($transOrigPointerField) {
$showItemParts[] = $transOrigPointerField;
}
}
}
// Add enable fields either using the "hidden" amd "access" palettes or
// manually, in case the palettes do not exist or do not contain the fields.
if ($enablecolumns !== [] || $editlock !== '') {
$showItemParts[] = '--div--;core.form.tabs:access';
if (isset($enablecolumns['disabled'])) {
$hiddenPaletteParts = $this->removeCustomFieldLabels(GeneralUtility::trimExplode(',', $tca['tt_content']['palettes']['hidden']['showitem'] ?? '', true), $listOfValuesToRemove);
if (in_array($enablecolumns['disabled'], $hiddenPaletteParts, true)) {
$showItemParts[] = '--palette--;;hidden';
} else {
$showItemParts[] = $enablecolumns['disabled'];
}
}
if ((isset($enablecolumns['starttime']) || isset($enablecolumns['endtime']) || isset($enablecolumns['fe_group']) || $editlock)) {
$accessPaletteParts = $this->removeCustomFieldLabels(GeneralUtility::trimExplode(',', $tca['tt_content']['palettes']['access']['showitem'] ?? '', true), $listOfValuesToRemove);
if ((!isset($enablecolumns['starttime']) || in_array($enablecolumns['starttime'], $accessPaletteParts, true))
&& (!isset($enablecolumns['endtime']) || in_array($enablecolumns['endtime'], $accessPaletteParts, true))
&& (!isset($enablecolumns['fe_group']) || in_array($enablecolumns['fe_group'], $accessPaletteParts, true))
&& (!$editlock || in_array($editlock, $accessPaletteParts, true))
) {
$showItemParts[] = '--palette--;;access';
} else {
if (isset($enablecolumns['starttime'])) {
$showItemParts[] = $enablecolumns['starttime'];
}
if (isset($enablecolumns['endtime'])) {
$showItemParts[] = $enablecolumns['endtime'];
}
if (isset($enablecolumns['fe_group'])) {
$showItemParts[] = $enablecolumns['fe_group'];
}
if ($editlock) {
$showItemParts[] = $editlock;
}
}
}
}
// Add description column if defined
if ($descriptionColumn !== '') {
$showItemParts[] = '--div--;core.form.tabs:notes,' . $descriptionColumn;
}
// Add extended tab at the end - if it exists
$showItemParts = array_merge($showItemParts, $extendedParts);
// Merge parts together
$tca['tt_content']['types'][$type]['showitem'] = trim(implode(',', $showItemParts), ',');
}
return $tca;
}
private function extractExtendedParts(array $showItemFiltered): array
{
$extendedParts = [];
$addFields = false;
foreach ($showItemFiltered as $key => $part) {
if ($part === '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended' || $part === '--div--;core.form.tabs:extended') {
$extendedParts[] = $part;
$addFields = true;
unset($showItemFiltered[$key]);
} elseif ($addFields) {
if (str_starts_with($part, '--div--')) {
break;
}
$extendedParts[] = $part;
unset($showItemFiltered[$key]);
}
}
return [$showItemFiltered, $extendedParts];
}
private function removeCustomFieldLabels(array $showitemParts, array $fieldList): array
{
if ($fieldList === []) {
return $showitemParts;
}
foreach ($showitemParts as &$showItem) {
// Tabs keep their labels
if (str_starts_with($showItem, '--div--')) {
continue;
}
// Remove label of palette
if (str_starts_with($showItem, '--palette--')) {
$parts = GeneralUtility::trimExplode(';', $showItem, true, 3);
// Palette without label, continue.
if (count($parts) !== 3) {
continue;
}
$paletteName = '--palette--;;' . $parts[2];
if (in_array($paletteName, $fieldList, true)) {
$showItem = $paletteName;
}
continue;
}
// This must be a field.
$parts = GeneralUtility::trimExplode(';', $showItem, true, 2);
$fieldName = $parts[0];
// Just keep the first part => the fieldname in case field is defined in the $fieldList
if (in_array($fieldName, $fieldList, true)) {
$showItem = $fieldName;
}
}
return $showitemParts;
}
protected function addIgnoredPageTypeRestrictionRecords(array $tca): array
{
$allowedRecordTypes = [];
foreach ($tca as $table => $configuration) {
if ($configuration['ctrl']['security']['ignorePageTypeRestriction'] ?? false) {
$allowedRecordTypes[] = $table;
}
}
if ($allowedRecordTypes === []) {
return $tca;
}
$mergedAllowedRecords = array_merge(
$tca['pages']['ctrl']['defaultAllowedRecordTypes'] ?? [],
$allowedRecordTypes
);
$tca['pages']['ctrl']['defaultAllowedRecordTypes'] = array_unique($mergedAllowedRecords);
return $tca;
}
}