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