Passed
Push — master ( 03f78e...954a3f )
by
unknown
12:06
created

AbstractItemProvider::getAllSites()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Backend\Form\FormDataProvider;
17
18
use Doctrine\DBAL\Exception as DBALException;
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\Configuration\FlexForm\Exception\InvalidIdentifierException;
23
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
24
use TYPO3\CMS\Core\Database\ConnectionPool;
25
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
26
use TYPO3\CMS\Core\Database\Query\QueryHelper;
27
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
28
use TYPO3\CMS\Core\Database\RelationHandler;
29
use TYPO3\CMS\Core\Imaging\IconFactory;
30
use TYPO3\CMS\Core\Imaging\IconRegistry;
31
use TYPO3\CMS\Core\Localization\LanguageService;
32
use TYPO3\CMS\Core\Messaging\FlashMessage;
33
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
34
use TYPO3\CMS\Core\Messaging\FlashMessageService;
35
use TYPO3\CMS\Core\Resource\FileRepository;
36
use TYPO3\CMS\Core\Resource\ResourceStorage;
37
use TYPO3\CMS\Core\Type\Bitmask\Permission;
38
use TYPO3\CMS\Core\Utility\ArrayUtility;
39
use TYPO3\CMS\Core\Utility\GeneralUtility;
40
use TYPO3\CMS\Core\Utility\MathUtility;
41
42
/**
43
 * Contains methods used by Data providers that handle elements
44
 * with single items like select, radio and some more.
45
 */
46
abstract class AbstractItemProvider
47
{
48
    /**
49
     * Resolve "itemProcFunc" of elements.
50
     *
51
     * @param array $result Main result array
52
     * @param string $fieldName Field name to handle item list for
53
     * @param array $items Existing items array
54
     * @return array New list of item elements
55
     */
56
    protected function resolveItemProcessorFunction(array $result, $fieldName, array $items)
57
    {
58
        $table = $result['tableName'];
59
        $config = $result['processedTca']['columns'][$fieldName]['config'];
60
61
        $pageTsProcessorParameters = null;
62
        if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'])) {
63
            $pageTsProcessorParameters = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'];
64
        }
65
        $processorParameters = [
66
            // Function manipulates $items directly and return nothing
67
            'items' => &$items,
68
            'config' => $config,
69
            'TSconfig' => $pageTsProcessorParameters,
70
            'table' => $table,
71
            'row' => $result['databaseRow'],
72
            'field' => $fieldName,
73
        ];
74
        if (!empty($result['flexParentDatabaseRow'])) {
75
            $processorParameters['flexParentDatabaseRow'] = $result['flexParentDatabaseRow'];
76
        }
77
78
        try {
79
            GeneralUtility::callUserFunction($config['itemsProcFunc'], $processorParameters, $this);
80
        } catch (\Exception $exception) {
81
            // The itemsProcFunc method may throw an exception, create a flash message if so
82
            $languageService = $this->getLanguageService();
0 ignored issues
show
Bug introduced by
The method getLanguageService() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

82
            /** @scrutinizer ignore-call */ 
83
            $languageService = $this->getLanguageService();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
83
            $fieldLabel = $fieldName;
84
            if (!empty($result['processedTca']['columns'][$fieldName]['label'])) {
85
                $fieldLabel = $languageService->sL($result['processedTca']['columns'][$fieldName]['label']);
86
            }
87
            $message = sprintf(
88
                $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.items_proc_func_error'),
89
                $fieldLabel,
90
                $exception->getMessage()
91
            );
92
            /** @var FlashMessage $flashMessage */
93
            $flashMessage = GeneralUtility::makeInstance(
94
                FlashMessage::class,
95
                $message,
96
                '',
97
                FlashMessage::ERROR,
98
                true
99
            );
100
            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
101
            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
102
            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
103
            $defaultFlashMessageQueue->enqueue($flashMessage);
104
        }
105
106
        return $items;
107
    }
108
109
    /**
110
     * PageTsConfig addItems:
111
     *
112
     * TCEFORMS.aTable.aField[.types][.aType].addItems.aValue = aLabel,
113
     * with type specific options merged by pageTsConfig already
114
     *
115
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
116
     *
117
     * @param array $result result array
118
     * @param string $fieldName Current handle field name
119
     * @param array $items Incoming items
120
     * @return array Modified item array
121
     */
122
    protected function addItemsFromPageTsConfig(array $result, $fieldName, array $items)
123
    {
124
        $table = $result['tableName'];
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
                $iconIdentifier = null;
136
                if (isset($addItemsArray[$value . '.'])
137
                    && is_array($addItemsArray[$value . '.'])
138
                    && !empty($addItemsArray[$value . '.']['icon'])
139
                ) {
140
                    $iconIdentifier = $addItemsArray[$value . '.']['icon'];
141
                }
142
                $items[] = [$label, $value, $iconIdentifier];
143
            }
144
        }
145
        return $items;
146
    }
147
148
    /**
149
     * TCA config "special" evaluation. Add them to $items
150
     *
151
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
152
     *
153
     * @param array $result Result array
154
     * @param string $fieldName Current handle field name
155
     * @param array $items Incoming items
156
     * @return array Modified item array
157
     * @throws \UnexpectedValueException
158
     */
159
    protected function addItemsFromSpecial(array $result, $fieldName, array $items)
160
    {
161
        // Guard
162
        if (empty($result['processedTca']['columns'][$fieldName]['config']['special'])
163
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['special'])
164
        ) {
165
            return $items;
166
        }
167
168
        $languageService = $this->getLanguageService();
169
        $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
170
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
171
172
        $special = $result['processedTca']['columns'][$fieldName]['config']['special'];
