‪TYPO3CMS  10.4
AbstractItemProvider.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Doctrine\DBAL\DBALException;
44 
50 {
59  protected function ‪resolveItemProcessorFunction(array $result, $fieldName, array $items)
60  {
61  $table = $result['tableName'];
62  $config = $result['processedTca']['columns'][$fieldName]['config'];
63 
64  $pageTsProcessorParameters = null;
65  if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'])) {
66  $pageTsProcessorParameters = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'];
67  }
68  $processorParameters = [
69  // Function manipulates $items directly and return nothing
70  'items' => &$items,
71  'config' => $config,
72  'TSconfig' => $pageTsProcessorParameters,
73  'table' => $table,
74  'row' => $result['databaseRow'],
75  'field' => $fieldName,
76  // IMPORTANT: Below fields are only available in FormEngine context.
77  // They are not used by the DataHandler when processing itemsProcFunc
78  // for checking if a submitted value is valid. This means, in case
79  // an item is added based on one of these fields, it won't be persisted
80  // by the DataHandler. This currently(!) only concerns columns of type "check"
81  // and type "radio", see checkValueForCheck() and checkValueForRadio().
82  // Therefore, no limitations when using those fields with other types
83  // like "select", but this may change in the future.
84  'inlineParentUid' => $result['inlineParentUid'],
85  'inlineParentTableName' => $result['inlineParentTableName'],
86  'inlineParentFieldName' => $result['inlineParentFieldName'],
87  'inlineParentConfig' => $result['inlineParentConfig'],
88  'inlineTopMostParentUid' => $result['inlineTopMostParentUid'],
89  'inlineTopMostParentTableName' => $result['inlineTopMostParentTableName'],
90  'inlineTopMostParentFieldName' => $result['inlineTopMostParentFieldName'],
91  ];
92  if (!empty($result['flexParentDatabaseRow'])) {
93  $processorParameters['flexParentDatabaseRow'] = $result['flexParentDatabaseRow'];
94  }
95 
96  try {
97  GeneralUtility::callUserFunction($config['itemsProcFunc'], $processorParameters, $this);
98  } catch (\‪Exception $exception) {
99  // The itemsProcFunc method may throw an exception, create a flash message if so
100  $languageService = $this->‪getLanguageService();
101  $fieldLabel = $fieldName;
102  if (!empty($result['processedTca']['columns'][$fieldName]['label'])) {
103  $fieldLabel = $languageService->sL($result['processedTca']['columns'][$fieldName]['label']);
104  }
105  $message = sprintf(
106  $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.items_proc_func_error'),
107  $fieldLabel,
108  $exception->getMessage()
109  );
111  $flashMessage = GeneralUtility::makeInstance(
112  FlashMessage::class,
113  $message,
114  '',
116  true
117  );
119  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
120  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
121  $defaultFlashMessageQueue->enqueue($flashMessage);
122  }
123 
124  return $items;
125  }
126 
140  protected function ‪addItemsFromPageTsConfig(array $result, $fieldName, array $items)
141  {
142  $table = $result['tableName'];
143  if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
144  && is_array($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
145  ) {
146  $addItemsArray = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'];
147  foreach ($addItemsArray as $value => $label) {
148  // If the value ends with a dot, it is a subelement like "34.icon = mylabel.png", skip it
149  if (substr($value, -1) === '.') {
150  continue;
151  }
152  // Check if value "34 = mylabel" also has a "34.icon = myImage.png"
153  $iconIdentifier = null;
154  if (isset($addItemsArray[$value . '.'])
155  && is_array($addItemsArray[$value . '.'])
156  && !empty($addItemsArray[$value . '.']['icon'])
157  ) {
158  $iconIdentifier = $addItemsArray[$value . '.']['icon'];
159  }
160  $items[] = [$label, $value, $iconIdentifier];
161  }
162  }
163  return $items;
164  }
165 
177  protected function ‪addItemsFromSpecial(array $result, $fieldName, array $items)
178  {
179  // Guard
180  if (empty($result['processedTca']['columns'][$fieldName]['config']['special'])
181  || !is_string($result['processedTca']['columns'][$fieldName]['config']['special'])
182  ) {
183  return $items;
184  }
185 
186  $languageService = $this->‪getLanguageService();
187  ‪$iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
188  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
189 
190  $special = $result['processedTca']['columns'][$fieldName]['config']['special'];
191  switch (true) {
192  case $special === 'tables':
193  foreach (‪$GLOBALS['TCA'] as $currentTable => $_) {
194  if (!empty(‪$GLOBALS['TCA'][$currentTable]['ctrl']['adminOnly'])) {
195  // Hide "admin only" tables
196  continue;
197  }
198  $label = !empty(‪$GLOBALS['TCA'][$currentTable]['ctrl']['title']) ? ‪$GLOBALS['TCA'][$currentTable]['ctrl']['title'] : '';
199  $icon = $iconFactory->mapRecordTypeToIconIdentifier($currentTable, []);
200  $languageService->loadSingleTableDescription($currentTable);
201  $helpText = (string)(‪$GLOBALS['TCA_DESCR'][$currentTable]['columns']['']['description'] ?? '');
202  $items[] = [$label, $currentTable, $icon, null, $helpText];
203  }
204  break;
205  case $special === 'pagetypes':
206  if (isset(‪$GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
207  && is_array(‪$GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
208  ) {
209  $specialItems = ‪$GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
210  foreach ($specialItems as $specialItem) {
211  if (!is_array($specialItem) || $specialItem[1] === '--div--') {
212  // Skip non arrays and divider items
213  continue;
214  }
215  $label = $specialItem[0];
216  $value = $specialItem[1];
217  $icon = $iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $specialItem[1]]);
218  $items[] = [$label, $value, $icon];
219  }
220  }
221  break;
222  case $special === 'exclude':
223  $excludeArrays = $this->‪getExcludeFields();
224  foreach ($excludeArrays as $excludeArray) {
225  // If the field comes from a FlexForm, the syntax is more complex
226  if ($excludeArray['origin'] === 'flexForm') {
227  // The field comes from a plugins FlexForm
228  // Add header if not yet set for plugin section
229  if (!isset($items[$excludeArray['sectionHeader']])) {
230  // there is no icon handling for plugins - we take the icon from the table
231  $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], []);
232  $items[$excludeArray['sectionHeader']] = [
233  $excludeArray['sectionHeader'],
234  '--div--',
235  $icon
236  ];
237  }
238  } else {
239  // Add header if not yet set for table
240  if (!isset($items[$excludeArray['table']])) {
241  $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], []);
242  $items[$excludeArray['table']] = [
243  ‪$GLOBALS['TCA'][$excludeArray['table']]['ctrl']['title'],
244  '--div--',
245  $icon
246  ];
247  }
248  }
249  // Add help text
250  $languageService->loadSingleTableDescription($excludeArray['table']);
251  $helpText = (string)(‪$GLOBALS['TCA_DESCR'][$excludeArray['table']]['columns'][$excludeArray['fullField']]['description'] ?? '');
252  // Item configuration:
253  $items[] = [
254  rtrim($excludeArray['origin'] === 'flexForm' ? $excludeArray['fieldLabel'] : $languageService->sL(‪$GLOBALS['TCA'][$excludeArray['table']]['columns'][$excludeArray['fieldName']]['label']), ':') . ' (' . $excludeArray['fieldName'] . ')',
255  $excludeArray['table'] . ':' . $excludeArray['fullField'],
256  'empty-empty',
257  null,
258  $helpText
259  ];
260  }
261  break;
262  case $special === 'explicitValues':
263  $theTypes = $this->‪getExplicitAuthFieldValues();
264  ‪$icons = [
265  'ALLOW' => 'status-status-permission-granted',
266  'DENY' => 'status-status-permission-denied'
267  ];
268  // Traverse types:
269  foreach ($theTypes as $tableFieldKey => $theTypeArrays) {
270  if (!empty($theTypeArrays['items'])) {
271  // Add header:
272  $items[] = [
273  $theTypeArrays['tableFieldLabel'],
274  '--div--',
275  ];
276  // Traverse options for this field:
277  foreach ($theTypeArrays['items'] as $itemValue => $itemContent) {
278  // Add item to be selected:
279  $items[] = [
280  '[' . $itemContent[2] . '] ' . $itemContent[1],
281  $tableFieldKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $itemContent[0],
282  ‪$icons[$itemContent[0]]
283  ];
284  }
285  }
286  }
287  break;
288  case $special === 'languages':
289  $allLanguages = [];
290  if (($result['effectivePid'] ?? 0) === 0) {
291  // This provides a list of all languages available for ALL sites
292  // Due to the nature of the "sys_language_uid" field having no meaning currently,
293  // We preserve the language ID and make a list of all languages
294  $sites = $this->‪getAllSites();
295  foreach ($sites as $site) {
296  foreach ($site->getAllLanguages() as $language) {
297  $languageId = $language->getLanguageId();
298  if (isset($allLanguages[$languageId])) {
299  // Language already provided by another site, just add the label separately
300  $allLanguages[$languageId][0] .= ', ' . $language->getTitle() . ' [Site: ' . $site->getIdentifier() . ']';
301  } else {
302  $allLanguages[$languageId] = [
303  0 => $language->getTitle() . ' [Site: ' . $site->getIdentifier() . ']',
304  1 => $languageId,
305  2 => $language->getFlagIdentifier()
306  ];
307  }
308  }
309  }
310  ksort($allLanguages);
311  }
312  if (!empty($allLanguages)) {
313  foreach ($allLanguages as $item) {
314  $items[] = $item;
315  }
316  } else {
317  // Happens for non-pid=0 records (e.g. "tt_content"), or when no site was configured
318  foreach ($result['systemLanguageRows'] as $language) {
319  if ($language['uid'] !== -1) {
320  $items[] = [
321  0 => $language['title'],
322  1 => $language['uid'],
323  2 => $language['flagIconIdentifier']
324  ];
325  }
326  }
327  }
328 
329  break;
330  case $special === 'custom':
331  $customOptions = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'];
332  if (is_array($customOptions)) {
333  foreach ($customOptions as $coKey => $coValue) {
334  if (is_array($coValue['items'])) {
335  // Add header:
336  $items[] = [
337  $languageService->sL($coValue['header']),
338  '--div--'
339  ];
340  // Traverse items:
341  foreach ($coValue['items'] as $itemKey => $itemCfg) {
342  $icon = 'empty-empty';
343  $helpText = '';
344  if (!empty($itemCfg[1])) {
345  if (‪$iconRegistry->isRegistered($itemCfg[1])) {
346  // Use icon identifier when registered
347  $icon = $itemCfg[1];
348  }
349  }
350  if (!empty($itemCfg[2])) {
351  $helpText = $languageService->sL($itemCfg[2]);
352  }
353  $items[] = [
354  $languageService->sL($itemCfg[0]),
355  $coKey . ':' . preg_replace('/[:|,]/', '', $itemKey),
356  $icon,
357  null,
358  $helpText
359  ];
360  }
361  }
362  }
363  }
364  break;
365  case $special === 'modListGroup' || $special === 'modListUser':
367  $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
368  $loadModules->load(‪$GLOBALS['TBE_MODULES']);
369  $modList = $special === 'modListUser' ? $loadModules->modListUser : $loadModules->modListGroup;
370  if (is_array($modList)) {
371  foreach ($modList as $theMod) {
372  $moduleLabels = $loadModules->getLabelsForModule($theMod);
373  $moduleArray = ‪GeneralUtility::trimExplode('_', $theMod, true);
374  $mainModule = $moduleArray[0] ?? '';
375  $subModule = $moduleArray[1] ?? '';
376  // Icon:
377  if (!empty($subModule)) {
378  $icon = $loadModules->modules[$mainModule]['sub'][$subModule]['iconIdentifier'];
379  } else {
380  $icon = $loadModules->modules[$theMod]['iconIdentifier'];
381  }
382  // Add help text
383  $helpText = [
384  'title' => $languageService->sL($moduleLabels['shortdescription']),
385  'description' => $languageService->sL($moduleLabels['description'])
386  ];
387 
388  $label = '';
389  // Add label for main module if this is a submodule
390  if (!empty($subModule)) {
391  $mainModuleLabels = $loadModules->getLabelsForModule($mainModule);
392  $label .= $languageService->sL($mainModuleLabels['title']) . '>';
393  }
394  // Add modules own label now
395  $label .= $languageService->sL($moduleLabels['title']);
396 
397  // Item configuration
398  $items[] = [$label, $theMod, $icon, null, $helpText];
399  }
400  }
401  break;
402  default:
403  throw new \UnexpectedValueException(
404  'Unknown special value ' . $special . ' for field ' . $fieldName . ' of table ' . $result['tableName'],
405  1439298496
406  );
407  }
408  return $items;
409  }
410 
422  protected function ‪addItemsFromFolder(array $result, $fieldName, array $items)
423  {
424  if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
425  || !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
426  ) {
427  return $items;
428  }
429 
430  $fileFolderRaw = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
431  $fileFolder = GeneralUtility::getFileAbsFileName($fileFolderRaw);
432  if ($fileFolder === '') {
433  throw new \RuntimeException(
434  'Invalid folder given for item processing: ' . $fileFolderRaw . ' for table ' . $result['tableName'] . ', field ' . $fieldName,
435  1479399227
436  );
437  }
438  $fileFolder = rtrim($fileFolder, '/') . '/';
439 
440  if (@is_dir($fileFolder)) {
441  $fileExtensionList = '';
442  if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
443  && is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
444  ) {
445  $fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
446  }
447  $recursionLevels = isset($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'])
448  ? ‪MathUtility::forceIntegerInRange($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'], 0, 99)
449  : 99;
450  $fileArray = GeneralUtility::getAllFilesAndFoldersInPath([], $fileFolder, $fileExtensionList, false, $recursionLevels);
451  $fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
452  foreach ($fileArray as $fileReference) {
453  $fileInformation = pathinfo($fileReference);
454  $icon = GeneralUtility::inList(‪$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($fileInformation['extension']))
455  ? $fileFolder . $fileReference
456  : '';
457  $items[] = [
458  $fileReference,
459  $fileReference,
460  $icon
461  ];
462  }
463  }
464 
465  return $items;
466  }
467 
479  protected function ‪addItemsFromForeignTable(array $result, $fieldName, array $items)
480  {
481  $databaseError = null;
482  $queryResult = null;
483  // Guard
484  if (empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
485  || !is_string($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
486  ) {
487  return $items;
488  }
489 
490  $languageService = $this->‪getLanguageService();
491 
492  $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
493 
494  if (!isset(‪$GLOBALS['TCA'][$foreignTable]) || !is_array(‪$GLOBALS['TCA'][$foreignTable])) {
495  throw new \UnexpectedValueException(
496  'Field ' . $fieldName . ' of table ' . $result['tableName'] . ' reference to foreign table '
497  . $foreignTable . ', but this table is not defined in TCA',
498  1439569743
499  );
500  }
501 
502  $queryBuilder = $this->‪buildForeignTableQueryBuilder($result, $fieldName);
503  try {
504  $queryResult = $queryBuilder->execute();
505  } catch (DBALException $e) {
506  $databaseError = $e->getPrevious()->getMessage();
507  }
508 
509  // Early return on error with flash message
510  if (!empty($databaseError)) {
511  $msg = $databaseError . '. ';
512  $msg .= $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch');
513  $msgTitle = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch_title');
515  $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, ‪FlashMessage::ERROR, true);
517  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
519  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
520  $defaultFlashMessageQueue->enqueue($flashMessage);
521  return $items;
522  }
523 
524  $labelPrefix = '';
525  if (!empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'])) {
526  $labelPrefix = $result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'];
527  $labelPrefix = $languageService->sL($labelPrefix);
528  }
529 
530  $fileRepository = GeneralUtility::makeInstance(FileRepository::class);
531  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
532 
533  while ($foreignRow = $queryResult->fetch()) {
534  ‪BackendUtility::workspaceOL($foreignTable, $foreignRow);
535  // Only proceed in case the row was not unset and we don't deal with a delete placeholder
536  if (is_array($foreignRow)
537  && !‪VersionState::cast($foreignRow['t3ver_state'] ?? 0)->equals(‪VersionState::DELETE_PLACEHOLDER)
538  ) {
539  // If the foreign table sets selicon_field, this field can contain an image
540  // that represents this specific row.
541  $iconFieldName = '';
542  $isReferenceField = false;
543  if (!empty(‪$GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'])) {
544  $iconFieldName = ‪$GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'];
545  if (isset(‪$GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['type'])
546  && ‪$GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['type'] === 'inline'
547  && ‪$GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['foreign_table'] === 'sys_file_reference'
548  ) {
549  $isReferenceField = true;
550  }
551  }
552  $icon = '';
553  if ($isReferenceField) {
554  $references = $fileRepository->findByRelation($foreignTable, $iconFieldName, $foreignRow['uid']);
555  if (is_array($references) && !empty($references)) {
556  $icon = reset($references);
557  $icon = $icon->getPublicUrl();
558  }
559  } else {
560  // Else, determine icon based on record type, or a generic fallback
561  $icon = $iconFactory->mapRecordTypeToIconIdentifier($foreignTable, $foreignRow);
562  }
563  // Add the item
564  $items[] = [
565  $labelPrefix . ‪BackendUtility::getRecordTitle($foreignTable, $foreignRow),
566  $foreignRow['uid'],
567  $icon
568  ];
569  }
570  }
571 
572  return $items;
573  }
574 
585  protected function ‪removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
586  {
587  $table = $result['tableName'];
588  if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
589  || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
590  ) {
591  return $items;
592  }
593 
594  // If keepItems is set but is an empty list all current items get removed
595  if ($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'] === '') {
596  return [];
597  }
598 
600  $items,
601  $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'],
602  function ($value) {
603  return $value[1];
604  }
605  );
606  }
607 
618  protected function ‪removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
619  {
620  $table = $result['tableName'];
621  if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
622  || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
623  || $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'] === ''
624  ) {
625  return $items;
626  }
627 
628  $removeItems = array_flip(‪GeneralUtility::trimExplode(
629  ',',
630  $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'],
631  true
632  ));
633  foreach ($items as $key => $itemValues) {
634  if (isset($removeItems[$itemValues[1]])) {
635  unset($items[$key]);
636  }
637  }
638 
639  return $items;
640  }
641 
652  protected function ‪removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
653  {
654  // Guard clause returns if not a language field is handled
655  if (empty($result['processedTca']['ctrl']['languageField'])
656  || $result['processedTca']['ctrl']['languageField'] !== $fieldName
657  ) {
658  return $items;
659  }
660 
661  $backendUser = $this->‪getBackendUser();
662  foreach ($items as $key => $itemValues) {
663  if (!$backendUser->checkLanguageAccess($itemValues[1])) {
664  unset($items[$key]);
665  }
666  }
667 
668  return $items;
669  }
670 
681  protected function ‪removeItemsByUserAuthMode(array $result, $fieldName, array $items)
682  {
683  // Guard clause returns early if no authMode field is configured
684  if (!isset($result['processedTca']['columns'][$fieldName]['config']['authMode'])
685  || !is_string($result['processedTca']['columns'][$fieldName]['config']['authMode'])
686  ) {
687  return $items;
688  }
689 
690  $backendUser = $this->‪getBackendUser();
691  $authMode = $result['processedTca']['columns'][$fieldName]['config']['authMode'];
692  foreach ($items as $key => $itemValues) {
693  // @todo: checkAuthMode() uses $GLOBAL access for "individual" authMode - get rid of this
694  if (!$backendUser->checkAuthMode($result['tableName'], $fieldName, $itemValues[1], $authMode)) {
695  unset($items[$key]);
696  }
697  }
698 
699  return $items;
700  }
701 
712  protected function ‪removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
713  {
714  $table = $result['tableName'];
715  $backendUser = $this->‪getBackendUser();
716  // Guard clause returns if not correct table and field or if user is admin
717  if ($table !== 'pages' || $fieldName !== 'doktype' || $backendUser->isAdmin()
718  ) {
719  return $items;
720  }
721 
722  $allowedPageTypes = $backendUser->groupData['pagetypes_select'];
723  foreach ($items as $key => $itemValues) {
724  if (!GeneralUtility::inList($allowedPageTypes, $itemValues[1])) {
725  unset($items[$key]);
726  }
727  }
728 
729  return $items;
730  }
731 
742  protected function ‪removeItemsByUserStorageRestriction(array $result, $fieldName, array $items)
743  {
744  $referencedTableName = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'] ?? null;
745  if ($referencedTableName !== 'sys_file_storage') {
746  return $items;
747  }
748 
749  $allowedStorageIds = array_map(
750  function (‪ResourceStorage $storage) {
751  return $storage->‪getUid();
752  },
754  );
755 
756  return array_filter(
757  $items,
758  function (array $item) use ($allowedStorageIds) {
759  $itemValue = $item[1] ?? null;
760  return empty($itemValue)
761  || in_array((int)$itemValue, $allowedStorageIds, true);
762  }
763  );
764  }
765 
773  protected function ‪getExcludeFields()
774  {
775  $languageService = $this->‪getLanguageService();
776  $finalExcludeArray = [];
777 
778  // Fetch translations for table names
779  $tableToTranslation = [];
780  // All TCA keys
781  foreach (‪$GLOBALS['TCA'] as $table => $conf) {
782  $tableToTranslation[$table] = $languageService->sL($conf['ctrl']['title']);
783  }
785  // Sort by translations
786  asort($tableToTranslation);
787  foreach ($tableToTranslation as $table => $translatedTable) {
788  $excludeArrayTable = [];
789 
790  // All field names configured and not restricted to admins
791  if (is_array(‪$GLOBALS['TCA'][$table]['columns'])
792  && empty(‪$GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
793  && (empty(‪$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty(‪$GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
794  ) {
795  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $_) {
796  $isExcludeField = (bool)(‪$GLOBALS['TCA'][$table]['columns'][$field]['exclude'] ?? false);
797  $isOnlyVisibleForAdmins = (‪$GLOBALS['TCA'][$table]['columns'][$field]['displayCond'] ?? '') === 'HIDE_FOR_NON_ADMINS';
798  // Only show fields that can be excluded for editors, or are hidden for non-admins
799  if ($isExcludeField && !$isOnlyVisibleForAdmins) {
800  // Get human readable names of fields
801  $translatedField = $languageService->sL(‪$GLOBALS['TCA'][$table]['columns'][$field]['label']);
802  // Add entry, key 'labels' needed for sorting
803  $excludeArrayTable[] = [
804  'labels' => $translatedTable . ':' . $translatedField,
805  'sectionHeader' => $translatedTable,
806  'table' => $table,
807  'tableField' => $field,
808  'fieldName' => $field,
809  'fullField' => $field,
810  'fieldLabel' => $translatedField,
811  'origin' => 'tca',
812  ];
813  }
814  }
815  }
816  // All FlexForm fields
817  $flexFormArray = $this->‪getRegisteredFlexForms($table);
818  foreach ($flexFormArray as $tableField => $flexForms) {
819  // Prefix for field label, e.g. "Plugin Options:"
820  $labelPrefix = '';
821  if (!empty(‪$GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
822  $labelPrefix = $languageService->sL(‪$GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
823  }
824  // Get all sheets
825  foreach ($flexForms as $extIdent => ‪$extConf) {
826  // Get all fields in sheet
827  foreach (‪$extConf['sheets'] as $sheetName => $sheet) {
828  if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
829  continue;
830  }
831  foreach ($sheet['ROOT']['el'] as $pluginFieldName => $field) {
832  // Use only fields that have exclude flag set
833  if (empty($field['TCEforms']['exclude'])) {
834  continue;
835  }
836  $fieldLabel = !empty($field['TCEforms']['label'])
837  ? $languageService->sL($field['TCEforms']['label'])
838  : $pluginFieldName;
839  $excludeArrayTable[] = [
840  'labels' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ': ') . ':' . $fieldLabel,
841  'sectionHeader' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ':'),
842  'table' => $table,
843  'tableField' => $tableField,
844  'extIdent' => $extIdent,
845  'fieldName' => $pluginFieldName,
846  'fullField' => $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $pluginFieldName,
847  'fieldLabel' => $fieldLabel,
848  'origin' => 'flexForm',
849  ];
850  }
851  }
852  }
853  }
854  // Sort fields by the translated value
855  if (!empty($excludeArrayTable)) {
856  usort($excludeArrayTable, function (array $array1, array $array2) {
857  $array1 = reset($array1);
858  $array2 = reset($array2);
859  if (is_string($array1) && is_string($array2)) {
860  return strcasecmp($array1, $array2);
861  }
862  return 0;
863  });
864  $finalExcludeArray = array_merge($finalExcludeArray, $excludeArrayTable);
865  }
866  }
867 
868  return $finalExcludeArray;
869  }
870 
889  protected function ‪getRegisteredFlexForms($table)
890  {
891  if (empty($table) || empty(‪$GLOBALS['TCA'][$table]['columns'])) {
892  return [];
893  }
894  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
895  $flexForms = [];
896  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $tableField => $fieldConf) {
897  if (!empty($fieldConf['config']['type']) && !empty($fieldConf['config']['ds']) && $fieldConf['config']['type'] === 'flex') {
898  $flexForms[$tableField] = [];
899  foreach (array_keys($fieldConf['config']['ds']) as $flexFormKey) {
900  $flexFormKey = (string)$flexFormKey;
901  // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
902  $identFields = ‪GeneralUtility::trimExplode(',', $flexFormKey);
903  $extIdent = $identFields[0];
904  if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
905  $extIdent = $identFields[1];
906  }
907  $flexFormDataStructureIdentifier = json_encode([
908  'type' => 'tca',
909  'tableName' => $table,
910  'fieldName' => $tableField,
911  'dataStructureKey' => $flexFormKey,
912  ]);
913  try {
914  $dataStructure = $flexFormTools->parseDataStructureByIdentifier($flexFormDataStructureIdentifier);
915  $flexForms[$tableField][$extIdent] = $dataStructure;
916  } catch (‪InvalidIdentifierException $e) {
917  // Deliberately empty: The DS identifier is guesswork and the flex ds parser throws
918  // this exception if it can not resolve to a valid data structure. This is "ok" here
919  // and the exception is just eaten.
920  }
921  }
922  }
923  }
924  return $flexForms;
925  }
926 
933  protected function ‪getExplicitAuthFieldValues()
934  {
935  $languageService = static::getLanguageService();
936  $adLabel = [
937  'ALLOW' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.allow'),
938  'DENY' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.deny')
939  ];
940  $allowDenyOptions = [];
941  foreach (‪$GLOBALS['TCA'] as $table => $_) {
942  // All field names configured:
943  if (is_array(‪$GLOBALS['TCA'][$table]['columns'])) {
944  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $__) {
945  $fieldConfig = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
946  if ($fieldConfig['type'] === 'select' && $fieldConfig['authMode']) {
947  // Check for items
948  if (is_array($fieldConfig['items'])) {
949  // Get Human Readable names of fields and table:
950  $allowDenyOptions[$table . ':' . $field]['tableFieldLabel'] =
951  $languageService->sL(‪$GLOBALS['TCA'][$table]['ctrl']['title']) . ': '
952  . $languageService->sL(‪$GLOBALS['TCA'][$table]['columns'][$field]['label']);
953  foreach ($fieldConfig['items'] as $iVal) {
954  $itemIdentifier = (string)$iVal[1];
955  // Values '' and '--div--' are not controlled by this setting.
956  if ($itemIdentifier === '' || $itemIdentifier === '--div--') {
957  continue;
958  }
959  // Find iMode
960  $iMode = '';
961  switch ((string)$fieldConfig['authMode']) {
962  case 'explicitAllow':
963  $iMode = 'ALLOW';
964  break;
965  case 'explicitDeny':
966  $iMode = 'DENY';
967  break;
968  case 'individual':
969  if ($iVal[5] ?? false) {
970  if ($iVal[5] === 'EXPL_ALLOW') {
971  $iMode = 'ALLOW';
972  } elseif ($iVal[5] === 'EXPL_DENY') {
973  $iMode = 'DENY';
974  }
975  }
976  break;
977  }
978  // Set iMode
979  if ($iMode) {
980  $allowDenyOptions[$table . ':' . $field]['items'][$itemIdentifier] = [
981  $iMode,
982  $languageService->sL($iVal[0]),
983  $adLabel[$iMode]
984  ];
985  }
986  }
987  }
988  }
989  }
990  }
991  }
992  return $allowDenyOptions;
993  }
994 
1003  protected function ‪buildForeignTableQueryBuilder(array $result, string $localFieldName): ‪QueryBuilder
1004  {
1005  $backendUser = $this->‪getBackendUser();
1006 
1007  $foreignTableName = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table'];
1008  $foreignTableClauseArray = $this->‪processForeignTableClause($result, $foreignTableName, $localFieldName);
1009 
1010  $fieldList = ‪BackendUtility::getCommonSelectFields($foreignTableName, $foreignTableName . '.');
1012  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1013  ->getQueryBuilderForTable($foreignTableName);
1014 
1015  $queryBuilder->getRestrictions()
1016  ->removeAll()
1017  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1018  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->‪getBackendUser()->workspace));
1019 
1020  $queryBuilder
1021  ->select(...‪GeneralUtility::trimExplode(',', $fieldList, true))
1022  ->from($foreignTableName)
1023  ->where($foreignTableClauseArray['WHERE']);
1024 
1025  if (!empty($foreignTableClauseArray['GROUPBY'])) {
1026  $queryBuilder->groupBy(...$foreignTableClauseArray['GROUPBY']);
1027  }
1028 
1029  if (!empty($foreignTableClauseArray['ORDERBY'])) {
1030  foreach ($foreignTableClauseArray['ORDERBY'] as $orderPair) {
1031  [$fieldName, $order] = $orderPair;
1032  $queryBuilder->addOrderBy($fieldName, $order);
1033  }
1034  } elseif (!empty(‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['default_sortby'])) {
1035  $orderByClauses = ‪QueryHelper::parseOrderBy(‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['default_sortby']);
1036  foreach ($orderByClauses as $orderByClause) {
1037  if (!empty($orderByClause[0])) {
1038  $queryBuilder->addOrderBy($foreignTableName . '.' . $orderByClause[0], $orderByClause[1]);
1039  }
1040  }
1041  }
1042 
1043  if (!empty($foreignTableClauseArray['LIMIT'])) {
1044  if (!empty($foreignTableClauseArray['LIMIT'][1])) {
1045  $queryBuilder->setMaxResults($foreignTableClauseArray['LIMIT'][1]);
1046  $queryBuilder->setFirstResult($foreignTableClauseArray['LIMIT'][0]);
1047  } elseif (!empty($foreignTableClauseArray['LIMIT'][0])) {
1048  $queryBuilder->setMaxResults($foreignTableClauseArray['LIMIT'][0]);
1049  }
1050  }
1051 
1052  // rootLevel = -1 means that elements can be on the rootlevel OR on any page (pid!=-1)
1053  // rootLevel = 0 means that elements are not allowed on root level
1054  // rootLevel = 1 means that elements are only on the root level (pid=0)
1055  $rootLevel = 0;
1056  if (isset(‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'])) {
1057  $rootLevel = (int)‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'];
1058  }
1059 
1060  if ($rootLevel === -1) {
1061  $queryBuilder->andWhere(
1062  $queryBuilder->expr()->neq(
1063  $foreignTableName . '.pid',
1064  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1065  )
1066  );
1067  } elseif ($rootLevel === 1) {
1068  $queryBuilder->andWhere(
1069  $queryBuilder->expr()->eq(
1070  $foreignTableName . '.pid',
1071  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1072  )
1073  );
1074  } else {
1075  $queryBuilder->andWhere($backendUser->getPagePermsClause(‪Permission::PAGE_SHOW));
1076  if ($foreignTableName !== 'pages') {
1077  $queryBuilder
1078  ->from('pages')
1079  ->andWhere(
1080  $queryBuilder->expr()->eq(
1081  'pages.uid',
1082  $queryBuilder->quoteIdentifier($foreignTableName . '.pid')
1083  )
1084  );
1085  }
1086  }
1087 
1088  // @todo what about PID restriction?
1089  if ($this->‪getBackendUser()->workspace !== 0 && ‪BackendUtility::isTableWorkspaceEnabled($foreignTableName)) {
1090  $queryBuilder
1091  ->andWhere(
1092  $queryBuilder->expr()->neq(
1093  $foreignTableName . '.t3ver_state',
1094  $queryBuilder->createNamedParameter(‪VersionState::MOVE_PLACEHOLDER, \PDO::PARAM_INT)
1095  )
1096  );
1097  }
1098 
1099  return $queryBuilder;
1100  }
1101 
1118  protected function ‪processForeignTableClause(array $result, $foreignTableName, $localFieldName)
1119  {
1120  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($foreignTableName);
1121  $localTable = $result['tableName'];
1122  $effectivePid = $result['effectivePid'];
1123 
1124  $foreignTableClause = '';
1125  if (!empty($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
1126  && is_string($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
1127  ) {
1128  $foreignTableClause = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'];
1129  // Replace possible markers in query
1130  if (strpos($foreignTableClause, '###REC_FIELD_') !== false) {
1131  // " AND table.field='###REC_FIELD_field1###' AND ..." -> array(" AND table.field='", "field1###' AND ...")
1132  $whereClauseParts = explode('###REC_FIELD_', $foreignTableClause);
1133  foreach ($whereClauseParts as $key => $value) {
1134  if ($key !== 0) {
1135  // "field1###' AND ..." -> array("field1", "' AND ...")
1136  $whereClauseSubParts = explode('###', $value, 2);
1137  // @todo: Throw exception if there is no value? What happens for NEW records?
1138  $databaseRowKey = empty($result['flexParentDatabaseRow']) ? 'databaseRow' : 'flexParentDatabaseRow';
1139  $rowFieldValue = $result[$databaseRowKey][$whereClauseSubParts[0]] ?? '';
1140  if (is_array($rowFieldValue)) {
1141  // If a select or group field is used here, it may have been processed already and
1142  // is now an array containing uid + table + title + row.
1143  // See TcaGroup data provider for details.
1144  // Pick the first one (always on 0), and use uid only.
1145  $rowFieldValue = $rowFieldValue[0]['uid'] ?? $rowFieldValue[0];
1146  }
1147  if (substr($whereClauseParts[0], -1) === '\'' && $whereClauseSubParts[1][0] === '\'') {
1148  $whereClauseParts[0] = substr($whereClauseParts[0], 0, -1);
1149  $whereClauseSubParts[1] = substr($whereClauseSubParts[1], 1);
1150  }
1151  $whereClauseParts[$key] = $connection->quote($rowFieldValue) . $whereClauseSubParts[1];
1152  }
1153  }
1154  $foreignTableClause = implode('', $whereClauseParts);
1155  }
1156  if (strpos($foreignTableClause, '###CURRENT_PID###') !== false) {
1157  // Use pid from parent page clause if in flex form context
1158  if (!empty($result['flexParentDatabaseRow']['pid'])) {
1159  $effectivePid = $result['flexParentDatabaseRow']['pid'];
1160  } elseif (!$effectivePid && !empty($result['databaseRow']['pid'])) {
1161  // Use pid from database row if in inline context
1162  $effectivePid = $result['databaseRow']['pid'];
1163  }
1164  }
1165 
1166  $siteRootUid = 0;
1167  foreach ($result['rootline'] as $rootlinePage) {
1168  if (!empty($rootlinePage['is_siteroot'])) {
1169  $siteRootUid = (int)$rootlinePage['uid'];
1170  break;
1171  }
1172  }
1173 
1174  $pageTsConfigId = 0;
1175  if (isset($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'])
1176  && $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID']
1177  ) {
1178  $pageTsConfigId = (int)$result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'];
1179  }
1180 
1181  $pageTsConfigIdList = 0;
1182  if (isset($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'])
1183  && $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST']
1184  ) {
1185  $pageTsConfigIdList = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'];
1186  }
1187  $pageTsConfigIdListArray = ‪GeneralUtility::trimExplode(',', $pageTsConfigIdList, true);
1188  $pageTsConfigIdList = [];
1189  foreach ($pageTsConfigIdListArray as $pageTsConfigIdListElement) {
1190  if (‪MathUtility::canBeInterpretedAsInteger($pageTsConfigIdListElement)) {
1191  $pageTsConfigIdList[] = (int)$pageTsConfigIdListElement;
1192  }
1193  }
1194  $pageTsConfigIdList = implode(',', $pageTsConfigIdList);
1195 
1196  $pageTsConfigString = '';
1197  if (isset($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'])
1198  && $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR']
1199  ) {
1200  $pageTsConfigString = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'];
1201  $pageTsConfigString = $connection->quote($pageTsConfigString);
1202  }
1203 
1204  $foreignTableClause = str_replace(
1205  [
1206  '###CURRENT_PID###',
1207  '###THIS_UID###',
1208  '###SITEROOT###',
1209  '###PAGE_TSCONFIG_ID###',
1210  '###PAGE_TSCONFIG_IDLIST###',
1211  '\'###PAGE_TSCONFIG_STR###\'',
1212  '###PAGE_TSCONFIG_STR###'
1213  ],
1214  [
1215  (int)$effectivePid,
1216  (int)$result['databaseRow']['uid'],
1217  $siteRootUid,
1218  $pageTsConfigId,
1219  $pageTsConfigIdList,
1220  $pageTsConfigString,
1221  $pageTsConfigString
1222  ],
1223  $foreignTableClause
1224  );
1225  }
1226 
1227  // Split the clause into an array with keys WHERE, GROUPBY, ORDERBY, LIMIT
1228  // Prepend a space to make sure "[[:space:]]+" will find a space there for the first element.
1229  $foreignTableClause = ' ' . $foreignTableClause;
1230  $foreignTableClauseArray = [
1231  'WHERE' => '',
1232  'GROUPBY' => '',
1233  'ORDERBY' => '',
1234  'LIMIT' => '',
1235  ];
1236  // Find LIMIT
1237  $reg = [];
1238  if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/is', $foreignTableClause, $reg)) {
1239  $foreignTableClauseArray['LIMIT'] = ‪GeneralUtility::intExplode(',', trim($reg[2]), true);
1240  $foreignTableClause = $reg[1];
1241  }
1242  // Find ORDER BY
1243  $reg = [];
1244  if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._()"]+)$/is', $foreignTableClause, $reg)) {
1245  $foreignTableClauseArray['ORDERBY'] = ‪QueryHelper::parseOrderBy(trim($reg[2]));
1246  $foreignTableClause = $reg[1];
1247  }
1248  // Find GROUP BY
1249  $reg = [];
1250  if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._()"]+)$/is', $foreignTableClause, $reg)) {
1251  $foreignTableClauseArray['GROUPBY'] = ‪QueryHelper::parseGroupBy(trim($reg[2]));
1252  $foreignTableClause = $reg[1];
1253  }
1254  // Rest is assumed to be "WHERE" clause
1255  $foreignTableClauseArray['WHERE'] = ‪QueryHelper::stripLogicalOperatorPrefix($foreignTableClause);
1256 
1257  return $foreignTableClauseArray;
1258  }
1259 
1267  protected function ‪processDatabaseFieldValue(array $row, $fieldName)
1268  {
1269  $currentDatabaseValues = array_key_exists($fieldName, $row)
1270  ? $row[$fieldName]
1271  : '';
1272  if (!is_array($currentDatabaseValues)) {
1273  $currentDatabaseValues = ‪GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
1274  }
1275  return $currentDatabaseValues;
1276  }
1277 
1289  protected function ‪processSelectFieldValue(array $result, $fieldName, array $staticValues)
1290  {
1291  $fieldConfig = $result['processedTca']['columns'][$fieldName];
1292 
1293  $currentDatabaseValueArray = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : [];
1294  $newDatabaseValueArray = [];
1295 
1296  // Add all values that were defined by static methods and do not come from the relation
1297  // e.g. TCA, TSconfig, itemProcFunc etc.
1298  foreach ($currentDatabaseValueArray as $value) {
1299  if (isset($staticValues[$value])) {
1300  $newDatabaseValueArray[] = $value;
1301  }
1302  }
1303 
1304  if (isset($fieldConfig['config']['foreign_table']) && !empty($fieldConfig['config']['foreign_table'])) {
1306  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1307  $relationHandler->registerNonTableValues = !empty($fieldConfig['config']['allowNonIdValues']);
1308  if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') {
1309  // MM relation
1310  $relationHandler->start(
1311  implode(',', $currentDatabaseValueArray),
1312  $fieldConfig['config']['foreign_table'],
1313  $fieldConfig['config']['MM'],
1314  $result['databaseRow']['uid'],
1315  $result['tableName'],
1316  $fieldConfig['config']
1317  );
1318  $relationHandler->processDeletePlaceholder();
1319  $newDatabaseValueArray = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
1320  } else {
1321  // Non MM relation
1322  // If not dealing with MM relations, use default live uid, not versioned uid for record relations
1323  $relationHandler->start(
1324  implode(',', $currentDatabaseValueArray),
1325  $fieldConfig['config']['foreign_table'],
1326  '',
1327  $this->‪getLiveUid($result),
1328  $result['tableName'],
1329  $fieldConfig['config']
1330  );
1331  $relationHandler->processDeletePlaceholder();
1332  $databaseIds = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
1333  // remove all items from the current DB values if not available as relation or static value anymore
1334  $newDatabaseValueArray = array_values(array_intersect($currentDatabaseValueArray, $databaseIds));
1335  }
1336  }
1337 
1338  if ($fieldConfig['config']['multiple'] ?? false) {
1339  return $newDatabaseValueArray;
1340  }
1341  return array_unique($newDatabaseValueArray);
1342  }
1343 
1355  public function ‪translateLabels(array $result, array $itemArray, $table, $fieldName)
1356  {
1357  $languageService = $this->‪getLanguageService();
1358 
1359  foreach ($itemArray as $key => $item) {
1360  if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
1361  && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
1362  ) {
1363  $label = $languageService->sL($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]]);
1364  } else {
1365  $label = $languageService->sL(trim($item[0]));
1366  }
1367  $value = strlen((string)$item[1]) > 0 ? $item[1] : '';
1368  $icon = !empty($item[2]) ? $item[2] : null;
1369  $groupId = $item[3] ?? null;
1370  $helpText = null;
1371  if (!empty($item[4])) {
1372  if (\is_string($item[4])) {
1373  $helpText = $languageService->sL($item[4]);
1374  } else {
1375  $helpText = $item[4];
1376  }
1377  }
1378  $itemArray[$key] = [
1379  $label,
1380  $value,
1381  $icon,
1382  $groupId,
1383  $helpText
1384  ];
1385  }
1386 
1387  return $itemArray;
1388  }
1389 
1400  public function ‪addIconFromAltIcons(array $result, array $items, string $table, string $fieldName): array
1401  {
1402  foreach ($items as &$item) {
1403  if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altIcons.'][$item[1]])
1404  && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altIcons.'][$item[1]])
1405  ) {
1406  $item[2] = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altIcons.'][$item[1]];
1407  }
1408  }
1409 
1410  return $items;
1411  }
1412 
1424  public function ‪sanitizeItemArray($itemArray, $tableName, $fieldName)
1425  {
1426  if (!is_array($itemArray)) {
1427  $itemArray = [];
1428  }
1429  foreach ($itemArray as $item) {
1430  if (!is_array($item)) {
1431  throw new \UnexpectedValueException(
1432  'An item in field ' . $fieldName . ' of table ' . $tableName . ' is not an array as expected',
1433  1439288036
1434  );
1435  }
1436  }
1437 
1438  return $itemArray;
1439  }
1440 
1449  protected function ‪getLiveUid(array $result)
1450  {
1451  $table = $result['tableName'];
1452  $row = $result['databaseRow'];
1453  $uid = $row['uid'];
1454  if (‪BackendUtility::isTableWorkspaceEnabled($table) && (int)$row['t3ver_oid'] > 0) {
1455  $uid = $row['t3ver_oid'];
1456  }
1457  return $uid;
1458  }
1459 
1460  protected function ‪getAllSites(): array
1461  {
1462  return GeneralUtility::makeInstance(SiteFinder::class)->getAllSites();
1463  }
1464 
1468  protected function ‪getLanguageService()
1469  {
1470  return ‪$GLOBALS['LANG'];
1471  }
1472 
1476  protected function ‪getBackendUser()
1477  {
1478  return ‪$GLOBALS['BE_USER'];
1479  }
1480 }
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\sanitizeItemArray
‪array sanitizeItemArray($itemArray, $tableName, $fieldName)
Definition: AbstractItemProvider.php:1424
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseOrderBy
‪static array array[] parseOrderBy(string $input)
Definition: QueryHelper.php:44
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\buildForeignTableQueryBuilder
‪QueryBuilder buildForeignTableQueryBuilder(array $result, string $localFieldName)
Definition: AbstractItemProvider.php:1003
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\getAllSites
‪getAllSites()
Definition: AbstractItemProvider.php:1460
‪$iconRegistry
‪$iconRegistry
Definition: ext_localconf.php:43
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addItemsFromPageTsConfig
‪array addItemsFromPageTsConfig(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:140
‪TYPO3\CMS\Core\Utility\ArrayUtility\keepItemsInArray
‪static array keepItemsInArray(array $array, $keepItems, $getValueFunc=null)
Definition: ArrayUtility.php:718
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\getBackendUser
‪BackendUserAuthentication getBackendUser()
Definition: AbstractItemProvider.php:1476
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Core\Resource\ResourceStorage\getUid
‪int getUid()
Definition: ResourceStorage.php:321
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByUserLanguageFieldRestriction
‪array removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:652
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:35
‪TYPO3\CMS\Backend\Utility\BackendUtility\getCommonSelectFields
‪static string getCommonSelectFields($table, $prefix='', $fields=[])
Definition: BackendUtility.php:2136
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseGroupBy
‪static array string[] parseGroupBy(string $input)
Definition: QueryHelper.php:102
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\getLanguageService
‪LanguageService getLanguageService()
Definition: AbstractItemProvider.php:1468
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:32
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addIconFromAltIcons
‪array addIconFromAltIcons(array $result, array $items, string $table, string $fieldName)
Definition: AbstractItemProvider.php:1400
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\translateLabels
‪array translateLabels(array $result, array $itemArray, $table, $fieldName)
Definition: AbstractItemProvider.php:1355
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addItemsFromSpecial
‪array addItemsFromSpecial(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:177
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:33
‪TYPO3\CMS\Core\Versioning\VersionState\DELETE_PLACEHOLDER
‪const DELETE_PLACEHOLDER
Definition: VersionState.php:55
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addItemsFromForeignTable
‪array addItemsFromForeignTable(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:479
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication\getFileStorages
‪TYPO3 CMS Core Resource ResourceStorage[] getFileStorages()
Definition: BackendUserAuthentication.php:1784
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:24
‪TYPO3\CMS\Core\Database\Query\QueryBuilder
Definition: QueryBuilder.php:52
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableWorkspaceEnabled
‪static bool isTableWorkspaceEnabled($table)
Definition: BackendUtility.php:4021
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\processForeignTableClause
‪array processForeignTableClause(array $result, $foreignTableName, $localFieldName)
Definition: AbstractItemProvider.php:1118
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:186
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\getExcludeFields
‪array getExcludeFields()
Definition: AbstractItemProvider.php:773
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException
Definition: InvalidIdentifierException.php:22
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Core\Resource\FileRepository
Definition: FileRepository.php:33
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByUserStorageRestriction
‪array removeItemsByUserStorageRestriction(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:742
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\resolveItemProcessorFunction
‪array resolveItemProcessorFunction(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:59
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByDoktypeUserRestriction
‪array removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:712
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools
Definition: FlexFormTools.php:38
‪TYPO3\CMS\Backend\Module\ModuleLoader
Definition: ModuleLoader.php:34
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordTitle
‪static string getRecordTitle($table, $row, $prep=false, $forceResult=true)
Definition: BackendUtility.php:1541
‪TYPO3\CMS\Core\Imaging\IconRegistry
Definition: IconRegistry.php:38
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:33
‪TYPO3\CMS\Backend\Form\FormDataProvider
Definition: AbstractDatabaseRecordProvider.php:16
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByKeepItemsPageTsConfig
‪array removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:585
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\processDatabaseFieldValue
‪array processDatabaseFieldValue(array $row, $fieldName)
Definition: AbstractItemProvider.php:1267
‪TYPO3\CMS\Core\Resource\Exception
Definition: Exception.php:22
‪TYPO3\CMS\Core\Resource\ResourceStorage
Definition: ResourceStorage.php:122
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:24
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\processSelectFieldValue
‪array processSelectFieldValue(array $result, $fieldName, array $staticValues)
Definition: AbstractItemProvider.php:1289
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:165
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:24
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\getExplicitAuthFieldValues
‪array getExplicitAuthFieldValues()
Definition: AbstractItemProvider.php:933
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Backend\Utility\BackendUtility\workspaceOL
‪static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
Definition: BackendUtility.php:3586
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:988
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\getRegisteredFlexForms
‪array getRegisteredFlexForms($table)
Definition: AbstractItemProvider.php:889
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByUserAuthMode
‪array removeItemsByUserAuthMode(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:681
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\getLiveUid
‪int getLiveUid(array $result)
Definition: AbstractItemProvider.php:1449
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪$extConf
‪$extConf
Definition: ext_localconf.php:56
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Messaging\FlashMessageQueue
Definition: FlashMessageQueue.php:29
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addItemsFromFolder
‪array addItemsFromFolder(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:422
‪$icons
‪$icons
Definition: ext_localconf.php:54
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider
Definition: AbstractItemProvider.php:50
‪TYPO3\CMS\Core\Messaging\AbstractMessage\ERROR
‪const ERROR
Definition: AbstractMessage.php:31
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_PLACEHOLDER
‪const MOVE_PLACEHOLDER
Definition: VersionState.php:72
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByRemoveItemsPageTsConfig
‪array removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:618
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:39