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