[TASK] Use FormDataProvider to provide system-languages
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / TcaSelectItems.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\Configuration\TranslationConfigurationProvider;
18 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
19 use TYPO3\CMS\Backend\Module\ModuleLoader;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22 use TYPO3\CMS\Core\Database\DatabaseConnection;
23 use TYPO3\CMS\Core\Database\RelationHandler;
24 use TYPO3\CMS\Core\Imaging\IconFactory;
25 use TYPO3\CMS\Core\Messaging\FlashMessage;
26 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
27 use TYPO3\CMS\Core\Messaging\FlashMessageService;
28 use TYPO3\CMS\Core\Utility\ArrayUtility;
29 use TYPO3\CMS\Core\Utility\GeneralUtility;
30 use TYPO3\CMS\Core\Utility\MathUtility;
31 use TYPO3\CMS\Core\Utility\PathUtility;
32 use TYPO3\CMS\Lang\LanguageService;
33
34 /**
35 * Resolve select items, set processed item list in processedTca, sanitize and resolve database field
36 */
37 class TcaSelectItems extends AbstractItemProvider implements FormDataProviderInterface
38 {
39 /**
40 * Resolve select items
41 *
42 * @param array $result
43 * @return array
44 * @throws \UnexpectedValueException
45 */
46 public function addData(array $result)
47 {
48 $languageService = $this->getLanguageService();
49
50 $table = $result['tableName'];
51
52 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
53 if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'select') {
54 continue;
55 }
56
57 // Sanitize incoming item array
58 if (!is_array($fieldConfig['config']['items'])) {
59 $fieldConfig['config']['items'] = [];
60 }
61
62 // Make sure maxitems is always filled with a valid integer value.
63 if (
64 !empty($fieldConfig['config']['maxitems'])
65 && (int)$fieldConfig['config']['maxitems'] > 1
66 ) {
67 $fieldConfig['config']['maxitems'] = (int)$fieldConfig['config']['maxitems'];
68 } else {
69 $fieldConfig['config']['maxitems'] = 1;
70 }
71
72 foreach ($fieldConfig['config']['items'] as $item) {
73 if (!is_array($item)) {
74 throw new \UnexpectedValueException(
75 'An item in field ' . $fieldName . ' of table ' . $table . ' is not an array as expected',
76 1439288036
77 );
78 }
79 }
80
81 $fieldConfig['config']['items'] = $this->addItemsFromPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
82 $fieldConfig['config']['items'] = $this->addItemsFromSpecial($result, $fieldName, $fieldConfig['config']['items']);
83 $fieldConfig['config']['items'] = $this->addItemsFromFolder($result, $fieldName, $fieldConfig['config']['items']);
84 $staticItems = $fieldConfig['config']['items'];
85
86 $fieldConfig['config']['items'] = $this->addItemsFromForeignTable($result, $fieldName, $fieldConfig['config']['items']);
87 $dynamicItems = array_diff_key($fieldConfig['config']['items'], $staticItems);
88
89 $fieldConfig['config']['items'] = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
90 $fieldConfig['config']['items'] = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
91 $fieldConfig['config']['items'] = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $fieldConfig['config']['items']);
92 $fieldConfig['config']['items'] = $this->removeItemsByUserAuthMode($result, $fieldName, $fieldConfig['config']['items']);
93 $fieldConfig['config']['items'] = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $fieldConfig['config']['items']);
94
95 // Resolve "itemsProcFunc"
96 if (!empty($fieldConfig['config']['itemsProcFunc'])) {
97 $fieldConfig['config']['items'] = $this->resolveItemProcessorFunction($result, $fieldName, $fieldConfig['config']['items']);
98 // itemsProcFunc must not be used anymore
99 unset($fieldConfig['config']['itemsProcFunc']);
100 }
101
102 // Translate labels
103 $staticValues = [];
104 foreach ($fieldConfig['config']['items'] as $key => $item) {
105 if (!isset($dynamicItems[$key])) {
106 $staticValues[$item[1]] = $item;
107 }
108 if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
109 && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
110 ) {
111 $label = $languageService->sL($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]]);
112 } else {
113 $label = $languageService->sL($item[0]);
114 }
115 $value = strlen((string)$item[1]) > 0 ? $item[1] : '';
116 $icon = $item[2] ?: null;
117 $helpText = $item[3] ?: null;
118 $fieldConfig['config']['items'][$key] = [
119 $label,
120 $value,
121 $icon,
122 $helpText
123 ];
124 }
125 // Keys may contain table names, so a numeric array is created
126 $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']);
127
128 $result['processedTca']['columns'][$fieldName] = $fieldConfig;
129 $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $staticValues);
130 }
131
132 return $result;
133 }
134
135 /**
136 * TCA config "special" evaluation. Add them to $items
137 *
138 * @param array $result Result array
139 * @param string $fieldName Current handle field name
140 * @param array $items Incoming items
141 * @return array Modified item array
142 * @throws \UnexpectedValueException
143 */
144 protected function addItemsFromSpecial(array $result, $fieldName, array $items)
145 {
146 // Guard
147 if (empty($result['processedTca']['columns'][$fieldName]['config']['special'])
148 || !is_string($result['processedTca']['columns'][$fieldName]['config']['special'])
149 ) {
150 return $items;
151 }
152
153 $languageService = $this->getLanguageService();
154 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
155
156 $special = $result['processedTca']['columns'][$fieldName]['config']['special'];
157 if ($special === 'tables') {
158 foreach ($GLOBALS['TCA'] as $currentTable => $_) {
159 if (!empty($GLOBALS['TCA'][$currentTable]['ctrl']['adminOnly'])) {
160 // Hide "admin only" tables
161 continue;
162 }
163 $label = !empty($GLOBALS['TCA'][$currentTable]['ctrl']['title']) ? $GLOBALS['TCA'][$currentTable]['ctrl']['title'] : '';
164 $icon = $iconFactory->mapRecordTypeToIconIdentifier($currentTable, array());
165 $helpText = array();
166 $languageService->loadSingleTableDescription($currentTable);
167 // @todo: check if this actually works, currently help texts are missing
168 $helpTextArray = $GLOBALS['TCA_DESCR'][$currentTable]['columns'][''];
169 if (!empty($helpTextArray['description'])) {
170 $helpText['description'] = $helpTextArray['description'];
171 }
172 $items[] = array($label, $currentTable, $icon, $helpText);
173 }
174 } elseif ($special === 'pagetypes') {
175 if (isset($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
176 && is_array($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
177 ) {
178 $specialItems = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
179 foreach ($specialItems as $specialItem) {
180 if (!is_array($specialItem) || $specialItem[1] === '--div--') {
181 // Skip non arrays and divider items
182 continue;
183 }
184 $label = $specialItem[0];
185 $value = $specialItem[1];
186 $icon = $iconFactory->mapRecordTypeToIconIdentifier('pages', array('doktype' => $specialItem[1]));
187 $items[] = array($label, $value, $icon);
188 }
189 }
190 } elseif ($special === 'exclude') {
191 $excludeArrays = $this->getExcludeFields();
192 foreach ($excludeArrays as $excludeArray) {
193 list($theTable, $theFullField) = explode(':', $excludeArray[1]);
194 // If the field comes from a FlexForm, the syntax is more complex
195 $theFieldParts = explode(';', $theFullField);
196 $theField = array_pop($theFieldParts);
197 // Add header if not yet set for table:
198 if (!array_key_exists($theTable, $items)) {
199 $icon = $iconFactory->mapRecordTypeToIconIdentifier($theTable, array());
200 $items[$theTable] = array(
201 $GLOBALS['TCA'][$theTable]['ctrl']['title'],
202 '--div--',
203 $icon
204 );
205 }
206 // Add help text
207 $helpText = array();
208 $languageService->loadSingleTableDescription($theTable);
209 $helpTextArray = $GLOBALS['TCA_DESCR'][$theTable]['columns'][$theFullField];
210 if (!empty($helpTextArray['description'])) {
211 $helpText['description'] = $helpTextArray['description'];
212 }
213 // Item configuration:
214 // @todo: the title calculation does not work well for flex form fields, see unit tests
215 $items[] = array(
216 rtrim($languageService->sL($GLOBALS['TCA'][$theTable]['columns'][$theField]['label']), ':') . ' (' . $theField . ')',
217 $excludeArray[1],
218 'empty-empty',
219 $helpText
220 );
221 }
222 } elseif ($special === 'explicitValues') {
223 $theTypes = $this->getExplicitAuthFieldValues();
224 $icons = array(
225 'ALLOW' => 'status-status-permission-granted',
226 'DENY' => 'status-status-permission-denied'
227 );
228 // Traverse types:
229 foreach ($theTypes as $tableFieldKey => $theTypeArrays) {
230 if (is_array($theTypeArrays['items'])) {
231 // Add header:
232 $items[] = array(
233 $theTypeArrays['tableFieldLabel'],
234 '--div--',
235 );
236 // Traverse options for this field:
237 foreach ($theTypeArrays['items'] as $itemValue => $itemContent) {
238 // Add item to be selected:
239 $items[] = array(
240 '[' . $itemContent[2] . '] ' . $itemContent[1],
241 $tableFieldKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $itemContent[0],
242 $icons[$itemContent[0]]
243 );
244 }
245 }
246 }
247 } elseif ($special === 'languages') {
248 // @todo: This should probably use the data provided by DatabaseSystemLanguageRows sitting in $result['systemLanguageRows']
249 /** @var TranslationConfigurationProvider $translationConfigurationProvider */
250 $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
251 $languages = $translationConfigurationProvider->getSystemLanguages();
252 foreach ($languages as $language) {
253 if ($language['uid'] !== -1) {
254 $items[] = array(
255 0 => $language['title'] . ' [' . $language['uid'] . ']',
256 1 => $language['uid'],
257 2 => $language['flagIcon']
258 );
259 }
260 }
261 } elseif ($special === 'custom') {
262 $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'];
263 if (is_array($customOptions)) {
264 foreach ($customOptions as $coKey => $coValue) {
265 if (is_array($coValue['items'])) {
266 // Add header:
267 $items[] = array(
268 $languageService->sL($coValue['header']),
269 '--div--'
270 );
271 // Traverse items:
272 foreach ($coValue['items'] as $itemKey => $itemCfg) {
273 $icon = 'empty-empty';
274 $helpText = array();
275 if (!empty($itemCfg[2])) {
276 $helpText['description'] = $languageService->sL($itemCfg[2]);
277 }
278 $items[] = array(
279 $languageService->sL($itemCfg[0]),
280 $coKey . ':' . preg_replace('/[:|,]/', '', $itemKey),
281 $icon,
282 $helpText
283 );
284 }
285 }
286 }
287 }
288 } elseif ($special === 'modListGroup' || $special === 'modListUser') {
289 $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
290 $loadModules->load($GLOBALS['TBE_MODULES']);
291 $modList = $special === 'modListUser' ? $loadModules->modListUser : $loadModules->modListGroup;
292 if (is_array($modList)) {
293 foreach ($modList as $theMod) {
294 // Icon:
295 $icon = $languageService->moduleLabels['tabs_images'][$theMod . '_tab'];
296 if ($icon) {
297 $icon = '../' . PathUtility::stripPathSitePrefix($icon);
298 }
299 // Add help text
300 $helpText = array(
301 'title' => $languageService->moduleLabels['labels'][$theMod . '_tablabel'],
302 'description' => $languageService->moduleLabels['labels'][$theMod . '_tabdescr']
303 );
304
305 $label = '';
306 // Add label for main module:
307 $pp = explode('_', $theMod);
308 if (count($pp) > 1) {
309 $label .= $languageService->moduleLabels['tabs'][($pp[0] . '_tab')] . '>';
310 }
311 // Add modules own label now:
312 $label .= $languageService->moduleLabels['tabs'][$theMod . '_tab'];
313
314 // Item configuration:
315 $items[] = array($label, $theMod, $icon, $helpText);
316 }
317 }
318 } else {
319 throw new \UnexpectedValueException(
320 'Unknown special value ' . $special . ' for field ' . $fieldName . ' of table ' . $result['tableName'],
321 1439298496
322 );
323 }
324
325 return $items;
326 }
327
328 /**
329 * TCA config "fileFolder" evaluation. Add them to $items
330 *
331 * @param array $result Result array
332 * @param string $fieldName Current handle field name
333 * @param array $items Incoming items
334 * @return array Modified item array
335 */
336 protected function addItemsFromFolder(array $result, $fieldName, array $items)
337 {
338 if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
339 || !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
340 ) {
341 return $items;
342 }
343
344 $fileFolder = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
345 $fileFolder = GeneralUtility::getFileAbsFileName($fileFolder);
346 $fileFolder = rtrim($fileFolder, '/') . '/';
347
348 if (@is_dir($fileFolder)) {
349 $fileExtensionList = '';
350 if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
351 && is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
352 ) {
353 $fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
354 }
355 $recursionLevels = isset($fieldValue['config']['fileFolder_recursions'])
356 ? MathUtility::forceIntegerInRange($fieldValue['config']['fileFolder_recursions'], 0, 99)
357 : 99;
358 $fileArray = GeneralUtility::getAllFilesAndFoldersInPath(array(), $fileFolder, $fileExtensionList, 0, $recursionLevels);
359 $fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
360 foreach ($fileArray as $fileReference) {
361 $fileInformation = pathinfo($fileReference);
362 $icon = GeneralUtility::inList('gif,png,jpeg,jpg', strtolower($fileInformation['extension']))
363 ? '../' . PathUtility::stripPathSitePrefix($fileFolder) . $fileReference
364 : '';
365 $items[] = array(
366 $fileReference,
367 $fileReference,
368 $icon
369 );
370 }
371 }
372
373 return $items;
374 }
375
376 /**
377 * TCA config "foreign_table" evaluation. Add them to $items
378 *
379 * @param array $result Result array
380 * @param string $fieldName Current handle field name
381 * @param array $items Incoming items
382 * @return array Modified item array
383 */
384 protected function addItemsFromForeignTable(array $result, $fieldName, array $items)
385 {
386 // Guard
387 if (empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
388 || !is_string($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
389 ) {
390 return $items;
391 }
392
393 $languageService = $this->getLanguageService();
394 $database = $this->getDatabaseConnection();
395
396 $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
397 $foreignTableQueryArray = $this->buildForeignTableQuery($result, $fieldName);
398 $queryResource = $database->exec_SELECT_queryArray($foreignTableQueryArray);
399
400 // Early return on error with flash message
401 $databaseError = $database->sql_error();
402 if (!empty($databaseError)) {
403 $msg = htmlspecialchars($databaseError) . '<br />' . LF;
404 $msg .= $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch');
405 $msgTitle = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch_title');
406 /** @var $flashMessage FlashMessage */
407 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, true);
408 /** @var $flashMessageService FlashMessageService */
409 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
410 /** @var $defaultFlashMessageQueue FlashMessageQueue */
411 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
412 $defaultFlashMessageQueue->enqueue($flashMessage);
413 $database->sql_free_result($queryResource);
414 return $items;
415 }
416
417 $labelPrefix = '';
418 if (!empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'])) {
419 $labelPrefix = $result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'];
420 $labelPrefix = $languageService->sL($labelPrefix);
421 }
422 $iconFieldName = '';
423 if (!empty($result['processedTca']['ctrl']['selicon_field'])) {
424 $iconFieldName = $result['processedTca']['ctrl']['selicon_field'];
425 }
426 $iconPath = '';
427 if (!empty($result['processedTca']['ctrl']['selicon_field_path'])) {
428 $iconPath = $result['processedTca']['ctrl']['selicon_field_path'];
429 }
430
431 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
432
433 while ($foreignRow = $database->sql_fetch_assoc($queryResource)) {
434 BackendUtility::workspaceOL($foreignTable, $foreignRow);
435 if (is_array($foreignRow)) {
436 // Prepare the icon if available:
437 if ($iconFieldName && $iconPath && $foreignRow[$iconFieldName]) {
438 $iParts = GeneralUtility::trimExplode(',', $foreignRow[$iconFieldName], true);
439 $icon = '../' . $iconPath . '/' . trim($iParts[0]);
440 } else {
441 $icon = $iconFactory->mapRecordTypeToIconIdentifier($foreignTable, $foreignRow);
442 }
443 // Add the item
444 $items[] = array(
445 $labelPrefix . htmlspecialchars(BackendUtility::getRecordTitle($foreignTable, $foreignRow)),
446 $foreignRow['uid'],
447 $icon
448 );
449 }
450 }
451
452 $database->sql_free_result($queryResource);
453
454 return $items;
455 }
456
457 /**
458 * Remove items using "keepItems" pageTsConfig
459 *
460 * @param array $result Result array
461 * @param string $fieldName Current handle field name
462 * @param array $items Incoming items
463 * @return array Modified item array
464 */
465 protected function removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
466 {
467 $table = $result['tableName'];
468 if (empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
469 || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
470 ) {
471 return $items;
472 }
473
474 return ArrayUtility::keepItemsInArray(
475 $items,
476 $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'],
477 function ($value) {
478 return $value[1];
479 }
480 );
481 }
482
483 /**
484 * Remove items using "removeItems" pageTsConfig
485 *
486 * @param array $result Result array
487 * @param string $fieldName Current handle field name
488 * @param array $items Incoming items
489 * @return array Modified item array
490 */
491 protected function removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
492 {
493 $table = $result['tableName'];
494 if (empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
495 || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
496 ) {
497 return $items;
498 }
499
500 $removeItems = GeneralUtility::trimExplode(
501 ',',
502 $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'],
503 true
504 );
505 foreach ($items as $key => $itemValues) {
506 if (in_array($itemValues[1], $removeItems)) {
507 unset($items[$key]);
508 }
509 }
510
511 return $items;
512 }
513
514 /**
515 * Remove items user restriction on language field
516 *
517 * @param array $result Result array
518 * @param string $fieldName Current handle field name
519 * @param array $items Incoming items
520 * @return array Modified item array
521 */
522 protected function removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
523 {
524 // Guard clause returns if not a language field is handled
525 if (empty($result['processedTca']['ctrl']['languageField'])
526 || $result['processedTca']['ctrl']['languageField'] !== $fieldName
527 ) {
528 return $items;
529 }
530
531 $backendUser = $this->getBackendUser();
532 foreach ($items as $key => $itemValues) {
533 if (!$backendUser->checkLanguageAccess($itemValues[1])) {
534 unset($items[$key]);
535 }
536 }
537
538 return $items;
539 }
540
541 /**
542 * Remove items by user restriction on authMode items
543 *
544 * @param array $result Result array
545 * @param string $fieldName Current handle field name
546 * @param array $items Incoming items
547 * @return array Modified item array
548 */
549 protected function removeItemsByUserAuthMode(array $result, $fieldName, array $items)
550 {
551 // Guard clause returns early if no authMode field is configured
552 if (!isset($result['processedTca']['columns'][$fieldName]['config']['authMode'])
553 || !is_string($result['processedTca']['columns'][$fieldName]['config']['authMode'])
554 ) {
555 return $items;
556 }
557
558 $backendUser = $this->getBackendUser();
559 $authMode = $result['processedTca']['columns'][$fieldName]['config']['authMode'];
560 foreach ($items as $key => $itemValues) {
561 // @todo: checkAuthMode() uses $GLOBAL access for "individual" authMode - get rid of this
562 if (!$backendUser->checkAuthMode($result['tableName'], $fieldName, $itemValues[1], $authMode)) {
563 unset($items[$key]);
564 }
565 }
566
567 return $items;
568 }
569
570 /**
571 * Remove items if doktype is handled for non admin users
572 *
573 * @param array $result Result array
574 * @param string $fieldName Current handle field name
575 * @param array $items Incoming items
576 * @return array Modified item array
577 */
578 protected function removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
579 {
580 $table = $result['tableName'];
581 $backendUser = $this->getBackendUser();
582 // Guard clause returns if not correct table and field or if user is admin
583 if ($table !== 'pages' && $table !== 'pages_language_overlay'
584 || $fieldName !== 'doktype' || $backendUser->isAdmin()
585 ) {
586 return $items;
587 }
588
589 $allowedPageTypes = $backendUser->groupData['pagetypes_select'];
590 foreach ($items as $key => $itemValues) {
591 if (!GeneralUtility::inList($allowedPageTypes, $itemValues[1])) {
592 unset($items[$key]);
593 }
594 }
595
596 return $items;
597 }
598
599 /**
600 * Returns an array with the exclude fields as defined in TCA and FlexForms
601 * Used for listing the exclude fields in be_groups forms.
602 *
603 * @return array Array of arrays with excludeFields (fieldName, table:fieldName) from TCA
604 * and FlexForms (fieldName, table:extKey;sheetName;fieldName)
605 */
606 protected function getExcludeFields()
607 {
608 $languageService = $this->getLanguageService();
609 $finalExcludeArray = array();
610
611 // Fetch translations for table names
612 $tableToTranslation = array();
613 // All TCA keys
614 foreach ($GLOBALS['TCA'] as $table => $conf) {
615 $tableToTranslation[$table] = $languageService->sl($conf['ctrl']['title']);
616 }
617 // Sort by translations
618 asort($tableToTranslation);
619 foreach ($tableToTranslation as $table => $translatedTable) {
620 $excludeArrayTable = array();
621
622 // All field names configured and not restricted to admins
623 if (is_array($GLOBALS['TCA'][$table]['columns'])
624 && empty($GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
625 && (empty($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
626 ) {
627 foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
628 if ($GLOBALS['TCA'][$table]['columns'][$field]['exclude']) {
629 // Get human readable names of fields
630 $translatedField = $languageService->sl($GLOBALS['TCA'][$table]['columns'][$field]['label']);
631 // Add entry
632 $excludeArrayTable[] = array($translatedTable . ': ' . $translatedField, $table . ':' . $field);
633 }
634 }
635 }
636 // All FlexForm fields
637 $flexFormArray = $this->getRegisteredFlexForms($table);
638 foreach ($flexFormArray as $tableField => $flexForms) {
639 // Prefix for field label, e.g. "Plugin Options:"
640 $labelPrefix = '';
641 if (!empty($GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
642 $labelPrefix = $languageService->sl($GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
643 }
644 // Get all sheets and title
645 foreach ($flexForms as $extIdent => $extConf) {
646 $extTitle = $languageService->sl($extConf['title']);
647 // Get all fields in sheet
648 foreach ($extConf['ds']['sheets'] as $sheetName => $sheet) {
649 if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
650 continue;
651 }
652 foreach ($sheet['ROOT']['el'] as $fieldName => $field) {
653 // Use only fields that have exclude flag set
654 if (empty($field['TCEforms']['exclude'])) {
655 continue;
656 }
657 $fieldLabel = !empty($field['TCEforms']['label']) ? $languageService->sl($field['TCEforms']['label']) : $fieldName;
658 $fieldIdent = $table . ':' . $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $fieldName;
659 $excludeArrayTable[] = array(trim($labelPrefix . ' ' . $extTitle, ': ') . ': ' . $fieldLabel, $fieldIdent);
660 }
661 }
662 }
663 }
664 // Sort fields by the translated value
665 if (!empty($excludeArrayTable)) {
666 usort($excludeArrayTable, function (array $array1, array $array2) {
667 $array1 = reset($array1);
668 $array2 = reset($array2);
669 if (is_string($array1) && is_string($array2)) {
670 return strcasecmp($array1, $array2);
671 }
672 return 0;
673 });
674 $finalExcludeArray = array_merge($finalExcludeArray, $excludeArrayTable);
675 }
676 }
677
678 return $finalExcludeArray;
679 }
680
681 /**
682 * Returns all registered FlexForm definitions with title and fields
683 *
684 * @param string $table Table to handle
685 * @return array Data structures with speaking extension title
686 */
687 protected function getRegisteredFlexForms($table)
688 {
689 if (empty($table) || empty($GLOBALS['TCA'][$table]['columns'])) {
690 return array();
691 }
692 $flexForms = array();
693 foreach ($GLOBALS['TCA'][$table]['columns'] as $tableField => $fieldConf) {
694 if (!empty($fieldConf['config']['type']) && !empty($fieldConf['config']['ds']) && $fieldConf['config']['type'] == 'flex') {
695 $flexForms[$tableField] = array();
696 unset($fieldConf['config']['ds']['default']);
697 // Get pointer fields
698 $pointerFields = !empty($fieldConf['config']['ds_pointerField']) ? $fieldConf['config']['ds_pointerField'] : 'list_type,CType';
699 $pointerFields = GeneralUtility::trimExplode(',', $pointerFields);
700 // Get FlexForms
701 foreach ($fieldConf['config']['ds'] as $flexFormKey => $dataStructure) {
702 // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
703 $identFields = GeneralUtility::trimExplode(',', $flexFormKey);
704 $extIdent = $identFields[0];
705 if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
706 $extIdent = $identFields[1];
707 }
708 // Load external file references
709 if (!is_array($dataStructure)) {
710 $file = GeneralUtility::getFileAbsFileName(str_ireplace('FILE:', '', $dataStructure));
711 if ($file && @is_file($file)) {
712 $dataStructure = GeneralUtility::getUrl($file);
713 }
714 $dataStructure = GeneralUtility::xml2array($dataStructure);
715 if (!is_array($dataStructure)) {
716 continue;
717 }
718 }
719 // Get flexform content
720 $dataStructure = GeneralUtility::resolveAllSheetsInDS($dataStructure);
721 if (empty($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
722 continue;
723 }
724 // Use DS pointer to get extension title from TCA
725 // @todo: I don't understand this code ... does it make sense at all?
726 $title = $extIdent;
727 $keyFields = GeneralUtility::trimExplode(',', $flexFormKey);
728 foreach ($pointerFields as $pointerKey => $pointerName) {
729 if (empty($keyFields[$pointerKey]) || $keyFields[$pointerKey] === '*' || $keyFields[$pointerKey] === 'list') {
730 continue;
731 }
732 if (!empty($GLOBALS['TCA'][$table]['columns'][$pointerName]['config']['items'])) {
733 $items = $GLOBALS['TCA'][$table]['columns'][$pointerName]['config']['items'];
734 if (!is_array($items)) {
735 continue;
736 }
737 foreach ($items as $itemConf) {
738 if (!empty($itemConf[0]) && !empty($itemConf[1]) && $itemConf[1] == $keyFields[$pointerKey]) {
739 $title = $itemConf[0];
740 break 2;
741 }
742 }
743 }
744 }
745 $flexForms[$tableField][$extIdent] = array(
746 'title' => $title,
747 'ds' => $dataStructure
748 );
749 }
750 }
751 }
752 return $flexForms;
753 }
754
755 /**
756 * Returns an array with explicit Allow/Deny fields.
757 * Used for listing these field/value pairs in be_groups forms
758 *
759 * @return array Array with information from all of $GLOBALS['TCA']
760 */
761 protected function getExplicitAuthFieldValues()
762 {
763 $languageService = static::getLanguageService();
764 $adLabel = array(
765 'ALLOW' => $languageService->sl('LLL:EXT:lang/locallang_core.xlf:labels.allow'),
766 'DENY' => $languageService->sl('LLL:EXT:lang/locallang_core.xlf:labels.deny')
767 );
768 $allowDenyOptions = array();
769 foreach ($GLOBALS['TCA'] as $table => $_) {
770 // All field names configured:
771 if (is_array($GLOBALS['TCA'][$table]['columns'])) {
772 foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
773 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
774 if ($fieldConfig['type'] === 'select' && $fieldConfig['authMode']) {
775 // Check for items
776 if (is_array($fieldConfig['items'])) {
777 // Get Human Readable names of fields and table:
778 $allowDenyOptions[$table . ':' . $field]['tableFieldLabel'] =
779 $languageService->sl($GLOBALS['TCA'][$table]['ctrl']['title']) . ': '
780 . $languageService->sl($GLOBALS['TCA'][$table]['columns'][$field]['label']);
781 foreach ($fieldConfig['items'] as $iVal) {
782 // Values '' is not controlled by this setting.
783 if ((string)$iVal[1] !== '') {
784 // Find iMode
785 $iMode = '';
786 switch ((string)$fieldConfig['authMode']) {
787 case 'explicitAllow':
788 $iMode = 'ALLOW';
789 break;
790 case 'explicitDeny':
791 $iMode = 'DENY';
792 break;
793 case 'individual':
794 if ($iVal[4] === 'EXPL_ALLOW') {
795 $iMode = 'ALLOW';
796 } elseif ($iVal[4] === 'EXPL_DENY') {
797 $iMode = 'DENY';
798 }
799 break;
800 }
801 // Set iMode
802 if ($iMode) {
803 $allowDenyOptions[$table . ':' . $field]['items'][$iVal[1]] = array($iMode, $languageService->sl($iVal[0]), $adLabel[$iMode]);
804 }
805 }
806 }
807 }
808 }
809 }
810 }
811 }
812 return $allowDenyOptions;
813 }
814
815 /**
816 * Build query to fetch foreign records
817 *
818 * @param array $result Result array
819 * @param string $localFieldName Current handle field name
820 * @return array Query array ready to be executed via Database->exec_SELECT_queryArray()
821 * @throws \UnexpectedValueException
822 */
823 protected function buildForeignTableQuery(array $result, $localFieldName)
824 {
825 $backendUser = $this->getBackendUser();
826
827 $foreignTableName = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table'];
828
829 if (!is_array($GLOBALS['TCA'][$foreignTableName])) {
830 throw new \UnexpectedValueException(
831 'Field ' . $localFieldName . ' of table ' . $result['tableName'] . ' reference to foreign table '
832 . $foreignTableName . ', but this table is not defined in TCA',
833 1439569743
834 );
835 }
836
837 $foreignTableClauseArray = $this->processForeignTableClause($result, $foreignTableName, $localFieldName);
838
839 $queryArray = array();
840 $queryArray['SELECT'] = BackendUtility::getCommonSelectFields($foreignTableName, $foreignTableName . '.');
841
842 // rootLevel = -1 means that elements can be on the rootlevel OR on any page (pid!=-1)
843 // rootLevel = 0 means that elements are not allowed on root level
844 // rootLevel = 1 means that elements are only on the root level (pid=0)
845 $rootLevel = 0;
846 if (isset($GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'])) {
847 $rootLevel = $GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'];
848 }
849 $deleteClause = BackendUtility::deleteClause($foreignTableName);
850 if ($rootLevel == 1 || $rootLevel == -1) {
851 $pidWhere = $foreignTableName . '.pid' . (($rootLevel == -1) ? '<>-1' : '=0');
852 $queryArray['FROM'] = $foreignTableName;
853 $queryArray['WHERE'] = $pidWhere . $deleteClause . $foreignTableClauseArray['WHERE'];
854 } else {
855 $pageClause = $backendUser->getPagePermsClause(1);
856 if ($foreignTableName === 'pages') {
857 $queryArray['FROM'] = 'pages';
858 $queryArray['WHERE'] = '1=1' . $deleteClause . ' AND' . $pageClause . $foreignTableClauseArray['WHERE'];
859 } else {
860 $queryArray['FROM'] = $foreignTableName . ', pages';
861 $queryArray['WHERE'] = 'pages.uid=' . $foreignTableName . '.pid AND pages.deleted=0'
862 . $deleteClause . ' AND' . $pageClause . $foreignTableClauseArray['WHERE'];
863 }
864 }
865
866 $queryArray['GROUPBY'] = $foreignTableClauseArray['GROUPBY'];
867 $queryArray['ORDERBY'] = $foreignTableClauseArray['ORDERBY'];
868 $queryArray['LIMIT'] = $foreignTableClauseArray['LIMIT'];
869
870 return $queryArray;
871 }
872
873 /**
874 * Replace markers in a where clause from TCA foreign_table_where
875 *
876 * ###REC_FIELD_[field name]###
877 * ###THIS_UID### - is current element uid (zero if new).
878 * ###CURRENT_PID### - is the current page id (pid of the record).
879 * ###SITEROOT###
880 * ###PAGE_TSCONFIG_ID### - a value you can set from Page TSconfig dynamically.
881 * ###PAGE_TSCONFIG_IDLIST### - a value you can set from Page TSconfig dynamically.
882 * ###PAGE_TSCONFIG_STR### - a value you can set from Page TSconfig dynamically.
883 *
884 * @param array $result Result array
885 * @param string $foreignTableName Name of foreign table
886 * @param string $localFieldName Current handle field name
887 * @return array Query parts with keys WHERE, ORDERBY, GROUPBY, LIMIT
888 */
889 protected function processForeignTableClause(array $result, $foreignTableName, $localFieldName)
890 {
891 $database = $this->getDatabaseConnection();
892 $localTable = $result['tableName'];
893
894 $foreignTableClause = '';
895 if (!empty($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
896 && is_string($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
897 ) {
898 $foreignTableClause = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'];
899 // Replace possible markers in query
900 if (strstr($foreignTableClause, '###REC_FIELD_')) {
901 // " AND table.field='###REC_FIELD_field1###' AND ..." -> array(" AND table.field='", "field1###' AND ...")
902 $whereClauseParts = explode('###REC_FIELD_', $foreignTableClause);
903 foreach ($whereClauseParts as $key => $value) {
904 if ($key !== 0) {
905 // "field1###' AND ..." -> array("field1", "' AND ...")
906 $whereClauseSubParts = explode('###', $value, 2);
907 // @todo: Throw exception if there is no value? What happens for NEW records?
908 $rowFieldValue = $result['databaseRow'][$whereClauseSubParts[0]];
909 if (is_array($rowFieldValue)) {
910 // If a select or group field is used here, it may have been processed already and
911 // is now an array. Use first selected value in this case.
912 $rowFieldValue = $rowFieldValue[0];
913 }
914 if (substr($whereClauseParts[0], -1) === '\'' && $whereClauseSubParts[1][0] === '\'') {
915 $whereClauseParts[$key] = $database->quoteStr($rowFieldValue, $foreignTableName) . $whereClauseSubParts[1];
916 } else {
917 $whereClauseParts[$key] = $database->fullQuoteStr($rowFieldValue, $foreignTableName) . $whereClauseSubParts[1];
918 }
919 }
920 }
921 $foreignTableClause = implode('', $whereClauseParts);
922 }
923
924 $siteRootUid = 0;
925 foreach ($result['rootline'] as $rootlinePage) {
926 if (!empty($rootlinePage['is_siteroot'])) {
927 $siteRootUid = (int)$rootlinePage['uid'];
928 break;
929 }
930 }
931 $pageTsConfigId = 0;
932 if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID']) {
933 $pageTsConfigId = (int)$result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'];
934 }
935 $pageTsConfigIdList = 0;
936 if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST']) {
937 $pageTsConfigIdList = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'];
938 $pageTsConfigIdListArray = GeneralUtility::trimExplode(',', $pageTsConfigIdList, true);
939 $pageTsConfigIdList = array();
940 foreach ($pageTsConfigIdListArray as $pageTsConfigIdListElement) {
941 if (MathUtility::canBeInterpretedAsInteger($pageTsConfigIdListElement)) {
942 $pageTsConfigIdList[] = (int)$pageTsConfigIdListElement;
943 }
944 }
945 $pageTsConfigIdList = implode(',', $pageTsConfigIdList);
946 }
947 $pageTsConfigString = '';
948 if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR']) {
949 $pageTsConfigString = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'];
950 $pageTsConfigString = $database->quoteStr($pageTsConfigString, $foreignTableName);
951 }
952
953 $foreignTableClause = str_replace(
954 array(
955 '###CURRENT_PID###',
956 '###THIS_UID###',
957 '###SITEROOT###',
958 '###PAGE_TSCONFIG_ID###',
959 '###PAGE_TSCONFIG_IDLIST###',
960 '###PAGE_TSCONFIG_STR###'
961 ),
962 array(
963 (int)$result['effectivePid'],
964 (int)$result['databaseRow']['uid'],
965 $siteRootUid,
966 $pageTsConfigId,
967 $pageTsConfigIdList,
968 $pageTsConfigString
969 ),
970 $foreignTableClause
971 );
972 }
973
974 // Split the clause into an array with keys WHERE, GROUPBY, ORDERBY, LIMIT
975 // Prepend a space to make sure "[[:space:]]+" will find a space there for the first element.
976 $foreignTableClause = ' ' . $foreignTableClause;
977 $foreignTableClauseArray = array(
978 'WHERE' => '',
979 'GROUPBY' => '',
980 'ORDERBY' => '',
981 'LIMIT' => '',
982 );
983 // Find LIMIT
984 $reg = array();
985 if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
986 $foreignTableClauseArray['LIMIT'] = trim($reg[2]);
987 $foreignTableClause = $reg[1];
988 }
989 // Find ORDER BY
990 $reg = array();
991 if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
992 $foreignTableClauseArray['ORDERBY'] = trim($reg[2]);
993 $foreignTableClause = $reg[1];
994 }
995 // Find GROUP BY
996 $reg = array();
997 if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
998 $foreignTableClauseArray['GROUPBY'] = trim($reg[2]);
999 $foreignTableClause = $reg[1];
1000 }
1001 // Rest is assumed to be "WHERE" clause
1002 $foreignTableClauseArray['WHERE'] = $foreignTableClause;
1003
1004 return $foreignTableClauseArray;
1005 }
1006
1007 /**
1008 * Validate and sanitize database row values of the select field with the given name.
1009 * Creates an array out of databaseRow[selectField] values.
1010 *
1011 * @param array $result The current result array.
1012 * @param string $fieldName Name of the current select field.
1013 * @param array $staticValues Array with statically defined items, item value is used as array key.
1014 * @return array
1015 */
1016 protected function processSelectFieldValue(array $result, $fieldName, array $staticValues)
1017 {
1018 $fieldConfig = $result['processedTca']['columns'][$fieldName];
1019
1020 // For single select fields we just keep the current value because the renderer
1021 // will take care of showing the "Invalid value" text.
1022 // For maxitems=1 select fields is is also possible to select empty values.
1023 // @todo: move handling of invalid values to this data provider.
1024 if ($fieldConfig['config']['maxitems'] === 1 && empty($fieldConfig['config']['MM'])) {
1025 return array($result['databaseRow'][$fieldName]);
1026 }
1027
1028 $currentDatabaseValues = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : '';
1029 // Selecting empty values does not make sense for fields that can contain more than one item
1030 // because it is impossible to determine if the empty value or nothing is selected.
1031 // This is why empty values will be removed for multi value fields.
1032 $currentDatabaseValuesArray = GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
1033 $newDatabaseValueArray = [];
1034
1035 // Add all values that were defined by static methods and do not come from the relation
1036 // e.g. TCA, TSconfig, itemProcFunc etc.
1037 foreach ($currentDatabaseValuesArray as $value) {
1038 if (isset($staticValues[$value])) {
1039 $newDatabaseValueArray[] = $value;
1040 }
1041 }
1042
1043 if (isset($fieldConfig['config']['foreign_table']) && !empty($fieldConfig['config']['foreign_table'])) {
1044 /** @var RelationHandler $relationHandler */
1045 $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1046 $relationHandler->registerNonTableValues = !empty($fieldConfig['config']['allowNonIdValues']);
1047 if (isset($fieldConfig['config']['MM']) && !empty($fieldConfig['config']['MM'])) {
1048 // MM relation
1049 $relationHandler->start(
1050 $currentDatabaseValues,
1051 $fieldConfig['config']['foreign_table'],
1052 $fieldConfig['config']['MM'],
1053 $result['databaseRow']['uid'],
1054 $result['tableName'],
1055 $fieldConfig['config']
1056 );
1057 } else {
1058 // Non MM relation
1059 // If not dealing with MM relations, use default live uid, not versioned uid for record relations
1060 $relationHandler->start(
1061 $currentDatabaseValues,
1062 $fieldConfig['config']['foreign_table'],
1063 '',
1064 $this->getLiveUid($result),
1065 $result['tableName'],
1066 $fieldConfig['config']
1067 );
1068 }
1069 $newDatabaseValueArray = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
1070 }
1071
1072 return array_unique($newDatabaseValueArray);
1073 }
1074
1075 /**
1076 * Gets the record uid of the live default record. If already
1077 * pointing to the live record, the submitted record uid is returned.
1078 *
1079 * @param array $result Result array
1080 * @return int
1081 * @throws \UnexpectedValueException
1082 */
1083 protected function getLiveUid(array $result)
1084 {
1085 $table = $result['tableName'];
1086 $row = $result['databaseRow'];
1087 $uid = $row['uid'];
1088 if (!empty($result['processedTca']['ctrl']['versioningWS'])
1089 && $result['pid'] === -1
1090 ) {
1091 if (empty($row['t3ver_oid'])) {
1092 throw new \UnexpectedValueException(
1093 'No t3ver_oid found for record ' . $row['uid'] . ' on table ' . $table,
1094 1440066481
1095 );
1096 }
1097 $uid = $row['t3ver_oid'];
1098 }
1099 return $uid;
1100 }
1101
1102 /**
1103 * @return LanguageService
1104 */
1105 protected function getLanguageService()
1106 {
1107 return $GLOBALS['LANG'];
1108 }
1109
1110 /**
1111 * @return DatabaseConnection
1112 */
1113 protected function getDatabaseConnection()
1114 {
1115 return $GLOBALS['TYPO3_DB'];
1116 }
1117
1118 /**
1119 * @return BackendUserAuthentication
1120 */
1121 protected function getBackendUser()
1122 {
1123 return $GLOBALS['BE_USER'];
1124 }
1125 }