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