| Current Path : /var/www/surf/TYPO3/vendor/typo3/cms-core/Classes/Hooks/ |
| Current File : /var/www/surf/TYPO3/vendor/typo3/cms-core/Classes/Hooks/TcaItemsProcessorFunctions.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\Hooks;
use TYPO3\CMS\Backend\Module\ModuleProvider;
use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException;
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconRegistry;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Various items processor functions, mainly used for special select fields in `be_users` and `be_groups`
*
* @internal This class is a hook implementation and is not part of the TYPO3 Core API.
*/
class TcaItemsProcessorFunctions
{
protected IconFactory $iconFactory;
protected IconRegistry $iconRegistry;
public function __construct()
{
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$this->iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
}
public function populateAvailableTables(array &$fieldDefinition): void
{
foreach ($GLOBALS['TCA'] as $tableName => $tableConfiguration) {
if ($tableConfiguration['ctrl']['adminOnly'] ?? false) {
// Hide "admin only" tables
continue;
}
$label = ($tableConfiguration['ctrl']['title'] ?? '') ?: '';
$icon = $this->iconFactory->mapRecordTypeToIconIdentifier($tableName, []);
$fieldDefinition['items'][] = ['label' => $label, 'value' => $tableName, 'icon' => $icon];
}
}
public function populateAvailablePageTypes(array &$fieldDefinition): void
{
$pageTypes = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'] ?? [];
if (is_array($pageTypes) && $pageTypes !== []) {
foreach ($pageTypes as $pageType) {
if (!is_array($pageType) || !isset($pageType['value']) || $pageType['value'] === '--div--') {
// Skip non arrays and divider items
continue;
}
$icon = $this->iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $pageType['value']]);
$fieldDefinition['items'][] = ['label' => $pageType['label'], 'value' => $pageType['value'], 'icon' => $icon];
}
}
}
public function populateAvailableUserModules(array &$fieldDefinition): void
{
$modules = GeneralUtility::makeInstance(ModuleProvider::class)->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'] ?? '';
// 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, []);
$fieldDefinition['items'][$sectionHeader] = ['label' => $sectionHeader, 'value' => '--div--', 'icon' => $icon];
}
} elseif (!isset($fieldDefinition['items'][$table])) {
// Add header if not yet set for table
$sectionHeader = $GLOBALS['TCA'][$table]['ctrl']['title'] ?? '';
$icon = $this->iconFactory->mapRecordTypeToIconIdentifier($table, []);
$fieldDefinition['items'][$table] = ['label' => $sectionHeader, 'value' => '--div--', 'icon' => $icon];
}
$fullField = $excludeFieldGroup['fullField'] ?? '';
$fieldName = $excludeFieldGroup['fieldName'] ?? '';
$label = $origin === 'flexForm'
? ($excludeFieldGroup['fieldLabel'] ?? '')
: $languageService->sL($GLOBALS['TCA'][$table]['columns'][$fieldName]['label'] ?? '');
// 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);
}
$columns = $GLOBALS['TCA'][$table]['columns'] ?? false;
if (!is_array($columns) || $columns === []) {
throw new \RuntimeException('Given table ' . $table . ' does not define any columns 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'];
}
// Loop on all table columns to find category fields
foreach ($columns as $fieldName => $fieldConfig) {
if (($fieldConfig['config']['type'] ?? '') !== 'category'
|| !in_array($fieldConfig['config']['relationship'] ?? '', $allowedRelationships, true)
) {
continue;
}
$fieldLabel = $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fieldName]['label']);
$fieldDefinition['items'][] = ['label' => $fieldLabel, '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 ($GLOBALS['TCA'] as $table => $conf) {
$tableToTranslation[$table] = $languageService->sL($conf['ctrl']['title'] ?? '');
}
// Sort by translations
asort($tableToTranslation);
foreach ($tableToTranslation as $table => $translatedTable) {
$excludeFieldGroup = [];
// All field names configured and not restricted to admins
if (!empty($GLOBALS['TCA'][$table]['columns'])
&& is_array($GLOBALS['TCA'][$table]['columns'])
&& empty($GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
&& (empty($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
) {
foreach ($GLOBALS['TCA'][$table]['columns'] as $fieldName => $fieldDefinition) {
// Only show fields that can be excluded for editors, or are hidden for non-admins
if (($fieldDefinition['exclude'] ?? false) && ($fieldDefinition['displayCond'] ?? '') !== 'HIDE_FOR_NON_ADMINS') {
// Get human readable names of fields
$translatedField = $languageService->sL($fieldDefinition['label'] ?? '');
// 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) {
// Prefix for field label, e.g. "Plugin Options:"
$labelPrefix = '';
if (!empty($GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
$labelPrefix = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
}
// Get all sheets
foreach ($flexForms as $extIdent => $extConf) {
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 . ' ' . $labelPrefix . ' ' . $extIdent, ': ') . ':' . $fieldLabel,
'sectionHeader' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $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.
* 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 (empty($GLOBALS['TCA'][$table]['columns']) || !is_array($GLOBALS['TCA'][$table]['columns'])) {
return [];
}
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
$flexForms = [];
foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fieldDefinition) {
if (($fieldDefinition['config']['type'] ?? '') !== 'flex'
|| empty($fieldDefinition['config']['ds'])
|| !is_array($fieldDefinition['config']['ds'])
) {
continue;
}
$flexForms[$field] = [];
foreach (array_keys($fieldDefinition['config']['ds']) as $flexFormKey) {
$flexFormKey = (string)$flexFormKey;
// Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
$identFields = GeneralUtility::trimExplode(',', $flexFormKey);
$extIdent = $identFields[0] ?? '';
if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
$extIdent = $identFields[1];
}
$flexFormDataStructureIdentifier = json_encode([
'type' => 'tca',
'tableName' => $table,
'fieldName' => $field,
'dataStructureKey' => $flexFormKey,
]);
try {
$dataStructure = $flexFormTools->parseDataStructureByIdentifier($flexFormDataStructureIdentifier);
$flexForms[$field][$extIdent] = $dataStructure;
} catch (InvalidIdentifierException $e) {
// Deliberately empty: The DS identifier is guesswork and the flex ds parser throws
// this exception if it can not resolve to a valid data structure. This is "ok" here
// and the exception is just eaten.
}
}
}
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 ($GLOBALS['TCA'] as $table => $tableConfiguration) {
if (empty($tableConfiguration['columns']) || !is_array($tableConfiguration['columns'])) {
continue;
}
// All field names configured:
foreach ($tableConfiguration['columns'] as $field => $fieldDefinition) {
$fieldConfig = $fieldDefinition['config'] ?? [];
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'] =
$languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title'] ?? '') . ': '
. $languageService->sL($GLOBALS['TCA'][$table]['columns'][$field]['label'] ?? '');
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'];
}
}