[CLEANUP] Use Permission constants consistently
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / AbstractItemProvider.php
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
17 use Doctrine\DBAL\DBALException;
18 use TYPO3\CMS\Backend\Module\ModuleLoader;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException;
22 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
23 use TYPO3\CMS\Core\Database\ConnectionPool;
24 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
25 use TYPO3\CMS\Core\Database\Query\QueryHelper;
26 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
27 use TYPO3\CMS\Core\Database\RelationHandler;
28 use TYPO3\CMS\Core\Imaging\Icon;
29 use TYPO3\CMS\Core\Imaging\IconFactory;
30 use TYPO3\CMS\Core\Imaging\IconRegistry;
31 use TYPO3\CMS\Core\Localization\LanguageService;
32 use TYPO3\CMS\Core\Messaging\FlashMessage;
33 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
34 use TYPO3\CMS\Core\Messaging\FlashMessageService;
35 use TYPO3\CMS\Core\Resource\FileRepository;
36 use TYPO3\CMS\Core\Type\Bitmask\Permission;
37 use TYPO3\CMS\Core\Utility\ArrayUtility;
38 use TYPO3\CMS\Core\Utility\GeneralUtility;
39 use TYPO3\CMS\Core\Utility\MathUtility;
40
41 /**
42 * Contains methods used by Data providers that handle elements
43 * with single items like select, radio and some more.
44 */
45 abstract class AbstractItemProvider
46 {
47 /**
48 * Resolve "itemProcFunc" of elements.
49 *
50 * @param array $result Main result array
51 * @param string $fieldName Field name to handle item list for
52 * @param array $items Existing items array
53 * @return array New list of item elements
54 */
55 protected function resolveItemProcessorFunction(array $result, $fieldName, array $items)
56 {
57 $table = $result['tableName'];
58 $config = $result['processedTca']['columns'][$fieldName]['config'];
59
60 $pageTsProcessorParameters = null;
61 if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'])) {
62 $pageTsProcessorParameters = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'];
63 }
64 $processorParameters = [
65 // Function manipulates $items directly and return nothing
66 'items' => &$items,
67 'config' => $config,
68 'TSconfig' => $pageTsProcessorParameters,
69 'table' => $table,
70 'row' => $result['databaseRow'],
71 'field' => $fieldName,
72 ];
73 if (!empty($result['flexParentDatabaseRow'])) {
74 $processorParameters['flexParentDatabaseRow'] = $result['flexParentDatabaseRow'];
75 }
76
77 try {
78 GeneralUtility::callUserFunction($config['itemsProcFunc'], $processorParameters, $this);
79 } catch (\Exception $exception) {
80 // The itemsProcFunc method may throw an exception, create a flash message if so
81 $languageService = $this->getLanguageService();
82 $fieldLabel = $fieldName;
83 if (!empty($result['processedTca']['columns'][$fieldName]['label'])) {
84 $fieldLabel = $languageService->sL($result['processedTca']['columns'][$fieldName]['label']);
85 }
86 $message = sprintf(
87 $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:error.items_proc_func_error'),
88 $fieldLabel,
89 $exception->getMessage()
90 );
91 /** @var FlashMessage $flashMessage */
92 $flashMessage = GeneralUtility::makeInstance(
93 FlashMessage::class,
94 $message,
95 '',
96 FlashMessage::ERROR,
97 true
98 );
99 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
100 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
101 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
102 $defaultFlashMessageQueue->enqueue($flashMessage);
103 }
104
105 return $items;
106 }
107
108 /**
109 * PageTsConfig addItems:
110 *
111 * TCEFORMS.aTable.aField[.types][.aType].addItems.aValue = aLabel,
112 * with type specific options merged by pageTsConfig already
113 *
114 * Used by TcaSelectItems and TcaSelectTreeItems data providers
115 *
116 * @param array $result result array
117 * @param string $fieldName Current handle field name
118 * @param array $items Incoming items
119 * @return array Modified item array
120 */
121 protected function addItemsFromPageTsConfig(array $result, $fieldName, array $items)
122 {
123 $table = $result['tableName'];
124 if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
125 && is_array($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
126 ) {
127 $addItemsArray = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'];
128 foreach ($addItemsArray as $value => $label) {
129 // If the value ends with a dot, it is a subelement like "34.icon = mylabel.png", skip it
130 if (substr($value, -1) === '.') {
131 continue;
132 }
133 // Check if value "34 = mylabel" also has a "34.icon = myImage.png"
134 $iconIdentifier = null;
135 if (isset($addItemsArray[$value . '.'])
136 && is_array($addItemsArray[$value . '.'])
137 && !empty($addItemsArray[$value . '.']['icon'])
138 ) {
139 $iconIdentifier = $addItemsArray[$value . '.']['icon'];
140 }
141 $items[] = [$label, $value, $iconIdentifier];
142 }
143 }
144 return $items;
145 }
146
147 /**
148 * TCA config "special" evaluation. Add them to $items
149 *
150 * Used by TcaSelectItems and TcaSelectTreeItems data providers
151 *
152 * @param array $result Result array
153 * @param string $fieldName Current handle field name
154 * @param array $items Incoming items
155 * @return array Modified item array
156 * @throws \UnexpectedValueException
157 */
158 protected function addItemsFromSpecial(array $result, $fieldName, array $items)
159 {
160 // Guard
161 if (empty($result['processedTca']['columns'][$fieldName]['config']['special'])
162 || !is_string($result['processedTca']['columns'][$fieldName]['config']['special'])
163 ) {
164 return $items;
165 }
166
167 $languageService = $this->getLanguageService();
168 $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
169 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
170
171 $special = $result['processedTca']['columns'][$fieldName]['config']['special'];
172 switch (true) {
173 case $special === 'tables':
174 foreach ($GLOBALS['TCA'] as $currentTable => $_) {
175 if (!empty($GLOBALS['TCA'][$currentTable]['ctrl']['adminOnly'])) {
176 // Hide "admin only" tables
177 continue;
178 }
179 $label = !empty($GLOBALS['TCA'][$currentTable]['ctrl']['title']) ? $GLOBALS['TCA'][$currentTable]['ctrl']['title'] : '';
180 $icon = $iconFactory->mapRecordTypeToIconIdentifier($currentTable, []);
181 $helpText = [];
182 $languageService->loadSingleTableDescription($currentTable);
183 // @todo: check if this actually works, currently help texts are missing
184 $helpTextArray = $GLOBALS['TCA_DESCR'][$currentTable]['columns'][''];
185 if (!empty($helpTextArray['description'])) {
186 $helpText['description'] = $helpTextArray['description'];
187 }
188 $items[] = [$label, $currentTable, $icon, $helpText];
189 }
190 break;
191 case $special === 'pagetypes':
192 if (isset($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
193 && is_array($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
194 ) {
195 $specialItems = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
196 foreach ($specialItems as $specialItem) {
197 if (!is_array($specialItem) || $specialItem[1] === '--div--') {
198 // Skip non arrays and divider items
199 continue;
200 }
201 $label = $specialItem[0];
202 $value = $specialItem[1];
203 $icon = $iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $specialItem[1]]);
204 $items[] = [$label, $value, $icon];
205 }
206 }
207 break;
208 case $special === 'exclude':
209 $excludeArrays = $this->getExcludeFields();
210 foreach ($excludeArrays as $excludeArray) {
211 // If the field comes from a FlexForm, the syntax is more complex
212 if ($excludeArray['origin'] === 'flexForm') {
213 // The field comes from a plugins FlexForm
214 // Add header if not yet set for plugin section
215 if (!isset($items[$excludeArray['sectionHeader']])) {
216 // there is no icon handling for plugins - we take the icon from the table
217 $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], []);
218 $items[$excludeArray['sectionHeader']] = [
219 $excludeArray['sectionHeader'],
220 '--div--',
221 $icon
222 ];
223 }
224 } else {
225 // Add header if not yet set for table
226 if (!isset($items[$excludeArray['table']])) {
227 $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], []);
228 $items[$excludeArray['table']] = [
229 $GLOBALS['TCA'][$excludeArray['table']]['ctrl']['title'],
230 '--div--',
231 $icon
232 ];
233 }
234 }
235 // Add help text
236 $helpText = [];
237 $languageService->loadSingleTableDescription($excludeArray['table']);
238 $helpTextArray = $GLOBALS['TCA_DESCR'][$excludeArray['table']]['columns'][$excludeArray['table']];
239 if (!empty($helpTextArray['description'])) {
240 $helpText['description'] = $helpTextArray['description'];
241 }
242 // Item configuration:
243 $items[] = [
244 rtrim($excludeArray['origin'] === 'flexForm' ? $excludeArray['fieldLabel'] : $languageService->sL($GLOBALS['TCA'][$excludeArray['table']]['columns'][$excludeArray['fieldName']]['label']), ':') . ' (' . $excludeArray['fieldName'] . ')',
245 $excludeArray['table'] . ':' . $excludeArray['fullField'] ,
246 'empty-empty',
247 $helpText
248 ];
249 }
250 break;
251 case $special === 'explicitValues':
252 $theTypes = $this->getExplicitAuthFieldValues();
253 $icons = [
254 'ALLOW' => 'status-status-permission-granted',
255 'DENY' => 'status-status-permission-denied'
256 ];
257 // Traverse types:
258 foreach ($theTypes as $tableFieldKey => $theTypeArrays) {
259 if (is_array($theTypeArrays['items'])) {
260 // Add header:
261 $items[] = [
262 $theTypeArrays['tableFieldLabel'],
263 '--div--',
264 ];
265 // Traverse options for this field:
266 foreach ($theTypeArrays['items'] as $itemValue => $itemContent) {
267 // Add item to be selected:
268 $items[] = [
269 '[' . $itemContent[2] . '] ' . $itemContent[1],
270 $tableFieldKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $itemContent[0],
271 $icons[$itemContent[0]]
272 ];
273 }
274 }
275 }
276 break;
277 case $special === 'languages':
278 foreach ($result['systemLanguageRows'] as $language) {
279 if ($language['uid'] !== -1) {
280 $items[] = [
281 0 => $language['title'] . ' [' . $language['uid'] . ']',
282 1 => $language['uid'],
283 2 => $language['flagIconIdentifier']
284 ];
285 }
286 }
287 break;
288 case $special === 'custom':
289 $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'];
290 if (is_array($customOptions)) {
291 foreach ($customOptions as $coKey => $coValue) {
292 if (is_array($coValue['items'])) {
293 // Add header:
294 $items[] = [
295 $languageService->sL($coValue['header']),
296 '--div--'
297 ];
298 // Traverse items:
299 foreach ($coValue['items'] as $itemKey => $itemCfg) {
300 $icon = 'empty-empty';
301 $helpText = [];
302 if (!empty($itemCfg[1])) {
303 if ($iconRegistry->isRegistered($itemCfg[1])) {
304 // Use icon identifier when registered
305 $icon = $itemCfg[1];
306 }
307 }
308 if (!empty($itemCfg[2])) {
309 $helpText['description'] = $languageService->sL($itemCfg[2]);
310 }
311 $items[] = [
312 $languageService->sL($itemCfg[0]),
313 $coKey . ':' . preg_replace('/[:|,]/', '', $itemKey),
314 $icon,
315 $helpText
316 ];
317 }
318 }
319 }
320 }
321 break;
322 case $special === 'modListGroup' || $special === 'modListUser':
323 /** @var ModuleLoader $loadModules */
324 $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
325 $loadModules->load($GLOBALS['TBE_MODULES']);
326 $modList = $special === 'modListUser' ? $loadModules->modListUser : $loadModules->modListGroup;
327 if (is_array($modList)) {
328 foreach ($modList as $theMod) {
329 $moduleLabels = $loadModules->getLabelsForModule($theMod);
330 list($mainModule, $subModule) = explode('_', $theMod, 2);
331 // Icon:
332 if (!empty($subModule)) {
333 $icon = $loadModules->modules[$mainModule]['sub'][$subModule]['iconIdentifier'];
334 } else {
335 $icon = $loadModules->modules[$theMod]['iconIdentifier'];
336 }
337 // Add help text
338 $helpText = [
339 'title' => $languageService->sL($moduleLabels['shortdescription']),
340 'description' => $languageService->sL($moduleLabels['description'])
341 ];
342
343 $label = '';
344 // Add label for main module if this is a submodule
345 if (!empty($subModule)) {
346 $mainModuleLabels = $loadModules->getLabelsForModule($mainModule);
347 $label .= $languageService->sL($mainModuleLabels['title']) . '>';
348 }
349 // Add modules own label now
350 $label .= $languageService->sL($moduleLabels['title']);
351
352 // Item configuration
353 $items[] = [$label, $theMod, $icon, $helpText];
354 }
355 }
356 break;
357 default:
358 throw new \UnexpectedValueException(
359 'Unknown special value ' . $special . ' for field ' . $fieldName . ' of table ' . $result['tableName'],
360 1439298496
361 );
362 }
363 return $items;
364 }
365
366 /**
367 * TCA config "fileFolder" evaluation. Add them to $items
368 *
369 * Used by TcaSelectItems and TcaSelectTreeItems data providers
370 *
371 * @param array $result Result array
372 * @param string $fieldName Current handle field name
373 * @param array $items Incoming items
374 * @return array Modified item array
375 * @throws \RuntimeException
376 */
377 protected function addItemsFromFolder(array $result, $fieldName, array $items)
378 {
379 if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
380 || !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
381 ) {
382 return $items;
383 }
384
385 $fileFolderRaw = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
386 $fileFolder = GeneralUtility::getFileAbsFileName($fileFolderRaw);
387 if ($fileFolder === '') {
388 throw new \RuntimeException(
389 'Invalid folder given for item processing: ' . $fileFolderRaw . ' for table ' . $result['tableName'] . ', field ' . $fieldName,
390 1479399227
391 );
392 }
393 $fileFolder = rtrim($fileFolder, '/') . '/';
394
395 if (@is_dir($fileFolder)) {
396 $fileExtensionList = '';
397 if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
398 && is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
399 ) {
400 $fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
401 }
402 $recursionLevels = isset($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'])
403 ? MathUtility::forceIntegerInRange($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'], 0, 99)
404 : 99;
405 $fileArray = GeneralUtility::getAllFilesAndFoldersInPath([], $fileFolder, $fileExtensionList, 0, $recursionLevels);
406 $fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
407 foreach ($fileArray as $fileReference) {
408 $fileInformation = pathinfo($fileReference);
409 $icon = GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($fileInformation['extension']))
410 ? $fileFolder . $fileReference
411 : '';
412 $items[] = [
413 $fileReference,
414 $fileReference,
415 $icon
416 ];
417 }
418 }
419
420 return $items;
421 }
422
423 /**
424 * TCA config "foreign_table" evaluation. Add them to $items
425 *
426 * Used by TcaSelectItems and TcaSelectTreeItems data providers
427 *
428 * @param array $result Result array
429 * @param string $fieldName Current handle field name
430 * @param array $items Incoming items
431 * @return array Modified item array
432 * @throws \UnexpectedValueException
433 */
434 protected function addItemsFromForeignTable(array $result, $fieldName, array $items)
435 {
436 // Guard
437 if (empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
438 || !is_string($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
439 ) {
440 return $items;
441 }
442
443 $languageService = $this->getLanguageService();
444
445 $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
446
447 if (!is_array($GLOBALS['TCA'][$foreignTable])) {
448 throw new \UnexpectedValueException(
449 'Field ' . $fieldName . ' of table ' . $result['tableName'] . ' reference to foreign table '
450 . $foreignTable . ', but this table is not defined in TCA',
451 1439569743
452 );
453 }
454
455 $queryBuilder = $this->buildForeignTableQueryBuilder($result, $fieldName);
456 try {
457 $queryResult = $queryBuilder->execute();
458 } catch (DBALException $e) {
459 $databaseError = $e->getPrevious()->getMessage();
460 }
461
462 // Early return on error with flash message
463 if (!empty($databaseError)) {
464 $msg = $databaseError . '. ';
465 $msg .= $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch');
466 $msgTitle = $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch_title');
467 /** @var $flashMessage FlashMessage */
468 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, true);
469 /** @var $flashMessageService FlashMessageService */
470 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
471 /** @var $defaultFlashMessageQueue FlashMessageQueue */
472 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
473 $defaultFlashMessageQueue->enqueue($flashMessage);
474 return $items;
475 }
476
477 $labelPrefix = '';
478 if (!empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'])) {
479 $labelPrefix = $result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'];
480 $labelPrefix = $languageService->sL($labelPrefix);
481 }
482
483 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
484 $fileRepository = GeneralUtility::makeInstance(FileRepository::class);
485
486 while ($foreignRow = $queryResult->fetch()) {
487 BackendUtility::workspaceOL($foreignTable, $foreignRow);
488 if (is_array($foreignRow)) {
489 // If the foreign table sets selicon_field, this field can contain an image
490 // that represents this specific row.
491 $iconFieldName = '';
492 $isReferenceField = false;
493 if (!empty($GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'])) {
494 $iconFieldName = $GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'];
495 if ($GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['type'] === 'inline'
496 && $GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['foreign_table'] === 'sys_file_reference') {
497 $isReferenceField = true;
498 }
499 }
500 $icon = '';
501 if ($isReferenceField) {
502 $references = $fileRepository->findByRelation($foreignTable, $iconFieldName, $foreignRow['uid']);
503 if (is_array($references) && !empty($references)) {
504 $icon = reset($references);
505 $icon = $icon->getPublicUrl();
506 }
507 } else {
508 $iconPath = '';
509 if (!empty($GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field_path'])) {
510 $iconPath = $GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field_path'];
511 }
512 if ($iconFieldName && $iconPath && $foreignRow[$iconFieldName]) {
513 // Prepare the row icon if available
514 $iParts = GeneralUtility::trimExplode(',', $foreignRow[$iconFieldName], true);
515 $icon = $iconPath . '/' . trim($iParts[0]);
516 } else {
517 // Else, determine icon based on record type, or a generic fallback
518 $icon = $iconFactory->mapRecordTypeToIconIdentifier($foreignTable, $foreignRow);
519 }
520 }
521 // Add the item
522 $items[] = [
523 $labelPrefix . BackendUtility::getRecordTitle($foreignTable, $foreignRow),
524 $foreignRow['uid'],
525 $icon
526 ];
527 }
528 }
529
530 return $items;
531 }
532
533 /**
534 * Remove items using "keepItems" pageTsConfig
535 *
536 * Used by TcaSelectItems and TcaSelectTreeItems data providers
537 *
538 * @param array $result Result array
539 * @param string $fieldName Current handle field name
540 * @param array $items Incoming items
541 * @return array Modified item array
542 */
543 protected function removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
544 {
545 $table = $result['tableName'];
546 if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
547 || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
548 ) {
549 return $items;
550 }
551
552 // If keepItems is set but is an empty list all current items get removed
553 if ($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'] === '') {
554 return [];
555 }
556
557 return ArrayUtility::keepItemsInArray(
558 $items,
559 $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'],
560 function ($value) {
561 return $value[1];
562 }
563 );
564 }
565
566 /**
567 * Remove items using "removeItems" pageTsConfig
568 *
569 * Used by TcaSelectItems and TcaSelectTreeItems data providers
570 *
571 * @param array $result Result array
572 * @param string $fieldName Current handle field name
573 * @param array $items Incoming items
574 * @return array Modified item array
575 */
576 protected function removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
577 {
578 $table = $result['tableName'];
579 if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
580 || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
581 || $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'] === ''
582 ) {
583 return $items;
584 }
585
586 $removeItems = array_flip(GeneralUtility::trimExplode(
587 ',',
588 $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'],
589 true
590 ));
591 foreach ($items as $key => $itemValues) {
592 if (isset($removeItems[$itemValues[1]])) {
593 unset($items[$key]);
594 }
595 }
596
597 return $items;
598 }
599
600 /**
601 * Remove items user restriction on language field
602 *
603 * Used by TcaSelectItems and TcaSelectTreeItems data providers
604 *
605 * @param array $result Result array
606 * @param string $fieldName Current handle field name
607 * @param array $items Incoming items
608 * @return array Modified item array
609 */
610 protected function removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
611 {
612 // Guard clause returns if not a language field is handled
613 if (empty($result['processedTca']['ctrl']['languageField'])
614 || $result['processedTca']['ctrl']['languageField'] !== $fieldName
615 ) {
616 return $items;
617 }
618
619 $backendUser = $this->getBackendUser();
620 foreach ($items as $key => $itemValues) {
621 if (!$backendUser->checkLanguageAccess($itemValues[1])) {
622 unset($items[$key]);
623 }
624 }
625
626 return $items;
627 }
628
629 /**
630 * Remove items by user restriction on authMode items
631 *
632 * Used by TcaSelectItems and TcaSelectTreeItems data providers
633 *
634 * @param array $result Result array
635 * @param string $fieldName Current handle field name
636 * @param array $items Incoming items
637 * @return array Modified item array
638 */
639 protected function removeItemsByUserAuthMode(array $result, $fieldName, array $items)
640 {
641 // Guard clause returns early if no authMode field is configured
642 if (!isset($result['processedTca']['columns'][$fieldName]['config']['authMode'])
643 || !is_string($result['processedTca']['columns'][$fieldName]['config']['authMode'])
644 ) {
645 return $items;
646 }
647
648 $backendUser = $this->getBackendUser();
649 $authMode = $result['processedTca']['columns'][$fieldName]['config']['authMode'];
650 foreach ($items as $key => $itemValues) {
651 // @todo: checkAuthMode() uses $GLOBAL access for "individual" authMode - get rid of this
652 if (!$backendUser->checkAuthMode($result['tableName'], $fieldName, $itemValues[1], $authMode)) {
653 unset($items[$key]);
654 }
655 }
656
657 return $items;
658 }
659
660 /**
661 * Remove items if doktype is handled for non admin users
662 *
663 * Used by TcaSelectItems and TcaSelectTreeItems data providers
664 *
665 * @param array $result Result array
666 * @param string $fieldName Current handle field name
667 * @param array $items Incoming items
668 * @return array Modified item array
669 */
670 protected function removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
671 {
672 $table = $result['tableName'];
673 $backendUser = $this->getBackendUser();
674 // Guard clause returns if not correct table and field or if user is admin
675 if ($table !== 'pages' || $fieldName !== 'doktype' || $backendUser->isAdmin()
676 ) {
677 return $items;
678 }
679
680 $allowedPageTypes = $backendUser->groupData['pagetypes_select'];
681 foreach ($items as $key => $itemValues) {
682 if (!GeneralUtility::inList($allowedPageTypes, $itemValues[1])) {
683 unset($items[$key]);
684 }
685 }
686
687 return $items;
688 }
689
690 /**
691 * Remove items if sys_file_storage is not allowed for non-admin users.
692 *
693 * Used by TcaSelectItems data providers
694 *
695 * @param array $result Result array
696 * @param string $fieldName Current handle field name
697 * @param array $items Incoming items
698 * @return array Modified item array
699 */
700 protected function removeItemsByUserStorageRestriction(array $result, $fieldName, array $items)
701 {
702 $referencedTableName = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'] ?? null;
703 if ($referencedTableName !== 'sys_file_storage') {
704 return $items;
705 }
706
707 $allowedStorageIds = array_map(
708 function (\TYPO3\CMS\Core\Resource\ResourceStorage $storage) {
709 return $storage->getUid();
710 },
711 $this->getBackendUser()->getFileStorages()
712 );
713
714 return array_filter(
715 $items,
716 function (array $item) use ($allowedStorageIds) {
717 $itemValue = $item[1] ?? null;
718 return empty($itemValue)
719 || in_array((int)$itemValue, $allowedStorageIds, true);
720 }
721 );
722 }
723
724 /**
725 * Returns an array with the exclude fields as defined in TCA and FlexForms
726 * Used for listing the exclude fields in be_groups forms.
727 *
728 * @return array Array of arrays with excludeFields (fieldName, table:fieldName) from TCA
729 * and FlexForms (fieldName, table:extKey;sheetName;fieldName)
730 */
731 protected function getExcludeFields()
732 {
733 $languageService = $this->getLanguageService();
734 $finalExcludeArray = [];
735
736 // Fetch translations for table names
737 $tableToTranslation = [];
738 // All TCA keys
739 foreach ($GLOBALS['TCA'] as $table => $conf) {
740 $tableToTranslation[$table] = $languageService->sL($conf['ctrl']['title']);
741 }
742 // Sort by translations
743 asort($tableToTranslation);
744 foreach ($tableToTranslation as $table => $translatedTable) {
745 $excludeArrayTable = [];
746
747 // All field names configured and not restricted to admins
748 if (is_array($GLOBALS['TCA'][$table]['columns'])
749 && empty($GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
750 && (empty($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
751 ) {
752 foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
753 if ($GLOBALS['TCA'][$table]['columns'][$field]['exclude']) {
754 // Get human readable names of fields
755 $translatedField = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$field]['label']);
756 // Add entry, key 'labels' needed for sorting
757 $excludeArrayTable[] = [
758 'labels' => $translatedTable . ':' . $translatedField,
759 'sectionHeader' => $translatedTable,
760 'table' => $table,
761 'tableField' => $field,
762 'fieldName' => $field,
763 'fullField' => $field,
764 'fieldLabel' => $translatedField,
765 'origin' => 'tca',
766 ];
767 }
768 }
769 }
770 // All FlexForm fields
771 $flexFormArray = $this->getRegisteredFlexForms($table);
772 foreach ($flexFormArray as $tableField => $flexForms) {
773 // Prefix for field label, e.g. "Plugin Options:"
774 $labelPrefix = '';
775 if (!empty($GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
776 $labelPrefix = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
777 }
778 // Get all sheets
779 foreach ($flexForms as $extIdent => $extConf) {
780 // Get all fields in sheet
781 foreach ($extConf['sheets'] as $sheetName => $sheet) {
782 if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
783 continue;
784 }
785 foreach ($sheet['ROOT']['el'] as $pluginFieldName => $field) {
786 // Use only fields that have exclude flag set
787 if (empty($field['TCEforms']['exclude'])) {
788 continue;
789 }
790 $fieldLabel = !empty($field['TCEforms']['label'])
791 ? $languageService->sL($field['TCEforms']['label'])
792 : $pluginFieldName;
793 $excludeArrayTable[] = [
794 'labels' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ': ') . ':' . $fieldLabel,
795 'sectionHeader' => trim(($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent), ':'),
796 'table' => $table,
797 'tableField' => $tableField,
798 'extIdent' => $extIdent,
799 'fieldName' => $pluginFieldName,
800 'fullField' => $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $pluginFieldName,
801 'fieldLabel' => $fieldLabel,
802 'origin' => 'flexForm',
803 ];
804 }
805 }
806 }
807 }
808 // Sort fields by the translated value
809 if (!empty($excludeArrayTable)) {
810 usort($excludeArrayTable, function (array $array1, array $array2) {
811 $array1 = reset($array1);
812 $array2 = reset($array2);
813 if (is_string($array1) && is_string($array2)) {
814 return strcasecmp($array1, $array2);
815 }
816 return 0;
817 });
818 $finalExcludeArray = array_merge($finalExcludeArray, $excludeArrayTable);
819 }
820 }
821
822 return $finalExcludeArray;
823 }
824
825 /**
826 * Returns FlexForm data structures it finds. Used in select "special" for be_groups
827 * to set "exclude" flags for single flex form fields.
828 *
829 * This only finds flex forms registered in 'ds' config sections.
830 * This does not resolve other sophisticated flex form data structure references.
831 *
832 * @todo: This approach is limited and doesn't find everything. It works for casual tt_content plugins, though:
833 * @todo: The data structure identifier determination depends on data row, but we don't have all rows at hand here.
834 * @todo: The code thus "guesses" some standard data structure identifier scenarios and tries to resolve those.
835 * @todo: This guessing can not be solved in a good way. A general registry of "all" possible data structures is
836 * @todo: probably not wanted, since that wouldn't work for truly dynamic DS calculations. Probably the only
837 * @todo: thing we could do here is a hook to allow extensions declaring specific data structures to
838 * @todo: allow backend admins to set exclude flags for certain fields in those cases.
839 *
840 * @param string $table Table to handle
841 * @return array Data structures
842 */
843 protected function getRegisteredFlexForms($table)
844 {
845 if (empty($table) || empty($GLOBALS['TCA'][$table]['columns'])) {
846 return [];
847 }
848 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
849 $flexForms = [];
850 foreach ($GLOBALS['TCA'][$table]['columns'] as $tableField => $fieldConf) {
851 if (!empty($fieldConf['config']['type']) && !empty($fieldConf['config']['ds']) && $fieldConf['config']['type'] === 'flex') {
852 $flexForms[$tableField] = [];
853 foreach (array_keys($fieldConf['config']['ds']) as $flexFormKey) {
854 // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
855 $identFields = GeneralUtility::trimExplode(',', $flexFormKey);
856 $extIdent = $identFields[0];
857 if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
858 $extIdent = $identFields[1];
859 }
860 $flexFormDataStructureIdentifier = json_encode([
861 'type' => 'tca',
862 'tableName' => $table,
863 'fieldName' => $tableField,
864 'dataStructureKey' => $flexFormKey,
865 ]);
866 try {
867 $dataStructure = $flexFormTools->parseDataStructureByIdentifier($flexFormDataStructureIdentifier);
868 $flexForms[$tableField][$extIdent] = $dataStructure;
869 } catch (InvalidIdentifierException $e) {
870 // Deliberately empty: The DS identifier is guesswork and the flex ds parser throws
871 // this exception if it can not resolve to a valid data structure. This is "ok" here
872 // and the exception is just eaten.
873 }
874 }
875 }
876 }
877 return $flexForms;
878 }
879
880 /**
881 * Returns an array with explicit Allow/Deny fields.
882 * Used for listing these field/value pairs in be_groups forms
883 *
884 * @return array Array with information from all of $GLOBALS['TCA']
885 */
886 protected function getExplicitAuthFieldValues()
887 {
888 $languageService = static::getLanguageService();
889 $adLabel = [
890 'ALLOW' => $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.allow'),
891 'DENY' => $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.deny')
892 ];
893 $allowDenyOptions = [];
894 foreach ($GLOBALS['TCA'] as $table => $_) {
895 // All field names configured:
896 if (is_array($GLOBALS['TCA'][$table]['columns'])) {
897 foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $__) {
898 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
899 if ($fieldConfig['type'] === 'select' && $fieldConfig['authMode']) {
900 // Check for items
901 if (is_array($fieldConfig['items'])) {
902 // Get Human Readable names of fields and table:
903 $allowDenyOptions[$table . ':' . $field]['tableFieldLabel'] =
904 $languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title']) . ': '
905 . $languageService->sL($GLOBALS['TCA'][$table]['columns'][$field]['label']);
906 foreach ($fieldConfig['items'] as $iVal) {
907 $itemIdentifier = (string)$iVal[1];
908 // Values '' and '--div--' are not controlled by this setting.
909 if ($itemIdentifier === '' || $itemIdentifier === '--div--') {
910 continue;
911 }
912 // Find iMode
913 $iMode = '';
914 switch ((string)$fieldConfig['authMode']) {
915 case 'explicitAllow':
916 $iMode = 'ALLOW';
917 break;
918 case 'explicitDeny':
919 $iMode = 'DENY';
920 break;
921 case 'individual':
922 if ($iVal[4] === 'EXPL_ALLOW') {
923 $iMode = 'ALLOW';
924 } elseif ($iVal[4] === 'EXPL_DENY') {
925 $iMode = 'DENY';
926 }
927 break;
928 }
929 // Set iMode
930 if ($iMode) {
931 $allowDenyOptions[$table . ':' . $field]['items'][$itemIdentifier] = [
932 $iMode,
933 $languageService->sL($iVal[0]),
934 $adLabel[$iMode]
935 ];
936 }
937 }
938 }
939 }
940 }
941 }
942 }
943 return $allowDenyOptions;
944 }
945
946 /**
947 * Build query to fetch foreign records. Helper method of
948 * addItemsFromForeignTable(), do not call otherwise.
949 *
950 * @param array $result Result array
951 * @param string $localFieldName Current handle field name
952 * @return QueryBuilder
953 */
954 protected function buildForeignTableQueryBuilder(array $result, string $localFieldName): QueryBuilder
955 {
956 $backendUser = $this->getBackendUser();
957
958 $foreignTableName = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table'];
959 $foreignTableClauseArray = $this->processForeignTableClause($result, $foreignTableName, $localFieldName);
960
961 $fieldList = BackendUtility::getCommonSelectFields($foreignTableName, $foreignTableName . '.');
962 /** @var QueryBuilder $queryBuilder */
963 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
964 ->getQueryBuilderForTable($foreignTableName);
965
966 $queryBuilder->getRestrictions()
967 ->removeAll()
968 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
969
970 $queryBuilder
971 ->select(...GeneralUtility::trimExplode(',', $fieldList, true))
972 ->from($foreignTableName)
973 ->where($foreignTableClauseArray['WHERE']);
974
975 if (!empty($foreignTableClauseArray['GROUPBY'])) {
976 $queryBuilder->groupBy(...$foreignTableClauseArray['GROUPBY']);
977 }
978
979 if (!empty($foreignTableClauseArray['ORDERBY'])) {
980 foreach ($foreignTableClauseArray['ORDERBY'] as $orderPair) {
981 list($fieldName, $order) = $orderPair;
982 $queryBuilder->addOrderBy($fieldName, $order);
983 }
984 }
985
986 if (!empty($foreignTableClauseArray['LIMIT'])) {
987 if (!empty($foreignTableClauseArray['LIMIT'][1])) {
988 $queryBuilder->setMaxResults($foreignTableClauseArray['LIMIT'][1]);
989 $queryBuilder->setFirstResult($foreignTableClauseArray['LIMIT'][0]);
990 } elseif (!empty($foreignTableClauseArray['LIMIT'][0])) {
991 $queryBuilder->setMaxResults($foreignTableClauseArray['LIMIT'][0]);
992 }
993 }
994
995 // rootLevel = -1 means that elements can be on the rootlevel OR on any page (pid!=-1)
996 // rootLevel = 0 means that elements are not allowed on root level
997 // rootLevel = 1 means that elements are only on the root level (pid=0)
998 $rootLevel = 0;
999 if (isset($GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'])) {
1000 $rootLevel = (int)$GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'];
1001 }
1002
1003 if ($rootLevel === -1) {
1004 $queryBuilder->andWhere(
1005 $queryBuilder->expr()->neq(
1006 $foreignTableName . '.pid',
1007 $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1008 )
1009 );
1010 } elseif ($rootLevel === 1) {
1011 $queryBuilder->andWhere(
1012 $queryBuilder->expr()->eq(
1013 $foreignTableName . '.pid',
1014 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1015 )
1016 );
1017 } else {
1018 $queryBuilder->andWhere($backendUser->getPagePermsClause(Permission::PAGE_SHOW));
1019 if ($foreignTableName !== 'pages') {
1020 $queryBuilder
1021 ->from('pages')
1022 ->andWhere(
1023 $queryBuilder->expr()->eq(
1024 'pages.uid',
1025 $queryBuilder->quoteIdentifier($foreignTableName . '.pid')
1026 )
1027 );
1028 }
1029 }
1030
1031 return $queryBuilder;
1032 }
1033
1034 /**
1035 * Replace markers in a where clause from TCA foreign_table_where
1036 *
1037 * ###REC_FIELD_[field name]###
1038 * ###THIS_UID### - is current element uid (zero if new).
1039 * ###CURRENT_PID### - is the current page id (pid of the record).
1040 * ###SITEROOT###
1041 * ###PAGE_TSCONFIG_ID### - a value you can set from Page TSconfig dynamically.
1042 * ###PAGE_TSCONFIG_IDLIST### - a value you can set from Page TSconfig dynamically.
1043 * ###PAGE_TSCONFIG_STR### - a value you can set from Page TSconfig dynamically.
1044 *
1045 * @param array $result Result array
1046 * @param string $foreignTableName Name of foreign table
1047 * @param string $localFieldName Current handle field name
1048 * @return array Query parts with keys WHERE, ORDERBY, GROUPBY, LIMIT
1049 */
1050 protected function processForeignTableClause(array $result, $foreignTableName, $localFieldName)
1051 {
1052 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($foreignTableName);
1053 $localTable = $result['tableName'];
1054 $effectivePid = $result['effectivePid'];
1055
1056 $foreignTableClause = '';
1057 if (!empty($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
1058 && is_string($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
1059 ) {
1060 $foreignTableClause = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'];
1061 // Replace possible markers in query
1062 if (strstr($foreignTableClause, '###REC_FIELD_')) {
1063 // " AND table.field='###REC_FIELD_field1###' AND ..." -> array(" AND table.field='", "field1###' AND ...")
1064 $whereClauseParts = explode('###REC_FIELD_', $foreignTableClause);
1065 foreach ($whereClauseParts as $key => $value) {
1066 if ($key !== 0) {
1067 // "field1###' AND ..." -> array("field1", "' AND ...")
1068 $whereClauseSubParts = explode('###', $value, 2);
1069 // @todo: Throw exception if there is no value? What happens for NEW records?
1070 $databaseRowKey = empty($result['flexParentDatabaseRow']) ? 'databaseRow' : 'flexParentDatabaseRow';
1071 $rowFieldValue = isset($result[$databaseRowKey][$whereClauseSubParts[0]]) ? $result[$databaseRowKey][$whereClauseSubParts[0]] : '';
1072 if (is_array($rowFieldValue)) {
1073 // If a select or group field is used here, it may have been processed already and
1074 // is now an array. Use first selected value in this case.
1075 $rowFieldValue = $rowFieldValue[0];
1076 }
1077 if (substr($whereClauseParts[0], -1) === '\'' && $whereClauseSubParts[1][0] === '\'') {
1078 $whereClauseParts[0] = substr($whereClauseParts[0], 0, -1);
1079 $whereClauseSubParts[1] = substr($whereClauseSubParts[1], 1);
1080 }
1081 $whereClauseParts[$key] = $connection->quote($rowFieldValue) . $whereClauseSubParts[1];
1082 }
1083 }
1084 $foreignTableClause = implode('', $whereClauseParts);
1085 }
1086 if (strpos($foreignTableClause, '###CURRENT_PID###') !== false) {
1087 // Use pid from parent page clause if in flex form context
1088 if (!empty($result['flexParentDatabaseRow']['pid'])) {
1089 $effectivePid = $result['flexParentDatabaseRow']['pid'];
1090 } elseif (!$effectivePid && !empty($result['databaseRow']['pid'])) {
1091 // Use pid from database row if in inline context
1092 $effectivePid = $result['databaseRow']['pid'];
1093 }
1094 }
1095
1096 $siteRootUid = 0;
1097 foreach ($result['rootline'] as $rootlinePage) {
1098 if (!empty($rootlinePage['is_siteroot'])) {
1099 $siteRootUid = (int)$rootlinePage['uid'];
1100 break;
1101 }
1102 }
1103
1104 $pageTsConfigId = 0;
1105 if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID']) {
1106 $pageTsConfigId = (int)$result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'];
1107 }
1108
1109 $pageTsConfigIdList = 0;
1110 if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST']) {
1111 $pageTsConfigIdList = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'];
1112 }
1113 $pageTsConfigIdListArray = GeneralUtility::trimExplode(',', $pageTsConfigIdList, true);
1114 $pageTsConfigIdList = [];
1115 foreach ($pageTsConfigIdListArray as $pageTsConfigIdListElement) {
1116 if (MathUtility::canBeInterpretedAsInteger($pageTsConfigIdListElement)) {
1117 $pageTsConfigIdList[] = (int)$pageTsConfigIdListElement;
1118 }
1119 }
1120 $pageTsConfigIdList = implode(',', $pageTsConfigIdList);
1121
1122 $pageTsConfigString = '';
1123 if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR']) {
1124 $pageTsConfigString = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'];
1125 $pageTsConfigString = $connection->quote($pageTsConfigString);
1126 }
1127
1128 $foreignTableClause = str_replace(
1129 [
1130 '###CURRENT_PID###',
1131 '###THIS_UID###',
1132 '###SITEROOT###',
1133 '###PAGE_TSCONFIG_ID###',
1134 '###PAGE_TSCONFIG_IDLIST###',
1135 '\'###PAGE_TSCONFIG_STR###\'',
1136 '###PAGE_TSCONFIG_STR###'
1137 ],
1138 [
1139 (int)$effectivePid,
1140 (int)$result['databaseRow']['uid'],
1141 $siteRootUid,
1142 $pageTsConfigId,
1143 $pageTsConfigIdList,
1144 $pageTsConfigString,
1145 $pageTsConfigString
1146 ],
1147 $foreignTableClause
1148 );
1149 }
1150
1151 // Split the clause into an array with keys WHERE, GROUPBY, ORDERBY, LIMIT
1152 // Prepend a space to make sure "[[:space:]]+" will find a space there for the first element.
1153 $foreignTableClause = ' ' . $foreignTableClause;
1154 $foreignTableClauseArray = [
1155 'WHERE' => '',
1156 'GROUPBY' => '',
1157 'ORDERBY' => '',
1158 'LIMIT' => '',
1159 ];
1160 // Find LIMIT
1161 $reg = [];
1162 if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/is', $foreignTableClause, $reg)) {
1163 $foreignTableClauseArray['LIMIT'] = GeneralUtility::intExplode(',', trim($reg[2]), true);
1164 $foreignTableClause = $reg[1];
1165 }
1166 // Find ORDER BY
1167 $reg = [];
1168 if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/is', $foreignTableClause, $reg)) {
1169 $foreignTableClauseArray['ORDERBY'] = QueryHelper::parseOrderBy(trim($reg[2]));
1170 $foreignTableClause = $reg[1];
1171 }
1172 // Find GROUP BY
1173 $reg = [];
1174 if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/is', $foreignTableClause, $reg)) {
1175 $foreignTableClauseArray['GROUPBY'] = QueryHelper::parseGroupBy(trim($reg[2]));
1176 $foreignTableClause = $reg[1];
1177 }
1178 // Rest is assumed to be "WHERE" clause
1179 $foreignTableClauseArray['WHERE'] = QueryHelper::stripLogicalOperatorPrefix($foreignTableClause);
1180
1181 return $foreignTableClauseArray;
1182 }
1183
1184 /**
1185 * Convert the current database values into an array
1186 *
1187 * @param array $row database row
1188 * @param string $fieldName fieldname to process
1189 * @return array
1190 */
1191 protected function processDatabaseFieldValue(array $row, $fieldName)
1192 {
1193 $currentDatabaseValues = array_key_exists($fieldName, $row)
1194 ? $row[$fieldName]
1195 : '';
1196 if (!is_array($currentDatabaseValues)) {
1197 $currentDatabaseValues = GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
1198 }
1199 return $currentDatabaseValues;
1200 }
1201
1202 /**
1203 * Validate and sanitize database row values of the select field with the given name.
1204 * Creates an array out of databaseRow[selectField] values.
1205 *
1206 * Used by TcaSelectItems and TcaSelectTreeItems data providers
1207 *
1208 * @param array $result The current result array.
1209 * @param string $fieldName Name of the current select field.
1210 * @param array $staticValues Array with statically defined items, item value is used as array key.
1211 * @return array
1212 */
1213 protected function processSelectFieldValue(array $result, $fieldName, array $staticValues)
1214 {
1215 $fieldConfig = $result['processedTca']['columns'][$fieldName];
1216
1217 $currentDatabaseValueArray = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : [];
1218 $newDatabaseValueArray = [];
1219
1220 // Add all values that were defined by static methods and do not come from the relation
1221 // e.g. TCA, TSconfig, itemProcFunc etc.
1222 foreach ($currentDatabaseValueArray as $value) {
1223 if (isset($staticValues[$value])) {
1224 $newDatabaseValueArray[] = $value;
1225 }
1226 }
1227
1228 if (isset($fieldConfig['config']['foreign_table']) && !empty($fieldConfig['config']['foreign_table'])) {
1229 /** @var RelationHandler $relationHandler */
1230 $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1231 $relationHandler->registerNonTableValues = !empty($fieldConfig['config']['allowNonIdValues']);
1232 if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') {
1233 // MM relation
1234 $relationHandler->start(
1235 implode(',', $currentDatabaseValueArray),
1236 $fieldConfig['config']['foreign_table'],
1237 $fieldConfig['config']['MM'],
1238 $result['databaseRow']['uid'],
1239 $result['tableName'],
1240 $fieldConfig['config']
1241 );
1242 } else {
1243 // Non MM relation
1244 // If not dealing with MM relations, use default live uid, not versioned uid for record relations
1245 $relationHandler->start(
1246 implode(',', $currentDatabaseValueArray),
1247 $fieldConfig['config']['foreign_table'],
1248 '',
1249 $this->getLiveUid($result),
1250 $result['tableName'],
1251 $fieldConfig['config']
1252 );
1253 }
1254 $newDatabaseValueArray = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
1255 }
1256
1257 if ($fieldConfig['config']['multiple']) {
1258 return $newDatabaseValueArray;
1259 }
1260 return array_unique($newDatabaseValueArray);
1261 }
1262
1263 /**
1264 * Translate the item labels
1265 *
1266 * Used by TcaSelectItems and TcaSelectTreeItems data providers
1267 *
1268 * @param array $result Result array
1269 * @param array $itemArray Items
1270 * @param string $table
1271 * @param string $fieldName
1272 * @return array
1273 */
1274 public function translateLabels(array $result, array $itemArray, $table, $fieldName)
1275 {
1276 $languageService = $this->getLanguageService();
1277
1278 foreach ($itemArray as $key => $item) {
1279 if (!isset($dynamicItems[$key])) {
1280 $staticValues[$item[1]] = $item;
1281 }
1282 if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
1283 && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
1284 ) {
1285 $label = $languageService->sL($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]]);
1286 } else {
1287 $label = $languageService->sL(trim($item[0]));
1288 }
1289 $value = strlen((string)$item[1]) > 0 ? $item[1] : '';
1290 $icon = $item[2] ?: null;
1291 $helpText = $item[3] ?: null;
1292 $itemArray[$key] = [
1293 $label,
1294 $value,
1295 $icon,
1296 $helpText
1297 ];
1298 }
1299
1300 return $itemArray;
1301 }
1302
1303 /**
1304 * Sanitize incoming item array
1305 *
1306 * Used by TcaSelectItems and TcaSelectTreeItems data providers
1307 *
1308 * @param mixed $itemArray
1309 * @param string $tableName
1310 * @param string $fieldName
1311 * @throws \UnexpectedValueException
1312 * @return array
1313 */
1314 public function sanitizeItemArray($itemArray, $tableName, $fieldName)
1315 {
1316 if (!is_array($itemArray)) {
1317 $itemArray = [];
1318 }
1319 foreach ($itemArray as $item) {
1320 if (!is_array($item)) {
1321 throw new \UnexpectedValueException(
1322 'An item in field ' . $fieldName . ' of table ' . $tableName . ' is not an array as expected',
1323 1439288036
1324 );
1325 }
1326 }
1327
1328 return $itemArray;
1329 }
1330
1331 /**
1332 * Gets the record uid of the live default record. If already
1333 * pointing to the live record, the submitted record uid is returned.
1334 *
1335 * @param array $result Result array
1336 * @return int
1337 * @throws \UnexpectedValueException
1338 */
1339 protected function getLiveUid(array $result)
1340 {
1341 $table = $result['tableName'];
1342 $row = $result['databaseRow'];
1343 $uid = $row['uid'];
1344 if (!empty($result['processedTca']['ctrl']['versioningWS'])
1345 && $result['pid'] === -1
1346 ) {
1347 if (empty($row['t3ver_oid'])) {
1348 throw new \UnexpectedValueException(
1349 'No t3ver_oid found for record ' . $row['uid'] . ' on table ' . $table,
1350 1440066481
1351 );
1352 }
1353 $uid = $row['t3ver_oid'];
1354 }
1355 return $uid;
1356 }
1357
1358 /**
1359 * Determine the static values in the item array
1360 *
1361 * Used by TcaSelectItems and TcaSelectTreeItems data providers
1362 *
1363 * @param array $itemArray All item records for the select field
1364 * @param array $dynamicItemArray Item records from dynamic sources
1365 * @return array
1366 * @todo: Check method usage, it's probably bogus in select context and was removed from select tree already.
1367 */
1368 protected function getStaticValues($itemArray, $dynamicItemArray)
1369 {
1370 $staticValues = [];
1371 foreach ($itemArray as $key => $item) {
1372 if (!isset($dynamicItemArray[$key])) {
1373 $staticValues[$item[1]] = $item;
1374 }
1375 }
1376 return $staticValues;
1377 }
1378
1379 /**
1380 * @return LanguageService
1381 */
1382 protected function getLanguageService()
1383 {
1384 return $GLOBALS['LANG'];
1385 }
1386
1387 /**
1388 * @return BackendUserAuthentication
1389 */
1390 protected function getBackendUser()
1391 {
1392 return $GLOBALS['BE_USER'];
1393 }
1394 }