TYPO3  7.6
AbstractItemProvider.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\Form\FormDataProvider;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
31 
36 abstract class AbstractItemProvider
37 {
46  protected function resolveItemProcessorFunction(array $result, $fieldName, array $items)
47  {
48  $table = $result['tableName'];
49  $config = $result['processedTca']['columns'][$fieldName]['config'];
50 
51  $pageTsProcessorParameters = null;
52  if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'])) {
53  $pageTsProcessorParameters = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'];
54  }
55  $processorParameters = [
56  // Function manipulates $items directly and return nothing
57  'items' => &$items,
58  'config' => $config,
59  'TSconfig' => $pageTsProcessorParameters,
60  'table' => $table,
61  'row' => $result['databaseRow'],
62  'field' => $fieldName,
63  ];
64  if (!empty($result['flexParentDatabaseRow'])) {
65  $processorParameters['flexParentDatabaseRow'] = $result['flexParentDatabaseRow'];
66  }
67 
68  try {
69  GeneralUtility::callUserFunction($config['itemsProcFunc'], $processorParameters, $this);
70  } catch (\Exception $exception) {
71  // The itemsProcFunc method may throw an exception, create a flash message if so
72  $languageService = $this->getLanguageService();
73  $fieldLabel = $fieldName;
74  if (!empty($result['processedTca']['columns'][$fieldName]['label'])) {
75  $fieldLabel = $languageService->sL($result['processedTca']['columns'][$fieldName]['label']);
76  }
77  $message = sprintf(
78  $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.items_proc_func_error'),
79  $fieldLabel,
80  $exception->getMessage()
81  );
83  $flashMessage = GeneralUtility::makeInstance(
84  FlashMessage::class,
85  $message,
86  '',
88  true
89  );
91  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
92  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
93  $defaultFlashMessageQueue->enqueue($flashMessage);
94  }
95 
96  return $items;
97  }
98 
112  protected function addItemsFromPageTsConfig(array $result, $fieldName, array $items)
113  {
114  $table = $result['tableName'];
115  if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
116  && is_array($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
117  ) {
118  $addItemsArray = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'];
119  foreach ($addItemsArray as $value => $label) {
120  // If the value ends with a dot, it is a subelement like "34.icon = mylabel.png", skip it
121  if (substr($value, -1) === '.') {
122  continue;
123  }
124  // Check if value "34 = mylabel" also has a "34.icon = myImage.png"
125  $icon = null;
126  if (isset($addItemsArray[$value . '.'])
127  && is_array($addItemsArray[$value . '.'])
128  && !empty($addItemsArray[$value . '.']['icon'])
129  ) {
130  $icon = $addItemsArray[$value . '.']['icon'];
131  }
132  $items[] = array($label, $value, $icon);
133  }
134  }
135  return $items;
136  }
137 
149  protected function addItemsFromSpecial(array $result, $fieldName, array $items)
150  {
151  // Guard
152  if (empty($result['processedTca']['columns'][$fieldName]['config']['special'])
153  || !is_string($result['processedTca']['columns'][$fieldName]['config']['special'])
154  ) {
155  return $items;
156  }
157 
158  $languageService = $this->getLanguageService();
159  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
160 
161  $special = $result['processedTca']['columns'][$fieldName]['config']['special'];
162  switch (true) {
163  case ($special === 'tables'):
164  foreach ($GLOBALS['TCA'] as $currentTable => $_) {
165  if (!empty($GLOBALS['TCA'][$currentTable]['ctrl']['adminOnly'])) {
166  // Hide "admin only" tables
167  continue;
168  }
169  $label = !empty($GLOBALS['TCA'][$currentTable]['ctrl']['title']) ? $GLOBALS['TCA'][$currentTable]['ctrl']['title'] : '';
170  $icon = $iconFactory->mapRecordTypeToIconIdentifier($currentTable, []);
171  $helpText = [];
172  $languageService->loadSingleTableDescription($currentTable);
173  // @todo: check if this actually works, currently help texts are missing
174  $helpTextArray = $GLOBALS['TCA_DESCR'][$currentTable]['columns'][''];
175  if (!empty($helpTextArray['description'])) {
176  $helpText['description'] = $helpTextArray['description'];
177  }
178  $items[] = [$label, $currentTable, $icon, $helpText];
179  }
180  break;
181  case ($special === 'pagetypes'):
182  if (isset($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
183  && is_array($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
184  ) {
185  $specialItems = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
186  foreach ($specialItems as $specialItem) {
187  if (!is_array($specialItem) || $specialItem[1] === '--div--') {
188  // Skip non arrays and divider items
189  continue;
190  }
191  $label = $specialItem[0];
192  $value = $specialItem[1];
193  $icon = $iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $specialItem[1]]);
194  $items[] = [$label, $value, $icon];
195  }
196  }
197  break;
198  case ($special === 'exclude'):
199  $excludeArrays = $this->getExcludeFields();
200  foreach ($excludeArrays as $excludeArray) {
201  // If the field comes from a FlexForm, the syntax is more complex
202  if ($excludeArray['origin'] === 'flexForm') {
203  // The field comes from a plugins FlexForm
204  // Add header if not yet set for plugin section
205  if (!isset($items[$excludeArray['sectionHeader']])) {
206  // there is no icon handling for plugins - we take the icon from the table
207  $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], array());
208  $items[$excludeArray['sectionHeader']] = [
209  $excludeArray['sectionHeader'],
210  '--div--',
211  $icon
212  ];
213  }
214  } else {
215  // Add header if not yet set for table
216  if (!isset($items[$excludeArray['table']])) {
217  $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], array());
218  $items[$excludeArray['table']] = [
219  $GLOBALS['TCA'][$excludeArray['table']]['ctrl']['title'],
220  '--div--',
221  $icon
222  ];
223  }
224  }
225  // Add help text
226  $helpText = [];
227  $languageService->loadSingleTableDescription($excludeArray['table']);
228  $helpTextArray = $GLOBALS['TCA_DESCR'][$excludeArray['table']]['columns'][$excludeArray['table']];
229  if (!empty($helpTextArray['description'])) {
230  $helpText['description'] = $helpTextArray['description'];
231  }
232  // Item configuration:
233  $items[] = array(
234  rtrim($excludeArray['origin'] === 'flexForm' ? $excludeArray['fieldLabel'] : $languageService->sL($GLOBALS['TCA'][$excludeArray['table']]['columns'][$excludeArray['fieldName']]['label']), ':') . ' (' . $excludeArray['fieldName'] . ')',
235  $excludeArray['table'] . ':' . $excludeArray['fullField'] ,
236  'empty-empty',
237  $helpText
238  );
239  }
240  break;
241  case ($special === 'explicitValues'):
242  $theTypes = $this->getExplicitAuthFieldValues();
243  $icons = [
244  'ALLOW' => 'status-status-permission-granted',
245  'DENY' => 'status-status-permission-denied'
246  ];
247  // Traverse types:
248  foreach ($theTypes as $tableFieldKey => $theTypeArrays) {
249  if (is_array($theTypeArrays['items'])) {
250  // Add header:
251  $items[] = [
252  $theTypeArrays['tableFieldLabel'],
253  '--div--',
254  ];
255  // Traverse options for this field:
256  foreach ($theTypeArrays['items'] as $itemValue => $itemContent) {
257  // Add item to be selected:
258  $items[] = [
259  '[' . $itemContent[2] . '] ' . $itemContent[1],
260  $tableFieldKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $itemContent[0],
261  $icons[$itemContent[0]]
262  ];
263  }
264  }
265  }
266  break;
267  case ($special === 'languages'):
268  foreach ($result['systemLanguageRows'] as $language) {
269  if ($language['uid'] !== -1) {
270  $items[] = [
271  0 => $language['title'] . ' [' . $language['uid'] . ']',
272  1 => $language['uid'],
273  2 => $language['flagIconIdentifier']
274  ];
275  }
276  }
277  break;
278  case ($special === 'custom'):
279  $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'];
280  if (is_array($customOptions)) {
281  foreach ($customOptions as $coKey => $coValue) {
282  if (is_array($coValue['items'])) {
283  // Add header:
284  $items[] = [
285  $languageService->sL($coValue['header']),
286  '--div--'
287  ];
288  // Traverse items:
289  foreach ($coValue['items'] as $itemKey => $itemCfg) {
290  $icon = 'empty-empty';
291  $helpText = [];
292  if (!empty($itemCfg[2])) {
293  $helpText['description'] = $languageService->sL($itemCfg[2]);
294  }
295  $items[] = [
296  $languageService->sL($itemCfg[0]),
297  $coKey . ':' . preg_replace('/[:|,]/', '', $itemKey),
298  $icon,
299  $helpText
300  ];
301  }
302  }
303  }
304  }
305  break;
306  case ($special === 'modListGroup' || $special === 'modListUser'):
307  $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
308  $loadModules->load($GLOBALS['TBE_MODULES']);
309  $modList = $special === 'modListUser' ? $loadModules->modListUser : $loadModules->modListGroup;
310  if (is_array($modList)) {
311  foreach ($modList as $theMod) {
312  // Icon:
313  $icon = $languageService->moduleLabels['tabs_images'][$theMod . '_tab'];
314  if ($icon) {
315  $icon = '../' . PathUtility::stripPathSitePrefix($icon);
316  }
317  // Add help text
318  $helpText = [
319  'title' => $languageService->moduleLabels['labels'][$theMod . '_tablabel'],
320  'description' => $languageService->moduleLabels['labels'][$theMod . '_tabdescr']
321  ];
322 
323  $label = '';
324  // Add label for main module:
325  $pp = explode('_', $theMod);
326  if (count($pp) > 1) {
327  $label .= $languageService->moduleLabels['tabs'][($pp[0] . '_tab')] . '>';
328  }
329  // Add modules own label now:
330  $label .= $languageService->moduleLabels['tabs'][$theMod . '_tab'];
331 
332  // Item configuration:
333  $items[] = [$label, $theMod, $icon, $helpText];
334  }
335  }
336  break;
337  default:
338  throw new \UnexpectedValueException(
339  'Unknown special value ' . $special . ' for field ' . $fieldName . ' of table ' . $result['tableName'],
340  1439298496
341  );
342  }
343  return $items;
344  }
345 
356  protected function addItemsFromFolder(array $result, $fieldName, array $items)
357  {
358  if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
359  || !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
360  ) {
361  return $items;
362  }
363 
364  $fileFolder = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
365  $fileFolder = GeneralUtility::getFileAbsFileName($fileFolder);
366  $fileFolder = rtrim($fileFolder, '/') . '/';
367 
368  if (@is_dir($fileFolder)) {
369  $fileExtensionList = '';
370  if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
371  && is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
372  ) {
373  $fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
374  }
375  $recursionLevels = isset($fieldValue['config']['fileFolder_recursions'])
376  ? MathUtility::forceIntegerInRange($fieldValue['config']['fileFolder_recursions'], 0, 99)
377  : 99;
378  $fileArray = GeneralUtility::getAllFilesAndFoldersInPath([], $fileFolder, $fileExtensionList, 0, $recursionLevels);
379  $fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
380  foreach ($fileArray as $fileReference) {
381  $fileInformation = pathinfo($fileReference);
382  $icon = GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($fileInformation['extension']))
383  ? '../' . PathUtility::stripPathSitePrefix($fileFolder) . $fileReference
384  : '';
385  $items[] = [
386  $fileReference,
387  $fileReference,
388  $icon
389  ];
390  }
391  }
392 
393  return $items;
394  }
395 
406  protected function addItemsFromForeignTable(array $result, $fieldName, array $items)
407  {
408  // Guard
409  if (empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
410  || !is_string($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
411  ) {
412  return $items;
413  }
414 
415  $languageService = $this->getLanguageService();
416  $database = $this->getDatabaseConnection();
417 
418  $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
419  $foreignTableQueryArray = $this->buildForeignTableQuery($result, $fieldName);
420  $queryResource = $database->exec_SELECT_queryArray($foreignTableQueryArray);
421 
422  // Early return on error with flash message
423  $databaseError = $database->sql_error();
424  if (!empty($databaseError)) {
425  $msg = htmlspecialchars($databaseError) . '<br />' . LF;
426  $msg .= $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch');
427  $msgTitle = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch_title');
429  $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, true);
431  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
433  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
434  $defaultFlashMessageQueue->enqueue($flashMessage);
435  $database->sql_free_result($queryResource);
436  return $items;
437  }
438 
439  $labelPrefix = '';
440  if (!empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'])) {
441  $labelPrefix = $result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'];
442  $labelPrefix = $languageService->sL($labelPrefix);
443  }
444  $iconFieldName = '';
445  if (!empty($result['processedTca']['ctrl']['selicon_field'])) {
446  $iconFieldName = $result['processedTca']['ctrl']['selicon_field'];
447  }
448  $iconPath = '';
449  if (!empty($result['processedTca']['ctrl']['selicon_field_path'])) {
450  $iconPath = $result['processedTca']['ctrl']['selicon_field_path'];
451  }
452 
453  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
454 
455  while ($foreignRow = $database->sql_fetch_assoc($queryResource)) {
456  BackendUtility::workspaceOL($foreignTable, $foreignRow);
457  if (is_array($foreignRow)) {
458  // Prepare the icon if available:
459  if ($iconFieldName && $iconPath && $foreignRow[$iconFieldName]) {
460  $iParts = GeneralUtility::trimExplode(',', $foreignRow[$iconFieldName], true);
461  $icon = '../' . $iconPath . '/' . trim($iParts[0]);
462  } else {
463  $icon = $iconFactory->mapRecordTypeToIconIdentifier($foreignTable, $foreignRow);
464  }
465  // Add the item
466  $items[] = [
467  $labelPrefix . htmlspecialchars(BackendUtility::getRecordTitle($foreignTable, $foreignRow)),
468  $foreignRow['uid'],
469  $icon
470  ];
471  }
472  }
473 
474  $database->sql_free_result($queryResource);
475 
476  return $items;
477  }
478 
489  protected function removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
490  {
491  $table = $result['tableName'];
492  if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
493  || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
494  ) {
495  return $items;
496  }
497 
498  // If keepItems is set but is an empty list all current items get removed
499  if (empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
500  && $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'] !== '0') {
501  return [];
502  }
503 
505  $items,
506  $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'],
507  function ($value) {
508  return $value[1];
509  }
510  );
511  }
512 
523  protected function removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
524  {
525  $table = $result['tableName'];
526  if (empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
527  || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
528  ) {
529  return $items;
530  }
531 
532  $removeItems = GeneralUtility::trimExplode(
533  ',',
534  $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'],
535  true
536  );
537  foreach ($items as $key => $itemValues) {
538  if (in_array($itemValues[1], $removeItems)) {
539  unset($items[$key]);
540  }
541  }
542 
543  return $items;
544  }
545 
556  protected function removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
557  {
558  // Guard clause returns if not a language field is handled
559  if (empty($result['processedTca']['ctrl']['languageField'])
560  || $result['processedTca']['ctrl']['languageField'] !== $fieldName
561  ) {
562  return $items;
563  }
564 
565  $backendUser = $this->getBackendUser();
566  foreach ($items as $key => $itemValues) {
567  if (!$backendUser->checkLanguageAccess($itemValues[1])) {
568  unset($items[$key]);
569  }
570  }
571 
572  return $items;
573  }
574 
585  protected function removeItemsByUserAuthMode(array $result, $fieldName, array $items)
586  {
587  // Guard clause returns early if no authMode field is configured
588  if (!isset($result['processedTca']['columns'][$fieldName]['config']['authMode'])
589  || !is_string($result['processedTca']['columns'][$fieldName]['config']['authMode'])
590  ) {
591  return $items;
592  }
593 
594  $backendUser = $this->getBackendUser();
595  $authMode = $result['processedTca']['columns'][$fieldName]['config']['authMode'];
596  foreach ($items as $key => $itemValues) {
597  // @todo: checkAuthMode() uses $GLOBAL access for "individual" authMode - get rid of this
598  if (!$backendUser->checkAuthMode($result['tableName'], $fieldName, $itemValues[1], $authMode)) {
599  unset($items[$key]);
600  }
601  }
602 
603  return $items;
604  }
605 
616  protected function removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
617  {
618  $table = $result['tableName'];
619  $backendUser = $this->getBackendUser();
620  // Guard clause returns if not correct table and field or if user is admin
621  if ($table !== 'pages' && $table !== 'pages_language_overlay'
622  || $fieldName !== 'doktype' || $backendUser->isAdmin()
623  ) {
624  return $items;
625  }
626 
627  $allowedPageTypes = $backendUser->groupData['pagetypes_select'];
628  foreach ($items as $key => $itemValues) {
629  if (!GeneralUtility::inList($allowedPageTypes, $itemValues[1])) {
630  unset($items[$key]);
631  }
632  }
633 
634  return $items;
635  }
636 
644  protected function getExcludeFields()
645  {
646  $languageService = $this->getLanguageService();
647  $finalExcludeArray = [];
648 
649  // Fetch translations for table names
650  $tableToTranslation = [];
651  // All TCA keys
652  foreach ($GLOBALS['TCA'] as $table => $conf) {
653  $tableToTranslation[$table] = $languageService->sL($conf['ctrl']['title']);
654  }
655  // Sort by translations
656  asort($tableToTranslation);
657  foreach ($tableToTranslation as $table => $translatedTable) {
658  $excludeArrayTable = [];
659 
660  // All field names configured and not restricted to admins
661  if (is_array($GLOBALS['TCA'][$table]['columns'])
662  && empty($GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
663  && (empty($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
664  ) {
665  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
666  if ($GLOBALS['TCA'][$table]['columns'][$field]['exclude']) {
667  // Get human readable names of fields
668  $translatedField = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$field]['label']);
669  // Add entry, key 'labels' needed for sorting
670  $excludeArrayTable[] = [
671  'labels' => $translatedTable . ':' . $translatedField,
672  'sectionHeader' => $translatedTable,
673  'table' => $table,
674  'tableField' => $field,
675  'fieldName' => $field,
676  'fullField' => $field,
677  'fieldLabel' => $translatedField,
678  'origin' => 'tca',
679  ];
680  }
681  }
682  }
683  // All FlexForm fields
684  $flexFormArray = $this->getRegisteredFlexForms($table);
685  foreach ($flexFormArray as $tableField => $flexForms) {
686  // Prefix for field label, e.g. "Plugin Options:"
687  $labelPrefix = '';
688  if (!empty($GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
689  $labelPrefix = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
690  }
691  // Get all sheets and title
692  foreach ($flexForms as $extIdent => $extConf) {
693  $extTitle = $languageService->sl(trim($extConf['title']));
694  // Get all fields in sheet
695  foreach ($extConf['ds']['sheets'] as $sheetName => $sheet) {
696  if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
697  continue;
698  }
699  foreach ($sheet['ROOT']['el'] as $pluginFieldName => $field) {
700  // Use only fields that have exclude flag set
701  if (empty($field['TCEforms']['exclude'])) {
702  continue;
703  }
704  $fieldLabel = !empty($field['TCEforms']['label'])
705  ? $languageService->sL($field['TCEforms']['label'])
706  : $pluginFieldName;
707  $excludeArrayTable[] = [
708  'labels' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ': ') . ':' . $fieldLabel,
709  'sectionHeader' => trim(($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent), ':'),
710  'table' => $table,
711  'tableField' => $tableField,
712  'extIdent' => $extIdent,
713  'fieldName' => $pluginFieldName,
714  'fullField' => $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $pluginFieldName,
715  'fieldLabel' => $fieldLabel,
716  'origin' => 'flexForm',
717  ];
718  }
719  }
720  }
721  }
722  // Sort fields by the translated value
723  if (!empty($excludeArrayTable)) {
724  usort($excludeArrayTable, function (array $array1, array $array2) {
725  $array1 = reset($array1);
726  $array2 = reset($array2);
727  if (is_string($array1) && is_string($array2)) {
728  return strcasecmp($array1, $array2);
729  }
730  return 0;
731  });
732  $finalExcludeArray = array_merge($finalExcludeArray, $excludeArrayTable);
733  }
734  }
735 
736  return $finalExcludeArray;
737  }
738 
745  protected function getRegisteredFlexForms($table)
746  {
747  if (empty($table) || empty($GLOBALS['TCA'][$table]['columns'])) {
748  return [];
749  }
750  $flexForms = [];
751  foreach ($GLOBALS['TCA'][$table]['columns'] as $tableField => $fieldConf) {
752  if (!empty($fieldConf['config']['type']) && !empty($fieldConf['config']['ds']) && $fieldConf['config']['type'] == 'flex') {
753  $flexForms[$tableField] = [];
754  // Get pointer fields
755  $pointerFields = !empty($fieldConf['config']['ds_pointerField']) ? $fieldConf['config']['ds_pointerField'] : 'list_type,CType,default';
756  $pointerFields = GeneralUtility::trimExplode(',', $pointerFields);
757  // Get FlexForms
758  foreach ($fieldConf['config']['ds'] as $flexFormKey => $dataStructure) {
759  // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
760  $identFields = GeneralUtility::trimExplode(',', $flexFormKey);
761  $extIdent = $identFields[0];
762  if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
763  $extIdent = $identFields[1];
764  }
765  // Load external file references
766  if (!is_array($dataStructure)) {
767  $file = GeneralUtility::getFileAbsFileName(str_ireplace('FILE:', '', $dataStructure));
768  if ($file && @is_file($file)) {
769  $dataStructure = GeneralUtility::getUrl($file);
770  }
771  $dataStructure = GeneralUtility::xml2array($dataStructure);
772  if (!is_array($dataStructure)) {
773  continue;
774  }
775  }
776  // Get flexform content
777  $dataStructure = GeneralUtility::resolveAllSheetsInDS($dataStructure);
778  if (empty($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
779  continue;
780  }
781  // Use DS pointer to get extension title from TCA
782  // @todo: I don't understand this code ... does it make sense at all?
783  $title = $extIdent;
784  $keyFields = GeneralUtility::trimExplode(',', $flexFormKey);
785  foreach ($pointerFields as $pointerKey => $pointerName) {
786  if (empty($keyFields[$pointerKey])
787  || $keyFields[$pointerKey] === '*'
788  || $keyFields[$pointerKey] === 'list'
789  || $keyFields[$pointerKey] === 'default'
790  ) {
791  continue;
792  }
793  if (!empty($GLOBALS['TCA'][$table]['columns'][$pointerName]['config']['items'])) {
794  $items = $GLOBALS['TCA'][$table]['columns'][$pointerName]['config']['items'];
795  if (!is_array($items)) {
796  continue;
797  }
798  foreach ($items as $itemConf) {
799  if (!empty($itemConf[0]) && !empty($itemConf[1]) && $itemConf[1] == $keyFields[$pointerKey]) {
800  $title = $itemConf[0];
801  break 2;
802  }
803  }
804  }
805  }
806  $flexForms[$tableField][$extIdent] = [
807  'title' => $title,
808  'ds' => $dataStructure
809  ];
810  }
811  }
812  }
813  return $flexForms;
814  }
815 
822  protected function getExplicitAuthFieldValues()
823  {
824  $languageService = static::getLanguageService();
825  $adLabel = [
826  'ALLOW' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.allow'),
827  'DENY' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.deny')
828  ];
829  $allowDenyOptions = [];
830  foreach ($GLOBALS['TCA'] as $table => $_) {
831  // All field names configured:
832  if (is_array($GLOBALS['TCA'][$table]['columns'])) {
833  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $__) {
834  $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
835  if ($fieldConfig['type'] === 'select' && $fieldConfig['authMode']) {
836  // Check for items
837  if (is_array($fieldConfig['items'])) {
838  // Get Human Readable names of fields and table:
839  $allowDenyOptions[$table . ':' . $field]['tableFieldLabel'] =
840  $languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title']) . ': '
841  . $languageService->sL($GLOBALS['TCA'][$table]['columns'][$field]['label']);
842  foreach ($fieldConfig['items'] as $iVal) {
843  // Values '' is not controlled by this setting.
844  if ((string)$iVal[1] !== '') {
845  // Find iMode
846  $iMode = '';
847  switch ((string)$fieldConfig['authMode']) {
848  case 'explicitAllow':
849  $iMode = 'ALLOW';
850  break;
851  case 'explicitDeny':
852  $iMode = 'DENY';
853  break;
854  case 'individual':
855  if ($iVal[4] === 'EXPL_ALLOW') {
856  $iMode = 'ALLOW';
857  } elseif ($iVal[4] === 'EXPL_DENY') {
858  $iMode = 'DENY';
859  }
860  break;
861  }
862  // Set iMode
863  if ($iMode) {
864  $allowDenyOptions[$table . ':' . $field]['items'][$iVal[1]] = [
865  $iMode,
866  $languageService->sL($iVal[0]),
867  $adLabel[$iMode]
868  ];
869  }
870  }
871  }
872  }
873  }
874  }
875  }
876  }
877  return $allowDenyOptions;
878  }
879 
888  protected function buildForeignTableQuery(array $result, $localFieldName)
889  {
890  $backendUser = $this->getBackendUser();
891 
892  $foreignTableName = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table'];
893 
894  if (!is_array($GLOBALS['TCA'][$foreignTableName])) {
895  throw new \UnexpectedValueException(
896  'Field ' . $localFieldName . ' of table ' . $result['tableName'] . ' reference to foreign table '
897  . $foreignTableName . ', but this table is not defined in TCA',
898  1439569743
899  );
900  }
901 
902  $foreignTableClauseArray = $this->processForeignTableClause($result, $foreignTableName, $localFieldName);
903 
904  $queryArray = [];
905  $queryArray['SELECT'] = BackendUtility::getCommonSelectFields($foreignTableName, $foreignTableName . '.');
906 
907  // rootLevel = -1 means that elements can be on the rootlevel OR on any page (pid!=-1)
908  // rootLevel = 0 means that elements are not allowed on root level
909  // rootLevel = 1 means that elements are only on the root level (pid=0)
910  $rootLevel = 0;
911  if (isset($GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'])) {
912  $rootLevel = $GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'];
913  }
914  $deleteClause = BackendUtility::deleteClause($foreignTableName);
915  if ($rootLevel == 1 || $rootLevel == -1) {
916  $pidWhere = $foreignTableName . '.pid' . (($rootLevel == -1) ? '<>-1' : '=0');
917  $queryArray['FROM'] = $foreignTableName;
918  $queryArray['WHERE'] = $pidWhere . $deleteClause . $foreignTableClauseArray['WHERE'];
919  } else {
920  $pageClause = $backendUser->getPagePermsClause(1);
921  if ($foreignTableName === 'pages') {
922  $queryArray['FROM'] = 'pages';
923  $queryArray['WHERE'] = '1=1' . $deleteClause . ' AND' . $pageClause . $foreignTableClauseArray['WHERE'];
924  } else {
925  $queryArray['FROM'] = $foreignTableName . ', pages';
926  $queryArray['WHERE'] = 'pages.uid=' . $foreignTableName . '.pid AND pages.deleted=0'
927  . $deleteClause . ' AND' . $pageClause . $foreignTableClauseArray['WHERE'];
928  }
929  }
930 
931  $queryArray['GROUPBY'] = $foreignTableClauseArray['GROUPBY'];
932  $queryArray['ORDERBY'] = $foreignTableClauseArray['ORDERBY'];
933  $queryArray['LIMIT'] = $foreignTableClauseArray['LIMIT'];
934 
935  return $queryArray;
936  }
937 
954  protected function processForeignTableClause(array $result, $foreignTableName, $localFieldName)
955  {
956  $database = $this->getDatabaseConnection();
957  $localTable = $result['tableName'];
958 
959  $foreignTableClause = '';
960  if (!empty($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
961  && is_string($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
962  ) {
963  $foreignTableClause = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'];
964  // Replace possible markers in query
965  if (strstr($foreignTableClause, '###REC_FIELD_')) {
966  // " AND table.field='###REC_FIELD_field1###' AND ..." -> array(" AND table.field='", "field1###' AND ...")
967  $whereClauseParts = explode('###REC_FIELD_', $foreignTableClause);
968  foreach ($whereClauseParts as $key => $value) {
969  if ($key !== 0) {
970  // "field1###' AND ..." -> array("field1", "' AND ...")
971  $whereClauseSubParts = explode('###', $value, 2);
972  // @todo: Throw exception if there is no value? What happens for NEW records?
973  $rowFieldValue = $result['databaseRow'][$whereClauseSubParts[0]];
974  if (is_array($rowFieldValue)) {
975  // If a select or group field is used here, it may have been processed already and
976  // is now an array. Use first selected value in this case.
977  $rowFieldValue = $rowFieldValue[0];
978  }
979  if (substr($whereClauseParts[0], -1) === '\'' && $whereClauseSubParts[1][0] === '\'') {
980  $whereClauseParts[$key] = $database->quoteStr($rowFieldValue, $foreignTableName) . $whereClauseSubParts[1];
981  } else {
982  $whereClauseParts[$key] = $database->fullQuoteStr($rowFieldValue, $foreignTableName) . $whereClauseSubParts[1];
983  }
984  }
985  }
986  $foreignTableClause = implode('', $whereClauseParts);
987  }
988 
989  $siteRootUid = 0;
990  foreach ($result['rootline'] as $rootlinePage) {
991  if (!empty($rootlinePage['is_siteroot'])) {
992  $siteRootUid = (int)$rootlinePage['uid'];
993  break;
994  }
995  }
996  $pageTsConfigId = 0;
997  if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID']) {
998  $pageTsConfigId = (int)$result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'];
999  }
1000  $pageTsConfigIdList = 0;
1001  if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST']) {
1002  $pageTsConfigIdList = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'];
1003  $pageTsConfigIdListArray = GeneralUtility::trimExplode(',', $pageTsConfigIdList, true);
1004  $pageTsConfigIdList = [];
1005  foreach ($pageTsConfigIdListArray as $pageTsConfigIdListElement) {
1006  if (MathUtility::canBeInterpretedAsInteger($pageTsConfigIdListElement)) {
1007  $pageTsConfigIdList[] = (int)$pageTsConfigIdListElement;
1008  }
1009  }
1010  $pageTsConfigIdList = implode(',', $pageTsConfigIdList);
1011  }
1012  $pageTsConfigString = '';
1013  if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR']) {
1014  $pageTsConfigString = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'];
1015  $pageTsConfigString = $database->quoteStr($pageTsConfigString, $foreignTableName);
1016  }
1017 
1018  $foreignTableClause = str_replace(
1019  [
1020  '###CURRENT_PID###',
1021  '###THIS_UID###',
1022  '###SITEROOT###',
1023  '###PAGE_TSCONFIG_ID###',
1024  '###PAGE_TSCONFIG_IDLIST###',
1025  '###PAGE_TSCONFIG_STR###'
1026  ],
1027  [
1028  (int)$result['effectivePid'],
1029  (int)$result['databaseRow']['uid'],
1030  $siteRootUid,
1031  $pageTsConfigId,
1032  $pageTsConfigIdList,
1033  $pageTsConfigString
1034  ],
1035  $foreignTableClause
1036  );
1037  }
1038 
1039  // Split the clause into an array with keys WHERE, GROUPBY, ORDERBY, LIMIT
1040  // Prepend a space to make sure "[[:space:]]+" will find a space there for the first element.
1041  $foreignTableClause = ' ' . $foreignTableClause;
1042  $foreignTableClauseArray = [
1043  'WHERE' => '',
1044  'GROUPBY' => '',
1045  'ORDERBY' => '',
1046  'LIMIT' => '',
1047  ];
1048  // Find LIMIT
1049  $reg = [];
1050  if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
1051  $foreignTableClauseArray['LIMIT'] = trim($reg[2]);
1052  $foreignTableClause = $reg[1];
1053  }
1054  // Find ORDER BY
1055  $reg = [];
1056  if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
1057  $foreignTableClauseArray['ORDERBY'] = trim($reg[2]);
1058  $foreignTableClause = $reg[1];
1059  }
1060  // Find GROUP BY
1061  $reg = [];
1062  if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
1063  $foreignTableClauseArray['GROUPBY'] = trim($reg[2]);
1064  $foreignTableClause = $reg[1];
1065  }
1066  // Rest is assumed to be "WHERE" clause
1067  $foreignTableClauseArray['WHERE'] = $foreignTableClause;
1068 
1069  return $foreignTableClauseArray;
1070  }
1071 
1079  protected function processDatabaseFieldValue(array $row, $fieldName)
1080  {
1081  $currentDatabaseValues = array_key_exists($fieldName, $row)
1082  ? $row[$fieldName]
1083  : '';
1084  return GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
1085  }
1086 
1098  protected function processSelectFieldValue(array $result, $fieldName, array $staticValues)
1099  {
1100  $fieldConfig = $result['processedTca']['columns'][$fieldName];
1101 
1102  $currentDatabaseValueArray = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : [];
1103  $newDatabaseValueArray = [];
1104 
1105  // Add all values that were defined by static methods and do not come from the relation
1106  // e.g. TCA, TSconfig, itemProcFunc etc.
1107  foreach ($currentDatabaseValueArray as $value) {
1108  if (isset($staticValues[$value])) {
1109  $newDatabaseValueArray[] = $value;
1110  }
1111  }
1112 
1113  if (isset($fieldConfig['config']['foreign_table']) && !empty($fieldConfig['config']['foreign_table'])) {
1115  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1116  $relationHandler->registerNonTableValues = !empty($fieldConfig['config']['allowNonIdValues']);
1117  if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') {
1118  // MM relation
1119  $relationHandler->start(
1120  implode(',', $currentDatabaseValueArray),
1121  $fieldConfig['config']['foreign_table'],
1122  $fieldConfig['config']['MM'],
1123  $result['databaseRow']['uid'],
1124  $result['tableName'],
1125  $fieldConfig['config']
1126  );
1127  } else {
1128  // Non MM relation
1129  // If not dealing with MM relations, use default live uid, not versioned uid for record relations
1130  $relationHandler->start(
1131  implode(',', $currentDatabaseValueArray),
1132  $fieldConfig['config']['foreign_table'],
1133  '',
1134  $this->getLiveUid($result),
1135  $result['tableName'],
1136  $fieldConfig['config']
1137  );
1138  }
1139  $newDatabaseValueArray = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
1140  }
1141 
1142  return array_unique($newDatabaseValueArray);
1143  }
1144 
1156  public function translateLabels(array $result, array $itemArray, $table, $fieldName)
1157  {
1158  $languageService = $this->getLanguageService();
1159 
1160  foreach ($itemArray as $key => $item) {
1161  if (!isset($dynamicItems[$key])) {
1162  $staticValues[$item[1]] = $item;
1163  }
1164  if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
1165  && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
1166  ) {
1167  $label = $languageService->sL($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]]);
1168  } else {
1169  $label = $languageService->sL(trim($item[0]));
1170  }
1171  $value = strlen((string)$item[1]) > 0 ? $item[1] : '';
1172  $icon = $item[2] ?: null;
1173  $helpText = $item[3] ?: null;
1174  $itemArray[$key] = [
1175  $label,
1176  $value,
1177  $icon,
1178  $helpText
1179  ];
1180  }
1181 
1182  return $itemArray;
1183  }
1184 
1196  public function sanitizeItemArray($itemArray, $tableName, $fieldName)
1197  {
1198  if (!is_array($itemArray)) {
1199  $itemArray = [];
1200  }
1201  foreach ($itemArray as $item) {
1202  if (!is_array($item)) {
1203  throw new \UnexpectedValueException(
1204  'An item in field ' . $fieldName . ' of table ' . $tableName . ' is not an array as expected',
1205  1439288036
1206  );
1207  }
1208  }
1209 
1210  return $itemArray;
1211  }
1212 
1221  public function sanitizeMaxItems($maxItems)
1222  {
1223  if (!empty($maxItems)
1224  && (int)$maxItems > 1
1225  ) {
1226  $maxItems = (int)$maxItems;
1227  } else {
1228  $maxItems = 1;
1229  }
1230 
1231  return $maxItems;
1232  }
1233 
1242  protected function getLiveUid(array $result)
1243  {
1244  $table = $result['tableName'];
1245  $row = $result['databaseRow'];
1246  $uid = $row['uid'];
1247  if (!empty($result['processedTca']['ctrl']['versioningWS'])
1248  && $result['pid'] === -1
1249  ) {
1250  if (empty($row['t3ver_oid'])) {
1251  throw new \UnexpectedValueException(
1252  'No t3ver_oid found for record ' . $row['uid'] . ' on table ' . $table,
1253  1440066481
1254  );
1255  }
1256  $uid = $row['t3ver_oid'];
1257  }
1258  return $uid;
1259  }
1260 
1270  public function getStaticValues($itemArray, $dynamicItemArray)
1271  {
1272  $staticValues = [];
1273  foreach ($itemArray as $key => $item) {
1274  if (!isset($dynamicItemArray[$key])) {
1275  $staticValues[$item[1]] = $item;
1276  }
1277  }
1278  return $staticValues;
1279  }
1280 
1284  protected function getLanguageService()
1285  {
1286  return $GLOBALS['LANG'];
1287  }
1288 
1292  protected function getDatabaseConnection()
1293  {
1294  return $GLOBALS['TYPO3_DB'];
1295  }
1296 
1300  protected function getBackendUser()
1301  {
1302  return $GLOBALS['BE_USER'];
1303  }
1304 }