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