tcaSchemaFactory->all() as $tableName => $schema) { // Hide "admin only" tables if ($schema->hasCapability(TcaSchemaCapability::AccessAdminOnly)) { continue; } $icon = $this->iconFactory->mapRecordTypeToIconIdentifier($tableName, [], $this->tcaSchemaFactory->get($tableName)); $fieldDefinition['items'][] = ['label' => $schema->getTitle(), 'value' => $tableName, 'icon' => $icon]; } } public function populateAvailablePageTypes(array &$fieldDefinition): void { foreach ($this->pageDoktypeRegistry->getAllDoktypes() as $pageType) { if (!$pageType->getValue()) { continue; } $icon = $this->iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $pageType->getValue()], $this->tcaSchemaFactory->get('pages')); $fieldDefinition['items'][] = ['label' => $pageType->getLabel(), 'value' => $pageType->getValue(), 'icon' => $icon]; } } public function populateAvailableUserModules(array &$fieldDefinition): void { $modules = $this->moduleProvider->getUserModules(); if ($modules === []) { return; } $languageService = $this->getLanguageService(); foreach ($modules as $identifier => $module) { // Item configuration $label = $languageService->sL($module->getTitle()); $parentModule = $module->getParentModule(); while ($parentModule) { $label = $languageService->sL($parentModule->getTitle()) . ' > ' . $label; $parentModule = $parentModule->getParentModule(); } $help = null; if ($module->getDescription()) { $help = [ 'title' => $languageService->sL($module->getShortDescription()), 'description' => $languageService->sL($module->getDescription()), ]; } $fieldDefinition['items'][] = [ 'label' => $label, 'value' => $identifier, 'icon' => $module->getIconIdentifier(), 'description' => $help, ]; } } public function populateExcludeFields(array &$fieldDefinition): void { $languageService = $this->getLanguageService(); foreach ($this->getGroupedExcludeFields() as $excludeFieldGroup) { $table = $excludeFieldGroup['table'] ?? ''; $origin = $excludeFieldGroup['origin'] ?? ''; $schema = $this->tcaSchemaFactory->get($table); // If the field comes from a FlexForm, the syntax is more complex if ($origin === 'flexForm') { // The field comes from a plugins FlexForm // Add header if not yet set for plugin section $sectionHeader = $excludeFieldGroup['sectionHeader'] ?? ''; if (!isset($fieldDefinition['items'][$sectionHeader])) { // there is no icon handling for plugins - we take the icon from the table $icon = $this->iconFactory->mapRecordTypeToIconIdentifier($table, [], $this->tcaSchemaFactory->get($table)); $fieldDefinition['items'][$sectionHeader] = ['label' => $sectionHeader, 'value' => '--div--', 'icon' => $icon]; } } elseif (!isset($fieldDefinition['items'][$table])) { // Add header if not yet set for table $icon = $this->iconFactory->mapRecordTypeToIconIdentifier($table, [], $this->tcaSchemaFactory->get($table)); $fieldDefinition['items'][$table] = ['label' => $schema->getTitle(), 'value' => '--div--', 'icon' => $icon]; } $fullField = $excludeFieldGroup['fullField'] ?? ''; $fieldName = $excludeFieldGroup['fieldName'] ?? ''; $label = $origin === 'flexForm' ? ($excludeFieldGroup['fieldLabel'] ?? '') : $languageService->sL($schema->getField($fieldName)->getLabel()); // Item configuration: $fieldDefinition['items'][] = [ 'label' => rtrim($label, ':') . ' (' . $fieldName . ')', 'value' => $table . ':' . $fullField, 'icon' => 'empty-empty', ]; } } public function populateExplicitAuthValues(array &$fieldDefinition): void { // Traverse grouped field values: foreach ($this->getGroupedExplicitAuthFieldValues() as $groupKey => $tableFields) { if (empty($tableFields['items']) || !is_array($tableFields['items'])) { continue; } // Add header: $fieldDefinition['items'][] = [ 'label' => $tableFields['tableFieldLabel'] ?? '', 'value' => '--div--', ]; // Traverse options for this field: foreach ($tableFields['items'] as $itemValue => $itemContent) { $fieldDefinition['items'][] = [ 'label' => $itemContent, 'value' => $groupKey . ':' . preg_replace('/[:|,]/', '', (string)$itemValue), 'icon' => 'status-status-permission-granted', ]; } } } public function populateCustomPermissionOptions(array &$fieldDefinition): void { $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'] ?? []; if (!is_array($customOptions) || $customOptions === []) { return; } $languageService = $this->getLanguageService(); foreach ($customOptions as $customOptionsKey => $customOptionsValue) { if (empty($customOptionsValue['items']) || !is_array($customOptionsValue['items'])) { continue; } // Add header: $fieldDefinition['items'][] = [ 'label' => $languageService->sL($customOptionsValue['header'] ?? ''), 'value' => '--div--', ]; // Traverse items: foreach ($customOptionsValue['items'] as $itemKey => $itemConfig) { $icon = 'empty-empty'; $helpText = ''; if (!empty($itemConfig[1]) && $this->iconRegistry->isRegistered($itemConfig[1])) { // Use icon identifier when registered $icon = $itemConfig[1]; } if (!empty($itemConfig[2])) { $helpText = $languageService->sL($itemConfig[2]); } $fieldDefinition['items'][] = [ 'label' => $languageService->sL($itemConfig[0] ?? ''), 'value' => $customOptionsKey . ':' . preg_replace('/[:|,]/', '', (string)$itemKey), 'icon' => $icon, 'description' => $helpText, ]; } } } /** * Populates a list of category fields (with the defined relationships) for the given table */ public function populateAvailableCategoryFields(array &$fieldDefinition): void { $table = (string)($fieldDefinition['config']['itemsProcConfig']['table'] ?? ''); if ($table === '') { throw new \UnexpectedValueException('No table to search for category fields given.', 1627565458); } if (!$this->tcaSchemaFactory->has($table)) { throw new \RuntimeException('Given table ' . $table . ' does not define any valid schema to search for category fields.', 1627565459); } // Only category fields with the "manyToMany" relationship are allowed by default. // This can however be changed using the "allowedRelationships" itemsProcConfig. $allowedRelationships = $fieldDefinition['config']['itemsProcConfig']['allowedRelationships'] ?? false; if (!is_array($allowedRelationships) || $allowedRelationships === []) { $allowedRelationships = ['manyToMany']; } $schema = $this->tcaSchemaFactory->get($table); // Loop on all table columns to find category fields foreach ($schema->getFields() as $fieldName => $fieldConfig) { /** @var CategoryFieldType $fieldConfig */ if (!$fieldConfig->isType(TableColumnType::CATEGORY)) { continue; } if (!in_array($fieldConfig->getConfiguration()['relationship'] ?? '', $allowedRelationships, true)) { continue; } $fieldDefinition['items'][] = [ 'label' => $this->getLanguageService()->sL($fieldConfig->getLabel()), 'value' => $fieldName, ]; } } /** * Returns an array with the exclude fields as defined in TCA and FlexForms * Used for listing the exclude fields in be_groups forms. * * @return array Array of arrays with excludeFields (fieldName, table:fieldName) from TCA * and FlexForms (fieldName, table:extKey;sheetName;fieldName) */ protected function getGroupedExcludeFields(): array { $languageService = $this->getLanguageService(); $excludeFieldGroups = []; // Fetch translations for table names $tableToTranslation = []; // All TCA keys foreach ($this->tcaSchemaFactory->all() as $table => $schema) { $tableToTranslation[$table] = $schema->getTitle($languageService->sL(...)) ?: $table; } // Sort by translations asort($tableToTranslation); foreach ($tableToTranslation as $table => $translatedTable) { $excludeFieldGroup = []; $schema = $this->tcaSchemaFactory->get($table); // All field names configured and not restricted to admins $rootLevelCapability = $schema->getCapability(TcaSchemaCapability::RestrictionRootLevel); // Skip this table if it’s rootlevel-only and the rootlevel restriction applies // (unless ignoreRootLevelRestriction is enabled). if (!$rootLevelCapability->shallIgnoreRootLevelRestriction() && $rootLevelCapability->getRootLevelType() === RootLevelCapability::TYPE_ONLY_ON_ROOTLEVEL) { continue; } if ($schema->hasCapability(TcaSchemaCapability::AccessAdminOnly)) { continue; } foreach ($schema->getFields() as $fieldName => $fieldDefinition) { // Only show fields that can be excluded for editors, or are hidden for non-admins if ($fieldDefinition->supportsAccessControl() && $fieldDefinition->getDisplayConditions() !== 'HIDE_FOR_NON_ADMINS') { // Get human-readable names of fields $translatedField = $languageService->sL($fieldDefinition->getLabel()); // Add entry, key 'labels' needed for sorting $excludeFieldGroup[] = [ 'labels' => $translatedTable . ':' . $translatedField, 'sectionHeader' => $translatedTable, 'table' => $table, 'tableField' => $fieldName, 'fieldName' => $fieldName, 'fullField' => $fieldName, 'fieldLabel' => $translatedField, 'origin' => 'tca', ]; } } // All FlexForm fields $flexFormArray = $this->getRegisteredFlexForms((string)$table); foreach ($flexFormArray as $tableField => $flexForms) { $flexFieldLabel = ''; // Get all sheets foreach ($flexForms as $extIdent => $extConf) { if ($schema->hasSubSchema((string)$extIdent)) { $fieldDefinition = $schema->getSubSchema((string)$extIdent)->getField($tableField); } else { $fieldDefinition = $schema->getField($tableField); } if ($fieldDefinition->getLabel() !== '') { $flexFieldLabel = $languageService->sL($fieldDefinition->getLabel()); } if (empty($extConf['sheets']) || !is_array($extConf['sheets'])) { continue; } // Get all fields in sheet foreach ($extConf['sheets'] as $sheetName => $sheet) { if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) { continue; } foreach ($sheet['ROOT']['el'] as $pluginFieldName => $field) { // Use only fields that have exclude flag set if (empty($field['exclude'])) { continue; } $fieldLabel = !empty($field['label']) ? $languageService->sL($field['label']) : $pluginFieldName; $excludeFieldGroup[] = [ 'labels' => trim($translatedTable . ' ' . $flexFieldLabel . ' ' . $extIdent, ': ') . ':' . $fieldLabel, 'sectionHeader' => trim($translatedTable . ' ' . $flexFieldLabel . ' ' . $extIdent, ':'), 'table' => $table, 'tableField' => $tableField, 'extIdent' => $extIdent, 'fieldName' => $pluginFieldName, 'fullField' => $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $pluginFieldName, 'fieldLabel' => $fieldLabel, 'origin' => 'flexForm', ]; } } } } // Sort fields by the translated value if (!empty($excludeFieldGroup)) { usort($excludeFieldGroup, static function (array $array1, array $array2) { $array1 = reset($array1); $array2 = reset($array2); if (is_string($array1) && is_string($array2)) { return strcasecmp($array1, $array2); } return 0; }); $excludeFieldGroups = array_merge($excludeFieldGroups, $excludeFieldGroup); } } return $excludeFieldGroups; } /** * Returns FlexForm data structures it finds. Used in select "special" for be_groups * to set "exclude" flags for single flex form fields. * * This only finds flex forms registered in 'ds' config sections - default and record type specific. * This does not resolve other sophisticated flex form data structure references. * * @todo: This approach is limited and doesn't find everything. It works for casual tt_content plugins, though: * @todo: The data structure identifier determination depends on data row, but we don't have all rows at hand here. * @todo: The code thus "guesses" some standard data structure identifier scenarios and tries to resolve those. * @todo: This guessing can not be solved in a good way. A general registry of "all" possible data structures is * @todo: probably not wanted, since that wouldn't work for truly dynamic DS calculations. Probably the only * @todo: thing we could do here is a hook to allow extensions declaring specific data structures to * @todo: allow backend admins to set exclude flags for certain fields in those cases. * * @param string $table Table to handle * @return array Data structures */ protected function getRegisteredFlexForms(string $table): array { if (!$this->tcaSchemaFactory->has($table)) { return []; } $schema = $this->tcaSchemaFactory->get($table); $flexForms = []; // Get all flex fields and add the default data structure foreach ($schema->getFields() as $field => $fieldDefinition) { if ($fieldDefinition->getType() !== TableColumnType::FLEX->value) { continue; } $flexForms[$field] = []; // Default data structure try { $flexForms[$field]['default'] = $this->flexFormTools->parseDataStructureByIdentifier(json_encode([ 'type' => 'tca', 'tableName' => $table, 'fieldName' => $field, 'dataStructureKey' => 'default', ]), $schema); } catch (InvalidIdentifierException $e) { // Skip default on error } } // If flex fields exist and the table supports sub schemata, add specific data strcuturs for those sub schemas if ($flexForms !== [] && $schema->supportsSubSchema()) { foreach ($schema->getSubSchemata() as $recordType => $subSchema) { foreach (array_keys($flexForms) as $fieldName) { if ($subSchema->hasField($fieldName)) { try { $flexForms[$fieldName][$recordType] = $this->flexFormTools->parseDataStructureByIdentifier(json_encode([ 'type' => 'tca', 'tableName' => $table, 'fieldName' => $fieldName, 'dataStructureKey' => $recordType, ]), $schema); } catch (InvalidIdentifierException $e) { // Skip record type specific config on error } } } } } return $flexForms; } /** * Returns an array with explicit allow fields. * Used for listing these field/value pairs in be_groups forms * * @return array Array with information from all of $GLOBALS['TCA'] */ protected function getGroupedExplicitAuthFieldValues(): array { $languageService = $this->getLanguageService(); $allowOptions = []; foreach ($this->tcaSchemaFactory->all() as $table => $schema) { // All field names configured: foreach ($schema->getFields() as $field => $fieldDefinition) { $fieldConfig = $fieldDefinition->getConfiguration(); if (($fieldConfig['type'] ?? '') !== 'select' || ($fieldConfig['authMode'] ?? false) !== 'explicitAllow' || empty($fieldConfig['items']) || !is_array($fieldConfig['items']) ) { continue; } // Get Human Readable names of fields and table: $allowOptions[$table . ':' . $field]['tableFieldLabel'] = $schema->getTitle($languageService->sL(...)) . ': ' . $languageService->sL($fieldDefinition->getLabel()); foreach ($fieldConfig['items'] as $item) { $itemIdentifier = (string)($item['value'] ?? ''); // Values '' and '--div--' are not controlled by this setting. if ($itemIdentifier === '' || $itemIdentifier === '--div--') { continue; } $allowOptions[$table . ':' . $field]['items'][$itemIdentifier] = $languageService->sL($item['label'] ?? ''); } } } return $allowOptions; } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } }