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