‪TYPO3CMS  ‪main
TcaItemsProcessorFunctions.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
18 namespace ‪TYPO3\CMS\Core\Hooks;
19 
27 
34 {
37 
38  public function ‪__construct()
39  {
40  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
41  $this->iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
42  }
43 
44  public function ‪populateAvailableTables(array &$fieldDefinition): void
45  {
46  foreach (‪$GLOBALS['TCA'] as $tableName => $tableConfiguration) {
47  if ($tableConfiguration['ctrl']['adminOnly'] ?? false) {
48  // Hide "admin only" tables
49  continue;
50  }
51  $label = ($tableConfiguration['ctrl']['title'] ?? '') ?: '';
52  $icon = $this->iconFactory->mapRecordTypeToIconIdentifier($tableName, []);
53  $fieldDefinition['items'][] = ['label' => $label, 'value' => $tableName, 'icon' => $icon];
54  }
55  }
56 
57  public function ‪populateAvailablePageTypes(array &$fieldDefinition): void
58  {
59  $pageTypes = ‪$GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'] ?? [];
60  if (is_array($pageTypes) && $pageTypes !== []) {
61  foreach ($pageTypes as $pageType) {
62  if (!is_array($pageType) || !isset($pageType['value']) || $pageType['value'] === '--div--') {
63  // Skip non arrays and divider items
64  continue;
65  }
66  $icon = $this->iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $pageType['value']]);
67  $fieldDefinition['items'][] = ['label' => $pageType['label'], 'value' => $pageType['value'], 'icon' => $icon];
68  }
69  }
70  }
71 
72  public function ‪populateAvailableUserModules(array &$fieldDefinition): void
73  {
74  $modules = GeneralUtility::makeInstance(ModuleProvider::class)->getUserModules();
75  if ($modules === []) {
76  return;
77  }
78  $languageService = $this->‪getLanguageService();
79  foreach ($modules as ‪$identifier => $module) {
80  // Item configuration
81  $label = $languageService->sL($module->getTitle());
82  $parentModule = $module->getParentModule();
83  while ($parentModule) {
84  $label = $languageService->sL($parentModule->getTitle()) . ' > ' . $label;
85  $parentModule = $parentModule->getParentModule();
86  }
87  $help = null;
88  if ($module->getDescription()) {
89  $help = [
90  'title' => $languageService->sL($module->getShortDescription()),
91  'description' => $languageService->sL($module->getDescription()),
92  ];
93  }
94  $fieldDefinition['items'][] = [
95  'label' => $label,
96  'value' => ‪$identifier,
97  'icon' => $module->getIconIdentifier(),
98  'description' => $help,
99  ];
100  }
101  }
102 
103  public function ‪populateExcludeFields(array &$fieldDefinition): void
104  {
105  $languageService = $this->‪getLanguageService();
106  foreach ($this->‪getGroupedExcludeFields() as $excludeFieldGroup) {
107  $table = $excludeFieldGroup['table'] ?? '';
108  $origin = $excludeFieldGroup['origin'] ?? '';
109  // If the field comes from a FlexForm, the syntax is more complex
110  if ($origin === 'flexForm') {
111  // The field comes from a plugins FlexForm
112  // Add header if not yet set for plugin section
113  $sectionHeader = $excludeFieldGroup['sectionHeader'] ?? '';
114  if (!isset($fieldDefinition['items'][$sectionHeader])) {
115  // there is no icon handling for plugins - we take the icon from the table
116  $icon = $this->iconFactory->mapRecordTypeToIconIdentifier($table, []);
117  $fieldDefinition['items'][$sectionHeader] = ['label' => $sectionHeader, 'value' => '--div--', 'icon' => $icon];
118  }
119  } elseif (!isset($fieldDefinition['items'][$table])) {
120  // Add header if not yet set for table
121  $sectionHeader = ‪$GLOBALS['TCA'][$table]['ctrl']['title'] ?? '';
122  $icon = $this->iconFactory->mapRecordTypeToIconIdentifier($table, []);
123  $fieldDefinition['items'][$table] = ['label' => $sectionHeader, 'value' => '--div--', 'icon' => $icon];
124  }
125  $fullField = $excludeFieldGroup['fullField'] ?? '';
126  $fieldName = $excludeFieldGroup['fieldName'] ?? '';
127  $label = $origin === 'flexForm'
128  ? ($excludeFieldGroup['fieldLabel'] ?? '')
129  : $languageService->sL(‪$GLOBALS['TCA'][$table]['columns'][$fieldName]['label'] ?? '');
130  // Item configuration:
131  $fieldDefinition['items'][] = [
132  'label' => rtrim($label, ':') . ' (' . $fieldName . ')',
133  'value' => $table . ':' . $fullField,
134  'icon' => 'empty-empty',
135  ];
136  }
137  }
138 
139  public function ‪populateExplicitAuthValues(array &$fieldDefinition): void
140  {
141  // Traverse grouped field values:
142  foreach ($this->‪getGroupedExplicitAuthFieldValues() as $groupKey => $tableFields) {
143  if (empty($tableFields['items']) || !is_array($tableFields['items'])) {
144  continue;
145  }
146  // Add header:
147  $fieldDefinition['items'][] = [
148  'label' => $tableFields['tableFieldLabel'] ?? '',
149  'value' => '--div--',
150  ];
151  // Traverse options for this field:
152  foreach ($tableFields['items'] as $itemValue => $itemContent) {
153  $fieldDefinition['items'][] = [
154  'label' => $itemContent,
155  'value' => $groupKey . ':' . preg_replace('/[:|,]/', '', (string)$itemValue),
156  'icon' => 'status-status-permission-granted',
157  ];
158  }
159  }
160  }
161 
162  public function ‪populateCustomPermissionOptions(array &$fieldDefinition): void
163  {
164  $customOptions = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'] ?? [];
165  if (!is_array($customOptions) || $customOptions === []) {
166  return;
167  }
168  $languageService = $this->‪getLanguageService();
169  foreach ($customOptions as $customOptionsKey => $customOptionsValue) {
170  if (empty($customOptionsValue['items']) || !is_array($customOptionsValue['items'])) {
171  continue;
172  }
173  // Add header:
174  $fieldDefinition['items'][] = [
175  'label' => $languageService->sL($customOptionsValue['header'] ?? ''),
176  'value' => '--div--',
177  ];
178  // Traverse items:
179  foreach ($customOptionsValue['items'] as $itemKey => $itemConfig) {
180  $icon = 'empty-empty';
181  $helpText = '';
182  if (!empty($itemConfig[1]) && $this->iconRegistry->isRegistered($itemConfig[1])) {
183  // Use icon identifier when registered
184  $icon = $itemConfig[1];
185  }
186  if (!empty($itemConfig[2])) {
187  $helpText = $languageService->sL($itemConfig[2]);
188  }
189  $fieldDefinition['items'][] = [
190  'label' => $languageService->sL($itemConfig[0] ?? ''),
191  'value' => $customOptionsKey . ':' . preg_replace('/[:|,]/', '', (string)$itemKey),
192  'icon' => $icon,
193  'description' => $helpText,
194  ];
195  }
196  }
197  }
198 
202  public function ‪populateAvailableCategoryFields(array &$fieldDefinition): void
203  {
204  $table = (string)($fieldDefinition['config']['itemsProcConfig']['table'] ?? '');
205  if ($table === '') {
206  throw new \UnexpectedValueException('No table to search for category fields given.', 1627565458);
207  }
208 
209  $columns = ‪$GLOBALS['TCA'][$table]['columns'] ?? false;
210  if (!is_array($columns) || $columns === []) {
211  throw new \RuntimeException('Given table ' . $table . ' does not define any columns to search for category fields.', 1627565459);
212  }
213 
214  // Only category fields with the "manyToMany" relationship are allowed by default.
215  // This can however be changed using the "allowedRelationships" itemsProcConfig.
216  $allowedRelationships = $fieldDefinition['config']['itemsProcConfig']['allowedRelationships'] ?? false;
217  if (!is_array($allowedRelationships) || $allowedRelationships === []) {
218  $allowedRelationships = ['manyToMany'];
219  }
220 
221  // Loop on all table columns to find category fields
222  foreach ($columns as $fieldName => $fieldConfig) {
223  if (($fieldConfig['config']['type'] ?? '') !== 'category'
224  || !in_array($fieldConfig['config']['relationship'] ?? '', $allowedRelationships, true)
225  ) {
226  continue;
227  }
228  $fieldLabel = $this->‪getLanguageService()->sL(‪$GLOBALS['TCA'][$table]['columns'][$fieldName]['label']);
229  $fieldDefinition['items'][] = ['label' => $fieldLabel, 'value' => $fieldName];
230  }
231  }
232 
240  protected function ‪getGroupedExcludeFields(): array
241  {
242  $languageService = $this->‪getLanguageService();
243  $excludeFieldGroups = [];
244 
245  // Fetch translations for table names
246  $tableToTranslation = [];
247  // All TCA keys
248  foreach (‪$GLOBALS['TCA'] as $table => $conf) {
249  $tableToTranslation[$table] = $languageService->sL($conf['ctrl']['title'] ?? '');
250  }
251  // Sort by translations
252  asort($tableToTranslation);
253  foreach ($tableToTranslation as $table => $translatedTable) {
254  $excludeFieldGroup = [];
255 
256  // All field names configured and not restricted to admins
257  if (!empty(‪$GLOBALS['TCA'][$table]['columns'])
258  && is_array(‪$GLOBALS['TCA'][$table]['columns'])
259  && empty(‪$GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
260  && (empty(‪$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty(‪$GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
261  ) {
262  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $fieldName => $fieldDefinition) {
263  // Only show fields that can be excluded for editors, or are hidden for non-admins
264  if (($fieldDefinition['exclude'] ?? false) && ($fieldDefinition['displayCond'] ?? '') !== 'HIDE_FOR_NON_ADMINS') {
265  // Get human readable names of fields
266  $translatedField = $languageService->sL($fieldDefinition['label'] ?? '');
267  // Add entry, key 'labels' needed for sorting
268  $excludeFieldGroup[] = [
269  'labels' => $translatedTable . ':' . $translatedField,
270  'sectionHeader' => $translatedTable,
271  'table' => $table,
272  'tableField' => $fieldName,
273  'fieldName' => $fieldName,
274  'fullField' => $fieldName,
275  'fieldLabel' => $translatedField,
276  'origin' => 'tca',
277  ];
278  }
279  }
280  }
281  // All FlexForm fields
282  $flexFormArray = $this->‪getRegisteredFlexForms((string)$table);
283  foreach ($flexFormArray as $tableField => $flexForms) {
284  // Prefix for field label, e.g. "Plugin Options:"
285  $labelPrefix = '';
286  if (!empty(‪$GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
287  $labelPrefix = $languageService->sL(‪$GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
288  }
289  // Get all sheets
290  foreach ($flexForms as $extIdent => $extConf) {
291  if (empty($extConf['sheets']) || !is_array($extConf['sheets'])) {
292  continue;
293  }
294  // Get all fields in sheet
295  foreach ($extConf['sheets'] as $sheetName => $sheet) {
296  if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
297  continue;
298  }
299  foreach ($sheet['ROOT']['el'] as $pluginFieldName => $field) {
300  // Use only fields that have exclude flag set
301  if (empty($field['exclude'])) {
302  continue;
303  }
304  $fieldLabel = !empty($field['label']) ? $languageService->sL($field['label']) : $pluginFieldName;
305  $excludeFieldGroup[] = [
306  'labels' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ': ') . ':' . $fieldLabel,
307  'sectionHeader' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ':'),
308  'table' => $table,
309  'tableField' => $tableField,
310  'extIdent' => $extIdent,
311  'fieldName' => $pluginFieldName,
312  'fullField' => $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $pluginFieldName,
313  'fieldLabel' => $fieldLabel,
314  'origin' => 'flexForm',
315  ];
316  }
317  }
318  }
319  }
320  // Sort fields by the translated value
321  if (!empty($excludeFieldGroup)) {
322  usort($excludeFieldGroup, static function (array $array1, array $array2) {
323  $array1 = reset($array1);
324  $array2 = reset($array2);
325  if (is_string($array1) && is_string($array2)) {
326  return strcasecmp($array1, $array2);
327  }
328  return 0;
329  });
330  $excludeFieldGroups = array_merge($excludeFieldGroups, $excludeFieldGroup);
331  }
332  }
333 
334  return $excludeFieldGroups;
335  }
336 
355  protected function ‪getRegisteredFlexForms(string $table): array
356  {
357  if (empty(‪$GLOBALS['TCA'][$table]['columns']) || !is_array(‪$GLOBALS['TCA'][$table]['columns'])) {
358  return [];
359  }
360  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
361  $flexForms = [];
362  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $fieldDefinition) {
363  if (($fieldDefinition['config']['type'] ?? '') !== 'flex'
364  || empty($fieldDefinition['config']['ds'])
365  || !is_array($fieldDefinition['config']['ds'])
366  ) {
367  continue;
368  }
369  $flexForms[$field] = [];
370  foreach (array_keys($fieldDefinition['config']['ds']) as $flexFormKey) {
371  $flexFormKey = (string)$flexFormKey;
372  // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
373  $identFields = ‪GeneralUtility::trimExplode(',', $flexFormKey);
374  $extIdent = $identFields[0] ?? '';
375  if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
376  $extIdent = $identFields[1];
377  }
378  $flexFormDataStructureIdentifier = json_encode([
379  'type' => 'tca',
380  'tableName' => $table,
381  'fieldName' => $field,
382  'dataStructureKey' => $flexFormKey,
383  ]);
384  try {
385  $dataStructure = $flexFormTools->parseDataStructureByIdentifier($flexFormDataStructureIdentifier);
386  $flexForms[$field][$extIdent] = $dataStructure;
387  } catch (‪InvalidIdentifierException $e) {
388  // Deliberately empty: The DS identifier is guesswork and the flex ds parser throws
389  // this exception if it can not resolve to a valid data structure. This is "ok" here
390  // and the exception is just eaten.
391  }
392  }
393  }
394  return $flexForms;
395  }
396 
403  protected function ‪getGroupedExplicitAuthFieldValues(): array
404  {
405  $languageService = $this->‪getLanguageService();
406  $allowOptions = [];
407  foreach (‪$GLOBALS['TCA'] as $table => $tableConfiguration) {
408  if (empty($tableConfiguration['columns']) || !is_array($tableConfiguration['columns'])) {
409  continue;
410  }
411  // All field names configured:
412  foreach ($tableConfiguration['columns'] as $field => $fieldDefinition) {
413  $fieldConfig = $fieldDefinition['config'] ?? [];
414  if (($fieldConfig['type'] ?? '') !== 'select'
415  || ($fieldConfig['authMode'] ?? false) !== 'explicitAllow'
416  || empty($fieldConfig['items'])
417  || !is_array($fieldConfig['items'])
418  ) {
419  continue;
420  }
421  // Get Human Readable names of fields and table:
422  $allowOptions[$table . ':' . $field]['tableFieldLabel'] =
423  $languageService->sL(‪$GLOBALS['TCA'][$table]['ctrl']['title'] ?? '') . ': '
424  . $languageService->sL(‪$GLOBALS['TCA'][$table]['columns'][$field]['label'] ?? '');
425 
426  foreach ($fieldConfig['items'] as $item) {
427  $itemIdentifier = (string)($item['value'] ?? '');
428  // Values '' and '--div--' are not controlled by this setting.
429  if ($itemIdentifier === '' || $itemIdentifier === '--div--') {
430  continue;
431  }
432  $allowOptions[$table . ':' . $field]['items'][$itemIdentifier] = $languageService->sL($item['label'] ?? '');
433  }
434  }
435  }
436  return $allowOptions;
437  }
438 
440  {
441  return ‪$GLOBALS['LANG'];
442  }
443 }
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\__construct
‪__construct()
Definition: TcaItemsProcessorFunctions.php:38
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\getGroupedExcludeFields
‪array getGroupedExcludeFields()
Definition: TcaItemsProcessorFunctions.php:240
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\populateCustomPermissionOptions
‪populateCustomPermissionOptions(array &$fieldDefinition)
Definition: TcaItemsProcessorFunctions.php:162
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\getRegisteredFlexForms
‪array getRegisteredFlexForms(string $table)
Definition: TcaItemsProcessorFunctions.php:355
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\populateAvailableUserModules
‪populateAvailableUserModules(array &$fieldDefinition)
Definition: TcaItemsProcessorFunctions.php:72
‪TYPO3\CMS\Backend\Module\ModuleProvider
Definition: ModuleProvider.php:29
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException
Definition: InvalidIdentifierException.php:21
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\populateAvailablePageTypes
‪populateAvailablePageTypes(array &$fieldDefinition)
Definition: TcaItemsProcessorFunctions.php:57
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\populateExplicitAuthValues
‪populateExplicitAuthValues(array &$fieldDefinition)
Definition: TcaItemsProcessorFunctions.php:139
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools
Definition: FlexFormTools.php:40
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\getLanguageService
‪getLanguageService()
Definition: TcaItemsProcessorFunctions.php:439
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\populateExcludeFields
‪populateExcludeFields(array &$fieldDefinition)
Definition: TcaItemsProcessorFunctions.php:103
‪TYPO3\CMS\Core\Imaging\IconRegistry
Definition: IconRegistry.php:32
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions
Definition: TcaItemsProcessorFunctions.php:34
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\getGroupedExplicitAuthFieldValues
‪array getGroupedExplicitAuthFieldValues()
Definition: TcaItemsProcessorFunctions.php:403
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\$iconFactory
‪IconFactory $iconFactory
Definition: TcaItemsProcessorFunctions.php:35
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\populateAvailableCategoryFields
‪populateAvailableCategoryFields(array &$fieldDefinition)
Definition: TcaItemsProcessorFunctions.php:202
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\populateAvailableTables
‪populateAvailableTables(array &$fieldDefinition)
Definition: TcaItemsProcessorFunctions.php:44
‪TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions\$iconRegistry
‪IconRegistry $iconRegistry
Definition: TcaItemsProcessorFunctions.php:36
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\Hooks
Definition: BackendUserGroupIntegrityCheck.php:18