173
        switch (true) {
174
            case $special === 'tables':
175
                foreach ($GLOBALS['TCA'] as $currentTable => $_) {
176
                    if (!empty($GLOBALS['TCA'][$currentTable]['ctrl']['adminOnly'])) {
177
                        // Hide "admin only" tables
178
                        continue;
179
                    }
180
                    $label = !empty($GLOBALS['TCA'][$currentTable]['ctrl']['title']) ? $GLOBALS['TCA'][$currentTable]['ctrl']['title'] : '';
181
                    $icon = $iconFactory->mapRecordTypeToIconIdentifier($currentTable, []);
182
                    $helpText = [];
183
                    $languageService->loadSingleTableDescription($currentTable);
184
                    // @todo: check if this actually works, currently help texts are missing
185
                    $helpTextArray = $GLOBALS['TCA_DESCR'][$currentTable]['columns'][''];
186
                    if (!empty($helpTextArray['description'])) {
187
                        $helpText['description'] = $helpTextArray['description'];
188
                    }
189
                    $items[] = [$label, $currentTable, $icon, null, $helpText];
190
                }
191
                break;
192
            case $special === 'pagetypes':
193
                if (isset($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
194
                    && is_array($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
195
                ) {
196
                    $specialItems = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
197
                    foreach ($specialItems as $specialItem) {
198
                        if (!is_array($specialItem) || $specialItem[1] === '--div--') {
199
                            // Skip non arrays and divider items
200
                            continue;
201
                        }
202
                        $label = $specialItem[0];
203
                        $value = $specialItem[1];
204
                        $icon = $iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $specialItem[1]]);
205
                        $items[] = [$label, $value, $icon];
206
                    }
207
                }
208
                break;
209
            case $special === 'exclude':
210
                $excludeArrays = $this->getExcludeFields();
211
                foreach ($excludeArrays as $excludeArray) {
212
                    // If the field comes from a FlexForm, the syntax is more complex
213
                    if ($excludeArray['origin'] === 'flexForm') {
214
                        // The field comes from a plugins FlexForm
215
                        // Add header if not yet set for plugin section
216
                        if (!isset($items[$excludeArray['sectionHeader']])) {
217
                            // there is no icon handling for plugins - we take the icon from the table
218
                            $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], []);
219
                            $items[$excludeArray['sectionHeader']] = [
220
                                $excludeArray['sectionHeader'],
221
                                '--div--',
222
                                $icon
223
                            ];
224
                        }
225
                    } else {
226
                        // Add header if not yet set for table
227
                        if (!isset($items[$excludeArray['table']])) {
228
                            $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], []);
229
                            $items[$excludeArray['table']] = [
230
                                $GLOBALS['TCA'][$excludeArray['table']]['ctrl']['title'],
231
                                '--div--',
232
                                $icon
233
                            ];
234
                        }
235
                    }
236
                    // Add help text
237
                    $helpText = [];
238
                    $languageService->loadSingleTableDescription($excludeArray['table']);
239
                    $helpTextArray = $GLOBALS['TCA_DESCR'][$excludeArray['table']]['columns'][$excludeArray['table']] ?? [];
240
                    if (!empty($helpTextArray['description'])) {
241
                        $helpText['description'] = $helpTextArray['description'];
242
                    }
243
                    // Item configuration:
244
                    $items[] = [
245
                        rtrim($excludeArray['origin'] === 'flexForm' ? $excludeArray['fieldLabel'] : $languageService->sL($GLOBALS['TCA'][$excludeArray['table']]['columns'][$excludeArray['fieldName']]['label']), ':') . ' (' . $excludeArray['fieldName'] . ')',
246
                        $excludeArray['table'] . ':' . $excludeArray['fullField'],
247
                        'empty-empty',
248
                        null,
249
                        $helpText
250
                    ];
251
                }
252
                break;
253
            case $special === 'explicitValues':
254
                $theTypes = $this->getExplicitAuthFieldValues();
255
                $icons = [
256
                    'ALLOW' => 'status-status-permission-granted',
257
                    'DENY' => 'status-status-permission-denied'
258
                ];
259
                // Traverse types:
260
                foreach ($theTypes as $tableFieldKey => $theTypeArrays) {
261
                    if (!empty($theTypeArrays['items'])) {
262
                        // Add header:
263
                        $items[] = [
264
                            $theTypeArrays['tableFieldLabel'],
265
                            '--div--',
266
                        ];
267
                        // Traverse options for this field:
268
                        foreach ($theTypeArrays['items'] as $itemValue => $itemContent) {
269
                            // Add item to be selected:
270
                            $items[] = [
271
                                '[' . $itemContent[2] . '] ' . $itemContent[1],
272
                                $tableFieldKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $itemContent[0],
273
                                $icons[$itemContent[0]]
274
                            ];
275
                        }
276
                    }
277
                }
278
                break;
279
            case $special === 'custom':
280
                $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'];
281
                if (is_array($customOptions)) {
282
                    foreach ($customOptions as $coKey => $coValue) {
283
                        if (is_array($coValue['items'])) {
284
                            // Add header:
285
                            $items[] = [
286
                                $languageService->sL($coValue['header']),
287
                                '--div--'
288
                            ];
289
                            // Traverse items:
290
                            foreach ($coValue['items'] as $itemKey => $itemCfg) {
291
                                $icon = 'empty-empty';
292
                                $helpText = [];
293
                                if (!empty($itemCfg[1])) {
294
                                    if ($iconRegistry->isRegistered($itemCfg[1])) {
295
                                        // Use icon identifier when registered
296
                                        $icon = $itemCfg[1];
297
                                    }
298
                                }
299
                                if (!empty($itemCfg[2])) {
300
                                    $helpText['description'] = $languageService->sL($itemCfg[2]);
301
                                }
302
                                $items[] = [
303
                                    $languageService->sL($itemCfg[0]),
304
                                    $coKey . ':' . preg_replace('/[:|,]/', '', $itemKey),
305
                                    $icon,
306
                                    null,
307
                                    $helpText
308
                                ];
309
                            }
310
                        }
311
                    }
312
                }
313
                break;
314
            case $special === 'modListGroup' || $special === 'modListUser':
315
                /** @var ModuleLoader $loadModules */
316
                $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
317
                $loadModules->load($GLOBALS['TBE_MODULES']);
318
                $modList = $special === 'modListUser' ? $loadModules->modListUser : $loadModules->modListGroup;
319
                if (is_array($modList)) {
0 ignored issues
show
introduced by
The condition is_array($modList) is always true.
Loading history...
320
                    foreach ($modList as $theMod) {
321
                        $moduleLabels = $loadModules->getLabelsForModule($theMod);
322
                        $moduleArray = GeneralUtility::trimExplode('_', $theMod, true);
323
                        $mainModule = $moduleArray[0] ?? '';
324
                        $subModule = $moduleArray[1] ?? '';
325
                        // Icon:
326
                        if (!empty($subModule)) {
327
                            $icon = $loadModules->modules[$mainModule]['sub'][$subModule]['iconIdentifier'];
328
                        } else {
329
                            $icon = $loadModules->modules[$theMod]['iconIdentifier'];
330
                        }
331
                        // Add help text
332
                        $helpText = [
333
                            'title' => $languageService->sL($moduleLabels['shortdescription']),
334
                            'description' => $languageService->sL($moduleLabels['description'])
335
                        ];
336
337
                        $label = '';
338
                        // Add label for main module if this is a submodule
339
                        if (!empty($subModule)) {
340
                            $mainModuleLabels = $loadModules->getLabelsForModule($mainModule);
341
                            $label .= $languageService->sL($mainModuleLabels['title']) . '>';
342
                        }
343
                        // Add modules own label now
344
                        $label .= $languageService->sL($moduleLabels['title']);
345
346
                        // Item configuration
347
                        $items[] = [$label, $theMod, $icon, null, $helpText];
348
                    }
349
                }
350
                break;
351
            default:
352
                throw new \UnexpectedValueException(
353
                    'Unknown special value ' . $special . ' for field ' . $fieldName . ' of table ' . $result['tableName'],
354
                    1439298496
355
                );
356
        }
357
        return $items;
358
    }
359
360
    /**
361
     * TCA config "fileFolder" evaluation. Add them to $items
362
     *
363
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
364
     *
365
     * @param array $result Result array
366
     * @param string $fieldName Current handle field name
367
     * @param array $items Incoming items
368
     * @return array Modified item array
369
     * @throws \RuntimeException
370
     */
371
    protected function addItemsFromFolder(array $result, $fieldName, array $items)
372
    {
373
        if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
374
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
375
        ) {
376
            return $items;
377
        }
378
379
        $fileFolderRaw = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
380
        $fileFolder = GeneralUtility::getFileAbsFileName($fileFolderRaw);
381
        if ($fileFolder === '') {
382
            throw new \RuntimeException(
383
                'Invalid folder given for item processing: ' . $fileFolderRaw . ' for table ' . $result['tableName'] . ', field ' . $fieldName,
384
                1479399227
385
            );
386
        }
387
        $fileFolder = rtrim($fileFolder, '/') . '/';
388
389
        if (@is_dir($fileFolder)) {
390
            $fileExtensionList = '';
391
            if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
392
                && is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
393
            ) {
394
                $fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
395
            }
396
            $recursionLevels = isset($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'])
397
                ? MathUtility::forceIntegerInRange($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'], 0, 99)
398
                : 99;
399
            $fileArray = GeneralUtility::getAllFilesAndFoldersInPath([], $fileFolder, $fileExtensionList, false, $recursionLevels);
400
            $fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
401
            foreach ($fileArray as $fileReference) {
402
                $fileInformation = pathinfo($fileReference);
403
                $icon = GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($fileInformation['extension']))
404
                    ? $fileFolder . $fileReference
405
                    : '';
406
                $items[] = [
407
                    $fileReference,
408
                    $fileReference,
409
                    $icon
410
                ];
411
            }
412
        }
413
414
        return $items;
415
    }
416
417
    /**
418
     * TCA config "foreign_table" evaluation. Add them to $items
419
     *
420
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
421
     *
422
     * @param array $result Result array
423
     * @param string $fieldName Current handle field name
424
     * @param array $items Incoming items
425
     * @return array Modified item array
426
     * @throws \UnexpectedValueException
427
     */
428
    protected function addItemsFromForeignTable(array $result, $fieldName, array $items)
429
    {
430
        $databaseError = null;
431
        $queryResult = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $queryResult is dead and can be removed.
Loading history...
432
        // Guard
433
        if (empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
434
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
435
        ) {
436
            return $items;
437
        }
438
439
        $languageService = $this->getLanguageService();
440
441
        $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
442
443
        if (!isset($GLOBALS['TCA'][$foreignTable]) || !is_array($GLOBALS['TCA'][$foreignTable])) {
444
            throw new \UnexpectedValueException(
445
                'Field ' . $fieldName . ' of table ' . $result['tableName'] . ' reference to foreign table '
446
                . $foreignTable . ', but this table is not defined in TCA',
447
                1439569743
448
            );
449
        }
450
451
        $queryBuilder = $this->buildForeignTableQueryBuilder($result, $fieldName);
452
        try {
453
            $queryResult = $queryBuilder->execute();
454
        } catch (DBALException $e) {
455
            $databaseError = $e->getPrevious()->getMessage();
456
        }
457
458
        // Early return on error with flash message
459
        if (!empty($databaseError)) {
460
            $msg = $databaseError . '. ';
461
            $msg .= $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch');
462
            $msgTitle = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch_title');
463
            /** @var FlashMessage $flashMessage */
464
            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, true);
465
            /** @var FlashMessageService $flashMessageService */
466
            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
467
            /** @var FlashMessageQueue $defaultFlashMessageQueue */
468
            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
469
            $defaultFlashMessageQueue->enqueue($flashMessage);
470
            return $items;
471
        }
472
473
        $labelPrefix = '';
474
        if (!empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'])) {
475
            $labelPrefix = $result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'];
476
            $labelPrefix = $languageService->sL($labelPrefix);
477
        }
478
479
        $fileRepository = GeneralUtility::makeInstance(FileRepository::class);
480
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
481
482
        while ($foreignRow = $queryResult->fetch()) {
483
            BackendUtility::workspaceOL($foreignTable, $foreignRow);
484
            if (is_array($foreignRow)) {
485
                // If the foreign table sets selicon_field, this field can contain an image
486
                // that represents this specific row.
487
                $iconFieldName = '';
488
                $isReferenceField = false;
489
                if (!empty($GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'])) {
490
                    $iconFieldName = $GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'];
491
                    if (isset($GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['type'])
492
                        && $GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['type'] === 'inline'
493
                        && $GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['foreign_table'] === 'sys_file_reference'
494
                    ) {
495
                        $isReferenceField = true;
496
                    }
497
                }
498
                $icon = '';
499
                if ($isReferenceField) {
500
                    $references = $fileRepository->findByRelation($foreignTable, $iconFieldName, $foreignRow['uid']);
501
                    if (is_array($references) && !empty($references)) {
502
                        $icon = reset($references);
503
                        $icon = $icon->getPublicUrl();
504
                    }
505
                } else {
506
                    // Else, determine icon based on record type, or a generic fallback
507
                    $icon = $iconFactory->mapRecordTypeToIconIdentifier($foreignTable, $foreignRow);
508
                }
509
                // Add the item
510
                $items[] = [
511
                    $labelPrefix . BackendUtility::getRecordTitle($foreignTable, $foreignRow),
512
                    $foreignRow['uid'],
513
                    $icon
514
                ];
515
            }
516
        }
517
518
        return $items;
519
    }
520
521
    /**
522
     * Remove items using "keepItems" pageTsConfig
523
     *
524
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
525
     *
526
     * @param array $result Result array
527
     * @param string $fieldName Current handle field name
528
     * @param array $items Incoming items
529
     * @return array Modified item array
530
     */
531
    protected function removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
532
    {
533
        $table = $result['tableName'];
534
        if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
535
            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
536
        ) {
537
            return $items;
538
        }
539
540
        // If keepItems is set but is an empty list all current items get removed
541
        if ($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'] === '') {
542
            return [];
543
        }
544
545
        return ArrayUtility::keepItemsInArray(
546
            $items,
547
            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'],
548
            function ($value) {
549
                return $value[1];
550
            }
551
        );
552
    }
553
554
    /**
555
     * Remove items using "removeItems" pageTsConfig
556
     *
557
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
558
     *
559
     * @param array $result Result array
560
     * @param string $fieldName Current handle field name
561
     * @param array $items Incoming items
562
     * @return array Modified item array
563
     */
564
    protected function removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
565
    {
566
        $table = $result['tableName'];
567
        if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
568
            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
569
            || $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'] === ''
570
        ) {
571
            return $items;
572
        }
573
574
        $removeItems = array_flip(GeneralUtility::trimExplode(
575
            ',',
576
            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'],
577
            true
578
        ));
579
        foreach ($items as $key => $itemValues) {
580
            if (isset($removeItems[$itemValues[1]])) {
581
                unset($items[$key]);
582
            }
583
        }
584
585
        return $items;
586
    }
587
588
    /**
589
     * Remove items user restriction on language field
590
     *
591
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
592
     *
593
     * @param array $result Result array
594
     * @param string $fieldName Current handle field name
595
     * @param array $items Incoming items
596
     * @return array Modified item array
597
     */
598
    protected function removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
599
    {
600
        // Guard clause returns if not a language field is handled
601
        if (empty($result['processedTca']['ctrl']['languageField'])
602
            || $result['processedTca']['ctrl']['languageField'] !== $fieldName
603
        ) {
604
            return $items;
605
        }
606
607
        $backendUser = $this->getBackendUser();
608
        foreach ($items as $key => $itemValues) {
609
            if (!$backendUser->checkLanguageAccess($itemValues[1])) {
610
                unset($items[$key]);
611
            }
612
        }
613
614
        return $items;
615
    }
616
617
    /**
618
     * Remove items by user restriction on authMode items
619
     *
620
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
621
     *
622
     * @param array $result Result array
623
     * @param string $fieldName Current handle field name
624
     * @param array $items Incoming items
625
     * @return array Modified item array
626
     */
627
    protected function removeItemsByUserAuthMode(array $result, $fieldName, array $items)
628
    {
629
        // Guard clause returns early if no authMode field is configured
630
        if (!isset($result['processedTca']['columns'][$fieldName]['config']['authMode'])
631
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['authMode'])
632
        ) {
633
            return $items;
634
        }
635
636
        $backendUser = $this->getBackendUser();
637
        $authMode = $result['processedTca']['columns'][$fieldName]['config']['authMode'];
638
        foreach ($items as $key => $itemValues) {
639
            // @todo: checkAuthMode() uses $GLOBAL access for "individual" authMode - get rid of this
640
            if (!$backendUser->checkAuthMode($result['tableName'], $fieldName, $itemValues[1], $authMode)) {
641
                unset($items[$key]);
642
            }
643
        }
644
645
        return $items;
646
    }
647
648
    /**
649
     * Remove items if doktype is handled for non admin users
650
     *
651
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
652
     *
653
     * @param array $result Result array
654
     * @param string $fieldName Current handle field name
655
     * @param array $items Incoming items
656
     * @return array Modified item array
657
     */
658
    protected function removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
659
    {
660
        $table = $result['tableName'];
661
        $backendUser = $this->getBackendUser();
662
        // Guard clause returns if not correct table and field or if user is admin
663
        if ($table !== 'pages' || $fieldName !== 'doktype' || $backendUser->isAdmin()
664
        ) {
665
            return $items;
666
        }
667
668
        $allowedPageTypes = $backendUser->groupData['pagetypes_select'];
669
        foreach ($items as $key => $itemValues) {
670
            if (!GeneralUtility::inList($allowedPageTypes, $itemValues[1])) {
671
                unset($items[$key]);
672
            }
673
        }
674
675
        return $items;
676
    }
677
678
    /**
679
     * Remove items if sys_file_storage is not allowed for non-admin users.
680
     *
681
     * Used by TcaSelectItems data providers
682
     *
683
     * @param array $result Result array
684
     * @param string $fieldName Current handle field name
685
     * @param array $items Incoming items
686
     * @return array Modified item array
687
     */
688
    protected function removeItemsByUserStorageRestriction(array $result, $fieldName, array $items)
689
    {
690
        $referencedTableName = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'] ?? null;
691
        if ($referencedTableName !== 'sys_file_storage') {
692
            return $items;
693
        }
694
695
        $allowedStorageIds = array_map(
696
            function (ResourceStorage $storage) {
697
                return $storage->getUid();
698
            },
699
            $this->getBackendUser()->getFileStorages()
700
        );
701
702
        return array_filter(
703
            $items,
704
            function (array $item) use ($allowedStorageIds) {
705
                $itemValue = $item[1] ?? null;
706
                return empty($itemValue)
707
                    || in_array((int)$itemValue, $allowedStorageIds, true);
708
            }
709
        );
710
    }
711
712
    /**
713
     * Returns an array with the exclude fields as defined in TCA and FlexForms
714
     * Used for listing the exclude fields in be_groups forms.
715
     *
716
     * @return array Array of arrays with excludeFields (fieldName, table:fieldName) from TCA
717
     *               and FlexForms (fieldName, table:extKey;sheetName;fieldName)
718
     */
719
    protected function getExcludeFields()
720
    {
721
        $languageService = $this->getLanguageService();
722
        $finalExcludeArray = [];
723
724
        // Fetch translations for table names
725
        $tableToTranslation = [];
726
        // All TCA keys
727
        foreach ($GLOBALS['TCA'] as $table => $conf) {
728
            $tableToTranslation[$table] = $languageService->sL($conf['ctrl']['title']);
729
        }
730
        /** @var array<string, string> $tableToTranslation */
731
        // Sort by translations
732
        asort($tableToTranslation);
733
        foreach ($tableToTranslation as $table => $translatedTable) {
734
            $excludeArrayTable = [];
735
736
            // All field names configured and not restricted to admins
737
            if (is_array($GLOBALS['TCA'][$table]['columns'])
738
                && empty($GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
739
                && (empty($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
740
            ) {
741
                foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
742
                    $isExcludeField = (bool)($GLOBALS['TCA'][$table]['columns'][$field]['exclude'] ?? false);
743
                    $isOnlyVisibleForAdmins = ($GLOBALS['TCA'][$table]['columns'][$field]['displayCond'] ?? '') === 'HIDE_FOR_NON_ADMINS';
744
                    // Only show fields that can be excluded for editors, or are hidden for non-admins
745
                    if ($isExcludeField && !$isOnlyVisibleForAdmins) {
746
                        // Get human readable names of fields
747
                        $translatedField = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$field]['label']);
748
                        // Add entry, key 'labels' needed for sorting
749
                        $excludeArrayTable[] = [
750
                            'labels' => $translatedTable . ':' . $translatedField,
751
                            'sectionHeader' => $translatedTable,
752
                            'table' => $table,
753
                            'tableField' => $field,
754
                            'fieldName' => $field,
755
                            'fullField' => $field,
756
                            'fieldLabel' => $translatedField,
757
                            'origin' => 'tca',
758
                        ];
759
                    }
760
                }
761
            }
762
            // All FlexForm fields
763
            $flexFormArray = $this->getRegisteredFlexForms($table);
764
            foreach ($flexFormArray as $tableField => $flexForms) {
765
                // Prefix for field label, e.g. "Plugin Options:"
766
                $labelPrefix = '';
767
                if (!empty($GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
768
                    $labelPrefix = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
769
                }
770
                // Get all sheets
771
                foreach ($flexForms as $extIdent => $extConf) {
772
                    // Get all fields in sheet
773
                    foreach ($extConf['sheets'] as $sheetName => $sheet) {
774
                        if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
775
                            continue;
776
                        }
777
                        foreach ($sheet['ROOT']['el'] as $pluginFieldName => $field) {
778
                            // Use only fields that have exclude flag set
779
                            if (empty($field['TCEforms']['exclude'])) {
780
                                continue;
781
                            }
782
                            $fieldLabel = !empty($field['TCEforms']['label'])
783
                                ? $languageService->sL($field['TCEforms']['label'])
784
                                : $pluginFieldName;
785
                            $excludeArrayTable[] = [
786
                                'labels' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ': ') . ':' . $fieldLabel,
787
                                'sectionHeader' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ':'),
788
                                'table' => $table,
789
                                'tableField' => $tableField,
790
                                'extIdent' => $extIdent,
791
                                'fieldName' => $pluginFieldName,
792
                                'fullField' => $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $pluginFieldName,
793
                                'fieldLabel' => $fieldLabel,
794
                                'origin' => 'flexForm',
795
                            ];
796
                        }
797
                    }
798
                }
799
            }
800
            // Sort fields by the translated value
801
            if (!empty($excludeArrayTable)) {
802
                usort($excludeArrayTable, function (array $array1, array $array2) {
803
                    $array1 = reset($array1);
804
                    $array2 = reset($array2);
805
                    if (is_string($array1) && is_string($array2)) {
806
                        return strcasecmp($array1, $array2);
807
                    }
808
                    return 0;
809
                });
810
                $finalExcludeArray = array_merge($finalExcludeArray, $excludeArrayTable);
811
            }
812
        }
813
814
        return $finalExcludeArray;
815
    }
816
817
    /**
818
     * Returns FlexForm data structures it finds. Used in select "special" for be_groups
819
     * to set "exclude" flags for single flex form fields.
820
     *
821
     * This only finds flex forms registered in 'ds' config sections.
822
     * This does not resolve other sophisticated flex form data structure references.
823
     *
824
     * @todo: This approach is limited and doesn't find everything. It works for casual tt_content plugins, though:
825
     * @todo: The data structure identifier determination depends on data row, but we don't have all rows at hand here.
826
     * @todo: The code thus "guesses" some standard data structure identifier scenarios and tries to resolve those.
827
     * @todo: This guessing can not be solved in a good way. A general registry of "all" possible data structures is
828
     * @todo: probably not wanted, since that wouldn't work for truly dynamic DS calculations. Probably the only
829
     * @todo: thing we could do here is a hook to allow extensions declaring specific data structures to
830
     * @todo: allow backend admins to set exclude flags for certain fields in those cases.
831
     *
832
     * @param string $table Table to handle
833
     * @return array Data structures
834
     */
835
    protected function getRegisteredFlexForms($table)
836
    {
837
        if (empty($table) || empty($GLOBALS['TCA'][$table]['columns'])) {
838
            return [];
839
        }
840
        $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
841
        $flexForms = [];
842
        foreach ($GLOBALS['TCA'][$table]['columns'] as $tableField => $fieldConf) {
843
            if (!empty($fieldConf['config']['type']) && !empty($fieldConf['config']['ds']) && $fieldConf['config']['type'] === 'flex') {
844
                $flexForms[$tableField] = [];
845
                foreach (array_keys($fieldConf['config']['ds']) as $flexFormKey) {
846
                    $flexFormKey = (string)$flexFormKey;
847
                    // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
848
                    $identFields = GeneralUtility::trimExplode(',', $flexFormKey);
849
                    $extIdent = $identFields[0];
850
                    if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
851
                        $extIdent = $identFields[1];
852
                    }
853
                    $flexFormDataStructureIdentifier = json_encode([
854
                        'type' => 'tca',
855
                        'tableName' => $table,
856
                        'fieldName' => $tableField,
857
                        'dataStructureKey' => $flexFormKey,
858
                    ]);
859
                    try {
860
                        $dataStructure = $flexFormTools->parseDataStructureByIdentifier($flexFormDataStructureIdentifier);
861
                        $flexForms[$tableField][$extIdent] = $dataStructure;
862
                    } catch (InvalidIdentifierException $e) {
863
                        // Deliberately empty: The DS identifier is guesswork and the flex ds parser throws
864
                        // this exception if it can not resolve to a valid data structure. This is "ok" here
865
                        // and the exception is just eaten.
866
                    }
867
                }
868
            }
869
        }
870
        return $flexForms;
871
    }
872
873
    /**
874
     * Returns an array with explicit Allow/Deny fields.
875
     * Used for listing these field/value pairs in be_groups forms
876
     *
877
     * @return array Array with information from all of $GLOBALS['TCA']
878
     */
879
    protected function getExplicitAuthFieldValues()
880
    {
881
        $languageService = static::getLanguageService();
0 ignored issues
show
Bug Best Practice introduced by
The method TYPO3\CMS\Backend\Form\F...r::getLanguageService() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

881
        /** @scrutinizer ignore-call */ 
882
        $languageService = static::getLanguageService();
Loading history...
882
        $adLabel = [
883
            'ALLOW' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.allow'),
884
            'DENY' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.deny')
885
        ];
886
        $allowDenyOptions = [];
887
        foreach ($GLOBALS['TCA'] as $table => $_) {
888
            // All field names configured:
889
            if (is_array($GLOBALS['TCA'][$table]['columns'])) {
890
                foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $__) {
891
                    $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
892
                    if ($fieldConfig['type'] === 'select' && $fieldConfig['authMode']) {
893
                        // Check for items
894
                        if (is_array($fieldConfig['items'])) {
895
                            // Get Human Readable names of fields and table:
896
                            $allowDenyOptions[$table . ':' . $field]['tableFieldLabel'] =
897
                                $languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title']) . ': '
898
                                . $languageService->sL($GLOBALS['TCA'][$table]['columns'][$field]['label']);
899
                            foreach ($fieldConfig['items'] as $iVal) {
900
                                $itemIdentifier = (string)$iVal[1];
901
                                // Values '' and '--div--' are not controlled by this setting.
902
                                if ($itemIdentifier === '' || $itemIdentifier === '--div--') {
903
                                    continue;
904
                                }
905
                                // Find iMode
906
                                $iMode = '';
907
                                switch ((string)$fieldConfig['authMode']) {
908
                                    case 'explicitAllow':
909
                                        $iMode = 'ALLOW';
910
                                        break;
911
                                    case 'explicitDeny':
912
                                        $iMode = 'DENY';
913
                                        break;
914
                                    case 'individual':
915
                                        if (isset($iVal[4]) && $iVal[4] === 'EXPL_ALLOW') {
916
                                            $iMode = 'ALLOW';
917
                                        } elseif (isset($iVal[4]) && $iVal[4] === 'EXPL_DENY') {
918
                                            $iMode = 'DENY';
919
                                        }
920
                                        break;
921
                                }
922
                                // Set iMode
923
                                if ($iMode) {
924
                                    $allowDenyOptions[$table . ':' . $field]['items'][$itemIdentifier] = [
925
                                        $iMode,
926
                                        $languageService->sL($iVal[0]),
927
                                        $adLabel[$iMode]
928
                                    ];
929
                                }
930
                            }
931
                        }
932
                    }
933
                }
934
            }
935
        }
936
        return $allowDenyOptions;
937
    }
938
939
    /**
940
     * Build query to fetch foreign records. Helper method of
941
     * addItemsFromForeignTable(), do not call otherwise.
942
     *
943
     * @param array $result Result array
944
     * @param string $localFieldName Current handle field name
945
     * @return QueryBuilder
946
     */
947
    protected function buildForeignTableQueryBuilder(array $result, string $localFieldName): QueryBuilder
948
    {
949
        $backendUser = $this->getBackendUser();
950
951
        $foreignTableName = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table'];
952
        $foreignTableClauseArray = $this->processForeignTableClause($result, $foreignTableName, $localFieldName);
953
954
        $fieldList = BackendUtility::getCommonSelectFields($foreignTableName, $foreignTableName . '.');
955
        /** @var QueryBuilder $queryBuilder */
956
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
957
            ->getQueryBuilderForTable($foreignTableName);
958
959
        $queryBuilder->getRestrictions()
960
            ->removeAll()
961
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
962
963
        $queryBuilder
964
            ->select(...GeneralUtility::trimExplode(',', $fieldList, true))
965
            ->from($foreignTableName)
966
            ->where($foreignTableClauseArray['WHERE']);
967
968
        if (!empty($foreignTableClauseArray['GROUPBY'])) {
969
            $queryBuilder->groupBy(...$foreignTableClauseArray['GROUPBY']);
970
        }
971
972
        if (!empty($foreignTableClauseArray['ORDERBY'])) {
973
            foreach ($foreignTableClauseArray['ORDERBY'] as $orderPair) {
974
                [$fieldName, $order] = $orderPair;
975
                $queryBuilder->addOrderBy($fieldName, $order);
976
            }
977
        } elseif (!empty($GLOBALS['TCA'][$foreignTableName]['ctrl']['default_sortby'])) {
978
            $orderByClauses = QueryHelper::parseOrderBy($GLOBALS['TCA'][$foreignTableName]['ctrl']['default_sortby']);
979
            foreach ($orderByClauses as $orderByClause) {
980
                if (!empty($orderByClause[0])) {
981
                    $queryBuilder->addOrderBy($foreignTableName . '.' . $orderByClause[0], $orderByClause[1]);
982
                }
983
            }
984
        }
985
986
        if (!empty($foreignTableClauseArray['LIMIT'])) {
987
            if (!empty($foreignTableClauseArray['LIMIT'][1])) {
988
                $queryBuilder->setMaxResults($foreignTableClauseArray['LIMIT'][1]);
989
                $queryBuilder->setFirstResult($foreignTableClauseArray['LIMIT'][0]);
990
            } elseif (!empty($foreignTableClauseArray['LIMIT'][0])) {
991
                $queryBuilder->setMaxResults($foreignTableClauseArray['LIMIT'][0]);
992
            }
993
        }
994
995
        // rootLevel = -1 means that elements can be on the rootlevel OR on any page (pid!=-1)
996
        // rootLevel = 0 means that elements are not allowed on root level
997
        // rootLevel = 1 means that elements are only on the root level (pid=0)
998
        $rootLevel = 0;
999
        if (isset($GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'])) {
1000
            $rootLevel = (int)$GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'];
1001
        }
1002
1003
        if ($rootLevel === -1) {
1004
            $queryBuilder->andWhere(
1005
                $queryBuilder->expr()->neq(
1006
                    $foreignTableName . '.pid',
1007
                    $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1008
                )
1009
            );
1010
        } elseif ($rootLevel === 1) {
1011
            $queryBuilder->andWhere(
1012
                $queryBuilder->expr()->eq(
1013
                    $foreignTableName . '.pid',
1014
                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1015
                )
1016
            );
1017
        } else {
1018
            $queryBuilder->andWhere($backendUser->getPagePermsClause(Permission::PAGE_SHOW));
1019
            if ($foreignTableName !== 'pages') {
1020
                $queryBuilder
1021
                    ->from('pages')
1022
                    ->andWhere(
1023
                        $queryBuilder->expr()->eq(
1024
                            'pages.uid',
1025
                            $queryBuilder->quoteIdentifier($foreignTableName . '.pid')
1026
                        )
1027
                    );
1028
            }
1029
        }
1030
1031
        return $queryBuilder;
1032
    }
1033
1034
    /**
1035
     * Replace markers in a where clause from TCA foreign_table_where
1036
     *
1037
     * ###REC_FIELD_[field name]###
1038
     * ###THIS_UID### - is current element uid (zero if new).
1039
     * ###CURRENT_PID### - is the current page id (pid of the record).
1040
     * ###SITEROOT###
1041
     * ###PAGE_TSCONFIG_ID### - a value you can set from Page TSconfig dynamically.
1042
     * ###PAGE_TSCONFIG_IDLIST### - a value you can set from Page TSconfig dynamically.
1043
     * ###PAGE_TSCONFIG_STR### - a value you can set from Page TSconfig dynamically.
1044
     *
1045
     * @param array $result Result array
1046
     * @param string $foreignTableName Name of foreign table
1047
     * @param string $localFieldName Current handle field name
1048
     * @return array Query parts with keys WHERE, ORDERBY, GROUPBY, LIMIT
1049
     */
1050
    protected function processForeignTableClause(array $result, $foreignTableName, $localFieldName)
1051
    {
1052
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($foreignTableName);
1053
        $localTable = $result['tableName'];
1054
        $effectivePid = $result['effectivePid'];
1055
1056
        $foreignTableClause = '';
1057
        if (!empty($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
1058
            && is_string($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
1059
        ) {
1060
            $foreignTableClause = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'];
1061
            // Replace possible markers in query
1062
            if (strpos($foreignTableClause, '###REC_FIELD_') !== false) {
1063
                // " AND table.field='###REC_FIELD_field1###' AND ..." -> array(" AND table.field='", "field1###' AND ...")
1064
                $whereClauseParts = explode('###REC_FIELD_', $foreignTableClause);
1065
                foreach ($whereClauseParts as $key => $value) {
1066
                    if ($key !== 0) {
1067
                        // "field1###' AND ..." -> array("field1", "' AND ...")
1068
                        $whereClauseSubParts = explode('###', $value, 2);
1069
                        // @todo: Throw exception if there is no value? What happens for NEW records?
1070
                        $databaseRowKey = empty($result['flexParentDatabaseRow']) ? 'databaseRow' : 'flexParentDatabaseRow';
1071
                        $rowFieldValue = $result[$databaseRowKey][$whereClauseSubParts[0]] ?? '';
1072
                        if (is_array($rowFieldValue)) {
1073
                            // If a select or group field is used here, it may have been processed already and
1074
                            // is now an array containing uid + table + title + row.
1075
                            // See TcaGroup data provider for details.
1076
                            // Pick the first one (always on 0), and use uid only.
1077
                            $rowFieldValue = $rowFieldValue[0]['uid'] ?? $rowFieldValue[0];
1078
                        }
1079
                        if (substr($whereClauseParts[0], -1) === '\'' && $whereClauseSubParts[1][0] === '\'') {
1080
                            $whereClauseParts[0] = substr($whereClauseParts[0], 0, -1);
1081
                            $whereClauseSubParts[1] = substr($whereClauseSubParts[1], 1);
1082
                        }
1083
                        $whereClauseParts[$key] = $connection->quote($rowFieldValue) . $whereClauseSubParts[1];
1084
                    }
1085
                }
1086
                $foreignTableClause = implode('', $whereClauseParts);
1087
            }
1088
            if (strpos($foreignTableClause, '###CURRENT_PID###') !== false) {
1089
                // Use pid from parent page clause if in flex form context
1090
                if (!empty($result['flexParentDatabaseRow']['pid'])) {
1091
                    $effectivePid = $result['flexParentDatabaseRow']['pid'];
1092
                } elseif (!$effectivePid && !empty($result['databaseRow']['pid'])) {
1093
                    // Use pid from database row if in inline context
1094
                    $effectivePid = $result['databaseRow']['pid'];
1095
                }
1096
            }
1097
1098
            $siteRootUid = 0;
1099
            foreach ($result['rootline'] as $rootlinePage) {
1100
                if (!empty($rootlinePage['is_siteroot'])) {
1101
                    $siteRootUid = (int)$rootlinePage['uid'];
1102
                    break;
1103
                }
1104
            }
1105
1106
            $pageTsConfigId = 0;
1107
            if (isset($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'])
1108
                && $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID']
1109
            ) {
1110
                $pageTsConfigId = (int)$result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'];
1111
            }
1112
1113
            $pageTsConfigIdList = 0;
1114
            if (isset($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'])
1115
                && $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST']
1116
            ) {
1117
                $pageTsConfigIdList = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'];
1118
            }
1119
            $pageTsConfigIdListArray = GeneralUtility::trimExplode(',', $pageTsConfigIdList, true);
1120
            $pageTsConfigIdList = [];
1121
            foreach ($pageTsConfigIdListArray as $pageTsConfigIdListElement) {
1122
                if (MathUtility::canBeInterpretedAsInteger($pageTsConfigIdListElement)) {
1123
                    $pageTsConfigIdList[] = (int)$pageTsConfigIdListElement;
1124
                }
1125
            }
1126
            $pageTsConfigIdList = implode(',', $pageTsConfigIdList);
1127
1128
            $pageTsConfigString = '';
1129
            if (isset($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'])
1130
                && $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR']
1131
            ) {
1132
                $pageTsConfigString = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'];
1133
                $pageTsConfigString = $connection->quote($pageTsConfigString);
1134
            }
1135
1136
            $foreignTableClause = str_replace(
1137
                [
1138
                    '###CURRENT_PID###',
1139
                    '###THIS_UID###',
1140
                    '###SITEROOT###',
1141
                    '###PAGE_TSCONFIG_ID###',
1142
                    '###PAGE_TSCONFIG_IDLIST###',
1143
                    '\'###PAGE_TSCONFIG_STR###\'',
1144
                    '###PAGE_TSCONFIG_STR###'
1145
                ],
1146
                [
1147
                    (int)$effectivePid,
1148
                    (int)$result['databaseRow']['uid'],
1149
                    $siteRootUid,
1150
                    $pageTsConfigId,
1151
                    $pageTsConfigIdList,
1152
                    $pageTsConfigString,
1153
                    $pageTsConfigString
1154
                ],
1155
                $foreignTableClause
1156
            );
1157
        }
1158
1159
        // Split the clause into an array with keys WHERE, GROUPBY, ORDERBY, LIMIT
1160
        // Prepend a space to make sure "[[:space:]]+" will find a space there for the first element.
1161
        $foreignTableClause = ' ' . $foreignTableClause;
1162
        $foreignTableClauseArray = [
1163
            'WHERE' => '',
1164
            'GROUPBY' => '',
1165
            'ORDERBY' => '',
1166
            'LIMIT' => '',
1167
        ];
1168
        // Find LIMIT
1169
        $reg = [];
1170
        if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/is', $foreignTableClause, $reg)) {
1171
            $foreignTableClauseArray['LIMIT'] = GeneralUtility::intExplode(',', trim($reg[2]), true);
1172
            $foreignTableClause = $reg[1];
1173
        }
1174
        // Find ORDER BY
1175
        $reg = [];
1176
        if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._()"]+)$/is', $foreignTableClause, $reg)) {
1177
            $foreignTableClauseArray['ORDERBY'] = QueryHelper::parseOrderBy(trim($reg[2]));
1178
            $foreignTableClause = $reg[1];
1179
        }
1180
        // Find GROUP BY
1181
        $reg = [];
1182
        if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._()"]+)$/is', $foreignTableClause, $reg)) {
1183
            $foreignTableClauseArray['GROUPBY'] = QueryHelper::parseGroupBy(trim($reg[2]));
1184
            $foreignTableClause = $reg[1];
1185
        }
1186
        // Rest is assumed to be "WHERE" clause
1187
        $foreignTableClauseArray['WHERE'] = QueryHelper::stripLogicalOperatorPrefix($foreignTableClause);
1188
1189
        return $foreignTableClauseArray;
1190
    }
1191
1192
    /**
1193
     * Convert the current database values into an array
1194
     *
1195
     * @param array $row database row
1196
     * @param string $fieldName fieldname to process
1197
     * @return array
1198
     */
1199
    protected function processDatabaseFieldValue(array $row, $fieldName)
1200
    {
1201
        $currentDatabaseValues = array_key_exists($fieldName, $row)
1202
            ? $row[$fieldName]
1203
            : '';
1204
        if (!is_array($currentDatabaseValues)) {
1205
            $currentDatabaseValues = GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
1206
        }
1207
        return $currentDatabaseValues;
1208
    }
1209
1210
    /**
1211
     * Validate and sanitize database row values of the select field with the given name.
1212
     * Creates an array out of databaseRow[selectField] values.
1213
     *
1214
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
1215
     *
1216
     * @param array $result The current result array.
1217
     * @param string $fieldName Name of the current select field.
1218
     * @param array $staticValues Array with statically defined items, item value is used as array key.
1219
     * @return array
1220
     */
1221
    protected function processSelectFieldValue(array $result, $fieldName, array $staticValues)
1222
    {
1223
        $fieldConfig = $result['processedTca']['columns'][$fieldName];
1224
1225
        $currentDatabaseValueArray = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : [];
1226
        $newDatabaseValueArray = [];
1227
1228
        // Add all values that were defined by static methods and do not come from the relation
1229
        // e.g. TCA, TSconfig, itemProcFunc etc.
1230
        foreach ($currentDatabaseValueArray as $value) {
1231
            if (isset($staticValues[$value])) {
1232
                $newDatabaseValueArray[] = $value;
1233
            }
1234
        }
1235
1236
        if (isset($fieldConfig['config']['foreign_table']) && !empty($fieldConfig['config']['foreign_table'])) {
1237
            /** @var RelationHandler $relationHandler */
1238
            $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1239
            $relationHandler->registerNonTableValues = !empty($fieldConfig['config']['allowNonIdValues']);
1240
            if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') {
1241
                // MM relation
1242
                $relationHandler->start(
1243
                    implode(',', $currentDatabaseValueArray),
1244
                    $fieldConfig['config']['foreign_table'],
1245
                    $fieldConfig['config']['MM'],
1246
                    $result['databaseRow']['uid'],
1247
                    $result['tableName'],
1248
                    $fieldConfig['config']
1249
                );
1250
                $newDatabaseValueArray = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
1251
            } else {
1252
                // Non MM relation
1253
                // If not dealing with MM relations, use default live uid, not versioned uid for record relations
1254
                $relationHandler->start(
1255
                    implode(',', $currentDatabaseValueArray),
1256
                    $fieldConfig['config']['foreign_table'],
1257
                    '',
1258
                    $this->getLiveUid($result),
1259
                    $result['tableName'],
1260
                    $fieldConfig['config']
1261
                );
1262
                $databaseIds = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
1263
                // remove all items from the current DB values if not available as relation or static value anymore
1264
                $newDatabaseValueArray = array_values(array_intersect($currentDatabaseValueArray, $databaseIds));
1265
            }
1266
        }
1267
1268
        if ($fieldConfig['config']['multiple'] ?? false) {
1269
            return $newDatabaseValueArray;
1270
        }
1271
        return array_unique($newDatabaseValueArray);
1272
    }
1273
1274
    /**
1275
     * Translate the item labels
1276
     *
1277
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
1278
     *
1279
     * @param array $result Result array
1280
     * @param array $itemArray Items
1281
     * @param string $table
1282
     * @param string $fieldName
1283
     * @return array
1284
     */
1285
    public function translateLabels(array $result, array $itemArray, $table, $fieldName)
1286
    {
1287
        $languageService = $this->getLanguageService();
1288
1289
        foreach ($itemArray as $key => $item) {
1290
            if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
1291
                && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
1292
            ) {
1293
                $label = $languageService->sL($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]]);
1294
            } else {
1295
                $label = $languageService->sL(trim($item[0]));
1296
            }
1297
            $value = strlen((string)$item[1]) > 0 ? $item[1] : '';
1298
            $icon = !empty($item[2]) ? $item[2] : null;
1299
            $groupId = $item[3] ?? null;
1300
            $helpText = null;
1301
            if (!empty($item[4])) {
1302
                if (\is_string($item[4])) {
1303
                    $helpText = $languageService->sL($item[4]);
1304
                } else {
1305
                    $helpText = $item[4];
1306
                }
1307
            }
1308
            $itemArray[$key] = [
1309
                $label,
1310
                $value,
1311
                $icon,
1312
                $groupId,
1313
                $helpText
1314
            ];
1315
        }
