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