1316
1317
        return $itemArray;
1318
    }
1319
1320
    /**
1321
     * Sanitize incoming item array
1322
     *
1323
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
1324
     *
1325
     * @param mixed $itemArray
1326
     * @param string $tableName
1327
     * @param string $fieldName
1328
     * @throws \UnexpectedValueException
1329
     * @return array
1330
     */
1331
    public function sanitizeItemArray($itemArray, $tableName, $fieldName)
1332
    {
1333
        if (!is_array($itemArray)) {
1334
            $itemArray = [];
1335
        }
1336
        foreach ($itemArray as $item) {
1337
            if (!is_array($item)) {
1338
                throw new \UnexpectedValueException(
1339
                    'An item in field ' . $fieldName . ' of table ' . $tableName . ' is not an array as expected',
1340
                    1439288036
1341
                );
1342
            }
1343
        }
1344
1345
        return $itemArray;
1346
    }
1347
1348
    /**
1349
     * Gets the record uid of the live default record. If already
1350
     * pointing to the live record, the submitted record uid is returned.
1351
     *
1352
     * @param array $result Result array
1353
     * @return int
1354
     * @throws \UnexpectedValueException
1355
     */
1356
    protected function getLiveUid(array $result)
1357
    {
1358
        $table = $result['tableName'];
1359
        $row = $result['databaseRow'];
1360
        $uid = $row['uid'];
1361
        if (BackendUtility::isTableWorkspaceEnabled($table) && (int)$row['t3ver_oid'] > 0) {
1362
            $uid = $row['t3ver_oid'];
1363
        }
1364
        return $uid;
1365
    }
1366
1367
    /**
1368
     * @return LanguageService
1369
     */
1370
    protected function getLanguageService()
1371
    {
1372
        return $GLOBALS['LANG'];
1373
    }
1374
1375
    /**
1376
     * @return BackendUserAuthentication
1377
     */
1378
    protected function getBackendUser()
1379
    {
1380
        return $GLOBALS['BE_USER'];
1381
    }
1382
}
1383