Passed
Push — master ( b4ff37...92297c )
by
unknown
65:05 queued 51:43
created

resolveItemProcessorFunction()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 58
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 42
dl 0
loc 58
rs 8.9368
c 0
b 0
f 0
cc 5
nc 12
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Utility\BackendUtility;
20
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21
use TYPO3\CMS\Core\Database\ConnectionPool;
22
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
23
use TYPO3\CMS\Core\Database\Query\QueryHelper;
24
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
25
use TYPO3\CMS\Core\Database\RelationHandler;
26
use TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions;
27
use TYPO3\CMS\Core\Imaging\IconFactory;
28
use TYPO3\CMS\Core\Localization\LanguageService;
29
use TYPO3\CMS\Core\Messaging\FlashMessage;
30
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
31
use TYPO3\CMS\Core\Messaging\FlashMessageService;
32
use TYPO3\CMS\Core\Resource\FileRepository;
33
use TYPO3\CMS\Core\Resource\ResourceStorage;
34
use TYPO3\CMS\Core\Type\Bitmask\Permission;
35
use TYPO3\CMS\Core\Utility\ArrayUtility;
36
use TYPO3\CMS\Core\Utility\GeneralUtility;
37
use TYPO3\CMS\Core\Utility\MathUtility;
38
39
/**
40
 * Contains methods used by Data providers that handle elements
41
 * with single items like select, radio and some more.
42
 */
43
abstract class AbstractItemProvider
44
{
45
    /**
46
     * Resolve "itemProcFunc" of elements.
47
     *
48
     * @param array $result Main result array
49
     * @param string $fieldName Field name to handle item list for
50
     * @param array $items Existing items array
51
     * @return array New list of item elements
52
     */
53
    protected function resolveItemProcessorFunction(array $result, $fieldName, array $items)
54
    {
55
        $table = $result['tableName'];
56
        $config = $result['processedTca']['columns'][$fieldName]['config'];
57
58
        $pageTsProcessorParameters = null;
59
        if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'])) {
60
            $pageTsProcessorParameters = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'];
61
        }
62
        $processorParameters = [
63
            // Function manipulates $items directly and return nothing
64
            'items' => &$items,
65
            'config' => $config,
66
            'TSconfig' => $pageTsProcessorParameters,
67
            'table' => $table,
68
            'row' => $result['databaseRow'],
69
            'field' => $fieldName,
70
            'inlineParentUid' => $result['inlineParentUid'],
71
            'inlineParentTableName' => $result['inlineParentTableName'],
72
            'inlineParentFieldName' => $result['inlineParentFieldName'],
73
            'inlineParentConfig' => $result['inlineParentConfig'],
74
            'inlineTopMostParentUid' => $result['inlineTopMostParentUid'],
75
            'inlineTopMostParentTableName' => $result['inlineTopMostParentTableName'],
76
            'inlineTopMostParentFieldName' => $result['inlineTopMostParentFieldName'],
77
        ];
78
        if (!empty($result['flexParentDatabaseRow'])) {
79
            $processorParameters['flexParentDatabaseRow'] = $result['flexParentDatabaseRow'];
80
        }
81
82
        try {
83
            GeneralUtility::callUserFunction($config['itemsProcFunc'], $processorParameters, $this);
84
        } catch (\Exception $exception) {
85
            // The itemsProcFunc method may throw an exception, create a flash message if so
86
            $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

86
            /** @scrutinizer ignore-call */ 
87
            $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...
87
            $fieldLabel = $fieldName;
88
            if (!empty($result['processedTca']['columns'][$fieldName]['label'])) {
89
                $fieldLabel = $languageService->sL($result['processedTca']['columns'][$fieldName]['label']);
90
            }
91
            $message = sprintf(
92
                $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.items_proc_func_error'),
93
                $fieldLabel,
94
                $exception->getMessage()
95
            );
96
            /** @var FlashMessage $flashMessage */
97
            $flashMessage = GeneralUtility::makeInstance(
98
                FlashMessage::class,
99
                $message,
100
                '',
101
                FlashMessage::ERROR,
102
                true
103
            );
104
            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
105
            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
106
            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
107
            $defaultFlashMessageQueue->enqueue($flashMessage);
108
        }
109
110
        return $items;
111
    }
112
113
    /**
114
     * PageTsConfig addItems:
115
     *
116
     * TCEFORMS.aTable.aField[.types][.aType].addItems.aValue = aLabel,
117
     * with type specific options merged by pageTsConfig already
118
     *
119
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
120
     *
121
     * @param array $result result array
122
     * @param string $fieldName Current handle field name
123
     * @param array $items Incoming items
124
     * @return array Modified item array
125
     */
126
    protected function addItemsFromPageTsConfig(array $result, $fieldName, array $items)
127
    {
128
        $table = $result['tableName'];
129
        if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
130
            && is_array($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
131
        ) {
132
            $addItemsArray = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'];
133
            foreach ($addItemsArray as $value => $label) {
134
                // If the value ends with a dot, it is a subelement like "34.icon = mylabel.png", skip it
135
                if (substr($value, -1) === '.') {
136
                    continue;
137
                }
138
                // Check if value "34 = mylabel" also has a "34.icon = myImage.png"
139
                $iconIdentifier = null;
140
                if (isset($addItemsArray[$value . '.'])
141
                    && is_array($addItemsArray[$value . '.'])
142
                    && !empty($addItemsArray[$value . '.']['icon'])
143
                ) {
144
                    $iconIdentifier = $addItemsArray[$value . '.']['icon'];
145
                }
146
                $items[] = [$label, $value, $iconIdentifier];
147
            }
148
        }
149
        return $items;
150
    }
151
152
    /**
153
     * TCA config "special" evaluation. Add them to $items
154
     *
155
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
156
     *
157
     * @param array $result Result array
158
     * @param string $fieldName Current handle field name
159
     * @param array $items Incoming items
160
     * @return array Modified item array
161
     * @throws \UnexpectedValueException
162
     * @deprecated since v11, will be removed in v12
163
     */
164
    protected function addItemsFromSpecial(array $result, $fieldName, array $items)
165
    {
166
        // Guard
167
        if (empty($result['processedTca']['columns'][$fieldName]['config']['special'])
168
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['special'])
169
        ) {
170
            return $items;
171
        }
172
173
        $special = $result['processedTca']['columns'][$fieldName]['config']['special'];
174
175
        trigger_error(
176
            'Using the TCA property \'special=' . $special . '\' is deprecated and will be removed in TYPO3 v12. Use a custom itemsProcFunc instead.',
177
            E_USER_DEPRECATED
178
        );
179
180
        switch (true) {
181
            case $special === 'tables':
182
                $fieldInformation = ['items' => $items];
183
                (new TcaItemsProcessorFunctions())->populateAvailableTables($fieldInformation);
184
                $items = $fieldInformation['items'];
185
                break;
186
            case $special === 'pagetypes':
187
                $fieldInformation = ['items' => $items];
188
                (new TcaItemsProcessorFunctions())->populateAvailablePageTypes($fieldInformation);
189
                $items = $fieldInformation['items'];
190
                break;
191
            case $special === 'exclude':
192
                $fieldInformation = ['items' => $items];
193
                (new TcaItemsProcessorFunctions())->populateExcludeFields($fieldInformation);
194
                $items = $fieldInformation['items'];
195
                break;
196
            case $special === 'explicitValues':
197
                $fieldInformation = ['items' => $items];
198
                (new TcaItemsProcessorFunctions())->populateExplicitAuthValues($fieldInformation);
199
                $items = $fieldInformation['items'];
200
                break;
201
            case $special === 'custom':
202
                $fieldInformation = ['items' => $items];
203
                (new TcaItemsProcessorFunctions())->populateCustomPermissionOptions($fieldInformation);
204
                $items = $fieldInformation['items'];
205
                break;
206
            case $special === 'modListGroup':
207
                $fieldInformation = ['items' => $items];
208
                (new TcaItemsProcessorFunctions())->populateAvailableGroupModules($fieldInformation);
209
                $items = $fieldInformation['items'];
210
                break;
211
            case $special === 'modListUser':
212
                $fieldInformation = ['items' => $items];
213
                (new TcaItemsProcessorFunctions())->populateAvailableUserModules($fieldInformation);
214
                $items = $fieldInformation['items'];
215
                break;
216
            default:
217
                throw new \UnexpectedValueException(
218
                    'Unknown special value ' . $special . ' for field ' . $fieldName . ' of table ' . $result['tableName'],
219
                    1439298496
220
                );
221
        }
222
        return $items;
223
    }
224
225
    /**
226
     * TCA config "fileFolder" evaluation. Add them to $items
227
     *
228
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
229
     *
230
     * @param array $result Result array
231
     * @param string $fieldName Current handle field name
232
     * @param array $items Incoming items
233
     * @return array Modified item array
234
     * @throws \RuntimeException
235
     */
236
    protected function addItemsFromFolder(array $result, $fieldName, array $items)
237
    {
238
        if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
239
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
240
        ) {
241
            return $items;
242
        }
243
244
        $fileFolderRaw = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
245
        $fileFolder = GeneralUtility::getFileAbsFileName($fileFolderRaw);
246
        if ($fileFolder === '') {
247
            throw new \RuntimeException(
248
                'Invalid folder given for item processing: ' . $fileFolderRaw . ' for table ' . $result['tableName'] . ', field ' . $fieldName,
249
                1479399227
250
            );
251
        }
252
        $fileFolder = rtrim($fileFolder, '/') . '/';
253
254
        if (@is_dir($fileFolder)) {
255
            $fileExtensionList = '';
256
            if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
257
                && is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
258
            ) {
259
                $fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
260
            }
261
            $recursionLevels = isset($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'])
262
                ? MathUtility::forceIntegerInRange($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'], 0, 99)
263
                : 99;
264
            $fileArray = GeneralUtility::getAllFilesAndFoldersInPath([], $fileFolder, $fileExtensionList, false, $recursionLevels);
265
            $fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
266
            foreach ($fileArray as $fileReference) {
267
                $fileInformation = pathinfo($fileReference);
268
                $icon = GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($fileInformation['extension']))
269
                    ? $fileFolder . $fileReference
270
                    : '';
271
                $items[] = [
272
                    $fileReference,
273
                    $fileReference,
274
                    $icon
275
                ];
276
            }
277
        }
278
279
        return $items;
280
    }
281
282
    /**
283
     * TCA config "foreign_table" evaluation. Add them to $items
284
     *
285
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
286
     *
287
     * @param array $result Result array
288
     * @param string $fieldName Current handle field name
289
     * @param array $items Incoming items
290
     * @return array Modified item array
291
     * @throws \UnexpectedValueException
292
     */
293
    protected function addItemsFromForeignTable(array $result, $fieldName, array $items)
294
    {
295
        $databaseError = null;
296
        $queryResult = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $queryResult is dead and can be removed.
Loading history...
297
        // Guard
298
        if (empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
299
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
300
        ) {
301
            return $items;
302
        }
303
304
        $languageService = $this->getLanguageService();
305
306
        $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
307
308
        if (!isset($GLOBALS['TCA'][$foreignTable]) || !is_array($GLOBALS['TCA'][$foreignTable])) {
309
            throw new \UnexpectedValueException(
310
                'Field ' . $fieldName . ' of table ' . $result['tableName'] . ' reference to foreign table '
311
                . $foreignTable . ', but this table is not defined in TCA',
312
                1439569743
313
            );
314
        }
315
316
        $queryBuilder = $this->buildForeignTableQueryBuilder($result, $fieldName);
317
        try {
318
            $queryResult = $queryBuilder->execute();
319
        } catch (DBALException $e) {
320
            $databaseError = $e->getPrevious()->getMessage();
321
        }
322
323
        // Early return on error with flash message
324
        if (!empty($databaseError)) {
325
            $msg = $databaseError . '. ';
326
            $msg .= $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch');
327
            $msgTitle = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch_title');
328
            /** @var FlashMessage $flashMessage */
329
            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, true);
330
            /** @var FlashMessageService $flashMessageService */
331
            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
332
            /** @var FlashMessageQueue $defaultFlashMessageQueue */
333
            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
334
            $defaultFlashMessageQueue->enqueue($flashMessage);
335
            return $items;
336
        }
337
338
        $labelPrefix = '';
339
        if (!empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'])) {
340
            $labelPrefix = $result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'];
341
            $labelPrefix = $languageService->sL($labelPrefix);
342
        }
343
344
        $fileRepository = GeneralUtility::makeInstance(FileRepository::class);
345
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
346
347
        while ($foreignRow = $queryResult->fetch()) {
348
            BackendUtility::workspaceOL($foreignTable, $foreignRow);
349
            if (is_array($foreignRow)) {
350
                // If the foreign table sets selicon_field, this field can contain an image
351
                // that represents this specific row.
352
                $iconFieldName = '';
353
                $isReferenceField = false;
354
                if (!empty($GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'])) {
355
                    $iconFieldName = $GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'];
356
                    if (isset($GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['type'])
357
                        && $GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['type'] === 'inline'
358
                        && $GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['foreign_table'] === 'sys_file_reference'
359
                    ) {
360
                        $isReferenceField = true;
361
                    }
362
                }
363
                $icon = '';
364
                if ($isReferenceField) {
365
                    $references = $fileRepository->findByRelation($foreignTable, $iconFieldName, $foreignRow['uid']);
366
                    if (is_array($references) && !empty($references)) {
367
                        $icon = reset($references);
368
                        $icon = $icon->getPublicUrl();
369
                    }
370
                } else {
371
                    // Else, determine icon based on record type, or a generic fallback
372
                    $icon = $iconFactory->mapRecordTypeToIconIdentifier($foreignTable, $foreignRow);
373
                }
374
                // Add the item
375
                $items[] = [
376
                    $labelPrefix . BackendUtility::getRecordTitle($foreignTable, $foreignRow),
377
                    $foreignRow['uid'],
378
                    $icon
379
                ];
380
            }
381
        }
382
383
        return $items;
384
    }
385
386
    /**
387
     * Remove items using "keepItems" pageTsConfig
388
     *
389
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
390
     *
391
     * @param array $result Result array
392
     * @param string $fieldName Current handle field name
393
     * @param array $items Incoming items
394
     * @return array Modified item array
395
     */
396
    protected function removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
397
    {
398
        $table = $result['tableName'];
399
        if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
400
            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
401
        ) {
402
            return $items;
403
        }
404
405
        // If keepItems is set but is an empty list all current items get removed
406
        if ($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'] === '') {
407
            return [];
408
        }
409
410
        return ArrayUtility::keepItemsInArray(
411
            $items,
412
            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'],
413
            function ($value) {
414
                return $value[1];
415
            }
416
        );
417
    }
418
419
    /**
420
     * Remove items using "removeItems" pageTsConfig
421
     *
422
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
423
     *
424
     * @param array $result Result array
425
     * @param string $fieldName Current handle field name
426
     * @param array $items Incoming items
427
     * @return array Modified item array
428
     */
429
    protected function removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
430
    {
431
        $table = $result['tableName'];
432
        if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
433
            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
434
            || $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'] === ''
435
        ) {
436
            return $items;
437
        }
438
439
        $removeItems = array_flip(GeneralUtility::trimExplode(
440
            ',',
441
            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'],
442
            true
443
        ));
444
        foreach ($items as $key => $itemValues) {
445
            if (isset($removeItems[$itemValues[1]])) {
446
                unset($items[$key]);
447
            }
448
        }
449
450
        return $items;
451
    }
452
453
    /**
454
     * Remove items user restriction on language field
455
     *
456
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
457
     *
458
     * @param array $result Result array
459
     * @param string $fieldName Current handle field name
460
     * @param array $items Incoming items
461
     * @return array Modified item array
462
     */
463
    protected function removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
464
    {
465
        // Guard clause returns if not a language field is handled
466
        if (empty($result['processedTca']['ctrl']['languageField'])
467
            || $result['processedTca']['ctrl']['languageField'] !== $fieldName
468
        ) {
469
            return $items;
470
        }
471
472
        $backendUser = $this->getBackendUser();
473
        foreach ($items as $key => $itemValues) {
474
            if (!$backendUser->checkLanguageAccess($itemValues[1])) {
475
                unset($items[$key]);
476
            }
477
        }
478
479
        return $items;
480
    }
481
482
    /**
483
     * Remove items by user restriction on authMode items
484
     *
485
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
486
     *
487
     * @param array $result Result array
488
     * @param string $fieldName Current handle field name
489
     * @param array $items Incoming items
490
     * @return array Modified item array
491
     */
492
    protected function removeItemsByUserAuthMode(array $result, $fieldName, array $items)
493
    {
494
        // Guard clause returns early if no authMode field is configured
495
        if (!isset($result['processedTca']['columns'][$fieldName]['config']['authMode'])
496
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['authMode'])
497
        ) {
498
            return $items;
499
        }
500
501
        $backendUser = $this->getBackendUser();
502
        $authMode = $result['processedTca']['columns'][$fieldName]['config']['authMode'];
503
        foreach ($items as $key => $itemValues) {
504
            // @todo: checkAuthMode() uses $GLOBAL access for "individual" authMode - get rid of this
505
            if (!$backendUser->checkAuthMode($result['tableName'], $fieldName, $itemValues[1], $authMode)) {
506
                unset($items[$key]);
507
            }
508
        }
509
510
        return $items;
511
    }
512
513
    /**
514
     * Remove items if doktype is handled for non admin users
515
     *
516
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
517
     *
518
     * @param array $result Result array
519
     * @param string $fieldName Current handle field name
520
     * @param array $items Incoming items
521
     * @return array Modified item array
522
     */
523
    protected function removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
524
    {
525
        $table = $result['tableName'];
526
        $backendUser = $this->getBackendUser();
527
        // Guard clause returns if not correct table and field or if user is admin
528
        if ($table !== 'pages' || $fieldName !== 'doktype' || $backendUser->isAdmin()
529
        ) {
530
            return $items;
531
        }
532
533
        $allowedPageTypes = $backendUser->groupData['pagetypes_select'];
534
        foreach ($items as $key => $itemValues) {
535
            if (!GeneralUtility::inList($allowedPageTypes, $itemValues[1])) {
536
                unset($items[$key]);
537
            }
538
        }
539
540
        return $items;
541
    }
542
543
    /**
544
     * Remove items if sys_file_storage is not allowed for non-admin users.
545
     *
546
     * Used by TcaSelectItems data providers
547
     *
548
     * @param array $result Result array
549
     * @param string $fieldName Current handle field name
550
     * @param array $items Incoming items
551
     * @return array Modified item array
552
     */
553
    protected function removeItemsByUserStorageRestriction(array $result, $fieldName, array $items)
554
    {
555
        $referencedTableName = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'] ?? null;
556
        if ($referencedTableName !== 'sys_file_storage') {
557
            return $items;
558
        }
559
560
        $allowedStorageIds = array_map(
561
            function (ResourceStorage $storage) {
562
                return $storage->getUid();
563
            },
564
            $this->getBackendUser()->getFileStorages()
565
        );
566
567
        return array_filter(
568
            $items,
569
            function (array $item) use ($allowedStorageIds) {
570
                $itemValue = $item[1] ?? null;
571
                return empty($itemValue)
572
                    || in_array((int)$itemValue, $allowedStorageIds, true);
573
            }
574
        );
575
    }
576
577
    /**
578
     * Build query to fetch foreign records. Helper method of
579
     * addItemsFromForeignTable(), do not call otherwise.
580
     *
581
     * @param array $result Result array
582
     * @param string $localFieldName Current handle field name
583
     * @return QueryBuilder
584
     */
585
    protected function buildForeignTableQueryBuilder(array $result, string $localFieldName): QueryBuilder
586
    {
587
        $backendUser = $this->getBackendUser();
588
589
        $foreignTableName = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table'];
590
        $foreignTableClauseArray = $this->processForeignTableClause($result, $foreignTableName, $localFieldName);
591
592
        $fieldList = BackendUtility::getCommonSelectFields($foreignTableName, $foreignTableName . '.');
593
        /** @var QueryBuilder $queryBuilder */
594
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
595
            ->getQueryBuilderForTable($foreignTableName);
596
597
        $queryBuilder->getRestrictions()
598
            ->removeAll()
599
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
600
601
        $queryBuilder
602
            ->select(...GeneralUtility::trimExplode(',', $fieldList, true))
603
            ->from($foreignTableName)
604
            ->where($foreignTableClauseArray['WHERE']);
605
606
        if (!empty($foreignTableClauseArray['GROUPBY'])) {
607
            $queryBuilder->groupBy(...$foreignTableClauseArray['GROUPBY']);
608
        }
609
610
        if (!empty($foreignTableClauseArray['ORDERBY'])) {
611
            foreach ($foreignTableClauseArray['ORDERBY'] as $orderPair) {
612
                [$fieldName, $order] = $orderPair;
613
                $queryBuilder->addOrderBy($fieldName, $order);
614
            }
615
        } elseif (!empty($GLOBALS['TCA'][$foreignTableName]['ctrl']['default_sortby'])) {
616
            $orderByClauses = QueryHelper::parseOrderBy($GLOBALS['TCA'][$foreignTableName]['ctrl']['default_sortby']);
617
            foreach ($orderByClauses as $orderByClause) {
618
                if (!empty($orderByClause[0])) {
619
                    $queryBuilder->addOrderBy($foreignTableName . '.' . $orderByClause[0], $orderByClause[1]);
620
                }
621
            }
622
        }
623
624
        if (!empty($foreignTableClauseArray['LIMIT'])) {
625
            if (!empty($foreignTableClauseArray['LIMIT'][1])) {
626
                $queryBuilder->setMaxResults($foreignTableClauseArray['LIMIT'][1]);
627
                $queryBuilder->setFirstResult($foreignTableClauseArray['LIMIT'][0]);
628
            } elseif (!empty($foreignTableClauseArray['LIMIT'][0])) {
629
                $queryBuilder->setMaxResults($foreignTableClauseArray['LIMIT'][0]);
630
            }
631
        }
632
633
        // rootLevel = -1 means that elements can be on the rootlevel OR on any page (pid!=-1)
634
        // rootLevel = 0 means that elements are not allowed on root level
635
        // rootLevel = 1 means that elements are only on the root level (pid=0)
636
        $rootLevel = 0;
637
        if (isset($GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'])) {
638
            $rootLevel = (int)$GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'];
639
        }
640
641
        if ($rootLevel === -1) {
642
            $queryBuilder->andWhere(
643
                $queryBuilder->expr()->neq(
644
                    $foreignTableName . '.pid',
645
                    $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
646
                )
647
            );
648
        } elseif ($rootLevel === 1) {
649
            $queryBuilder->andWhere(
650
                $queryBuilder->expr()->eq(
651
                    $foreignTableName . '.pid',
652
                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
653
                )
654
            );
655
        } else {
656
            $queryBuilder->andWhere($backendUser->getPagePermsClause(Permission::PAGE_SHOW));
657
            if ($foreignTableName !== 'pages') {
658
                $queryBuilder
659
                    ->from('pages')
660
                    ->andWhere(
661
                        $queryBuilder->expr()->eq(
662
                            'pages.uid',
663
                            $queryBuilder->quoteIdentifier($foreignTableName . '.pid')
664
                        )
665
                    );
666
            }
667
        }
668
669
        return $queryBuilder;
670
    }
671
672
    /**
673
     * Replace markers in a where clause from TCA foreign_table_where
674
     *
675
     * ###REC_FIELD_[field name]###
676
     * ###THIS_UID### - is current element uid (zero if new).
677
     * ###CURRENT_PID### - is the current page id (pid of the record).
678
     * ###SITEROOT###
679
     * ###PAGE_TSCONFIG_ID### - a value you can set from Page TSconfig dynamically.
680
     * ###PAGE_TSCONFIG_IDLIST### - a value you can set from Page TSconfig dynamically.
681
     * ###PAGE_TSCONFIG_STR### - a value you can set from Page TSconfig dynamically.
682
     *
683
     * @param array $result Result array
684
     * @param string $foreignTableName Name of foreign table
685
     * @param string $localFieldName Current handle field name
686
     * @return array Query parts with keys WHERE, ORDERBY, GROUPBY, LIMIT
687
     */
688
    protected function processForeignTableClause(array $result, $foreignTableName, $localFieldName)
689
    {
690
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($foreignTableName);
691
        $localTable = $result['tableName'];
692
        $effectivePid = $result['effectivePid'];
693
694
        $foreignTableClause = '';
695
        if (!empty($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
696
            && is_string($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
697
        ) {
698
            $foreignTableClause = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'];
699
            // Replace possible markers in query
700
            if (strpos($foreignTableClause, '###REC_FIELD_') !== false) {
701
                // " AND table.field='###REC_FIELD_field1###' AND ..." -> array(" AND table.field='", "field1###' AND ...")
702
                $whereClauseParts = explode('###REC_FIELD_', $foreignTableClause);
703
                foreach ($whereClauseParts as $key => $value) {
704
                    if ($key !== 0) {
705
                        // "field1###' AND ..." -> array("field1", "' AND ...")
706
                        $whereClauseSubParts = explode('###', $value, 2);
707
                        // @todo: Throw exception if there is no value? What happens for NEW records?
708
                        $databaseRowKey = empty($result['flexParentDatabaseRow']) ? 'databaseRow' : 'flexParentDatabaseRow';
709
                        $rowFieldValue = $result[$databaseRowKey][$whereClauseSubParts[0]] ?? '';
710
                        if (is_array($rowFieldValue)) {
711
                            // If a select or group field is used here, it may have been processed already and
712
                            // is now an array containing uid + table + title + row.
713
                            // See TcaGroup data provider for details.
714
                            // Pick the first one (always on 0), and use uid only.
715
                            $rowFieldValue = $rowFieldValue[0]['uid'] ?? $rowFieldValue[0];
716
                        }
717
                        if (substr($whereClauseParts[0], -1) === '\'' && $whereClauseSubParts[1][0] === '\'') {
718
                            $whereClauseParts[0] = substr($whereClauseParts[0], 0, -1);
719
                            $whereClauseSubParts[1] = substr($whereClauseSubParts[1], 1);
720
                        }
721
                        $whereClauseParts[$key] = $connection->quote($rowFieldValue) . $whereClauseSubParts[1];
722
                    }
723
                }
724
                $foreignTableClause = implode('', $whereClauseParts);
725
            }
726
            if (strpos($foreignTableClause, '###CURRENT_PID###') !== false) {
727
                // Use pid from parent page clause if in flex form context
728
                if (!empty($result['flexParentDatabaseRow']['pid'])) {
729
                    $effectivePid = $result['flexParentDatabaseRow']['pid'];
730
                } elseif (!$effectivePid && !empty($result['databaseRow']['pid'])) {
731
                    // Use pid from database row if in inline context
732
                    $effectivePid = $result['databaseRow']['pid'];
733
                }
734
            }
735
736
            $siteRootUid = 0;
737
            foreach ($result['rootline'] as $rootlinePage) {
738
                if (!empty($rootlinePage['is_siteroot'])) {
739
                    $siteRootUid = (int)$rootlinePage['uid'];
740
                    break;
741
                }
742
            }
743
744
            $pageTsConfigId = 0;
745
            if (isset($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'])
746
                && $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID']
747
            ) {
748
                $pageTsConfigId = (int)$result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'];
749
            }
750
751
            $pageTsConfigIdList = 0;
752
            if (isset($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'])
753
                && $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST']
754
            ) {
755
                $pageTsConfigIdList = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'];
756
            }
757
            $pageTsConfigIdListArray = GeneralUtility::trimExplode(',', $pageTsConfigIdList, true);
758
            $pageTsConfigIdList = [];
759
            foreach ($pageTsConfigIdListArray as $pageTsConfigIdListElement) {
760
                if (MathUtility::canBeInterpretedAsInteger($pageTsConfigIdListElement)) {
761
                    $pageTsConfigIdList[] = (int)$pageTsConfigIdListElement;
762
                }
763
            }
764
            $pageTsConfigIdList = implode(',', $pageTsConfigIdList);
765
766
            $pageTsConfigString = '';
767
            if (isset($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'])
768
                && $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR']
769
            ) {
770
                $pageTsConfigString = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'];
771
                $pageTsConfigString = $connection->quote($pageTsConfigString);
772
            }
773
774
            $foreignTableClause = str_replace(
775
                [
776
                    '###CURRENT_PID###',
777
                    '###THIS_UID###',
778
                    '###SITEROOT###',
779
                    '###PAGE_TSCONFIG_ID###',
780
                    '###PAGE_TSCONFIG_IDLIST###',
781
                    '\'###PAGE_TSCONFIG_STR###\'',
782
                    '###PAGE_TSCONFIG_STR###'
783
                ],
784
                [
785
                    (int)$effectivePid,
786
                    (int)$result['databaseRow']['uid'],
787
                    $siteRootUid,
788
                    $pageTsConfigId,
789
                    $pageTsConfigIdList,
790
                    $pageTsConfigString,
791
                    $pageTsConfigString
792
                ],
793
                $foreignTableClause
794
            );
795
        }
796
797
        // Split the clause into an array with keys WHERE, GROUPBY, ORDERBY, LIMIT
798
        // Prepend a space to make sure "[[:space:]]+" will find a space there for the first element.
799
        $foreignTableClause = ' ' . $foreignTableClause;
800
        $foreignTableClauseArray = [
801
            'WHERE' => '',
802
            'GROUPBY' => '',
803
            'ORDERBY' => '',
804
            'LIMIT' => '',
805
        ];
806
        // Find LIMIT
807
        $reg = [];
808
        if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/is', $foreignTableClause, $reg)) {
809
            $foreignTableClauseArray['LIMIT'] = GeneralUtility::intExplode(',', trim($reg[2]), true);
810
            $foreignTableClause = $reg[1];
811
        }
812
        // Find ORDER BY
813
        $reg = [];
814
        if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._()"]+)$/is', $foreignTableClause, $reg)) {
815
            $foreignTableClauseArray['ORDERBY'] = QueryHelper::parseOrderBy(trim($reg[2]));
816
            $foreignTableClause = $reg[1];
817
        }
818
        // Find GROUP BY
819
        $reg = [];
820
        if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._()"]+)$/is', $foreignTableClause, $reg)) {
821
            $foreignTableClauseArray['GROUPBY'] = QueryHelper::parseGroupBy(trim($reg[2]));
822
            $foreignTableClause = $reg[1];
823
        }
824
        // Rest is assumed to be "WHERE" clause
825
        $foreignTableClauseArray['WHERE'] = QueryHelper::stripLogicalOperatorPrefix($foreignTableClause);
826
827
        return $foreignTableClauseArray;
828
    }
829
830
    /**
831
     * Convert the current database values into an array
832
     *
833
     * @param array $row database row
834
     * @param string $fieldName fieldname to process
835
     * @return array
836
     */
837
    protected function processDatabaseFieldValue(array $row, $fieldName)
838
    {
839
        $currentDatabaseValues = array_key_exists($fieldName, $row)
840
            ? $row[$fieldName]
841
            : '';
842
        if (!is_array($currentDatabaseValues)) {
843
            $currentDatabaseValues = GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
844
        }
845
        return $currentDatabaseValues;
846
    }
847
848
    /**
849
     * Validate and sanitize database row values of the select field with the given name.
850
     * Creates an array out of databaseRow[selectField] values.
851
     *
852
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
853
     *
854
     * @param array $result The current result array.
855
     * @param string $fieldName Name of the current select field.
856
     * @param array $staticValues Array with statically defined items, item value is used as array key.
857
     * @return array
858
     */
859
    protected function processSelectFieldValue(array $result, $fieldName, array $staticValues)
860
    {
861
        $fieldConfig = $result['processedTca']['columns'][$fieldName];
862
863
        $currentDatabaseValueArray = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : [];
864
        $newDatabaseValueArray = [];
865
866
        // Add all values that were defined by static methods and do not come from the relation
867
        // e.g. TCA, TSconfig, itemProcFunc etc.
868
        foreach ($currentDatabaseValueArray as $value) {
869
            if (isset($staticValues[$value])) {
870
                $newDatabaseValueArray[] = $value;
871
            }
872
        }
873
874
        if (isset($fieldConfig['config']['foreign_table']) && !empty($fieldConfig['config']['foreign_table'])) {
875
            /** @var RelationHandler $relationHandler */
876
            $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
877
            $relationHandler->registerNonTableValues = !empty($fieldConfig['config']['allowNonIdValues']);
878
            if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') {
879
                // MM relation
880
                $relationHandler->start(
881
                    implode(',', $currentDatabaseValueArray),
882
                    $fieldConfig['config']['foreign_table'],
883
                    $fieldConfig['config']['MM'],
884
                    $result['databaseRow']['uid'],
885
                    $result['tableName'],
886
                    $fieldConfig['config']
887
                );
888
                $newDatabaseValueArray = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
889
            } else {
890
                // Non MM relation
891
                // If not dealing with MM relations, use default live uid, not versioned uid for record relations
892
                $relationHandler->start(
893
                    implode(',', $currentDatabaseValueArray),
894
                    $fieldConfig['config']['foreign_table'],
895
                    '',
896
                    $this->getLiveUid($result),
897
                    $result['tableName'],
898
                    $fieldConfig['config']
899
                );
900
                $databaseIds = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
901
                // remove all items from the current DB values if not available as relation or static value anymore
902
                $newDatabaseValueArray = array_values(array_intersect($currentDatabaseValueArray, $databaseIds));
903
            }
904
        }
905
906
        if ($fieldConfig['config']['multiple'] ?? false) {
907
            return $newDatabaseValueArray;
908
        }
909
        return array_unique($newDatabaseValueArray);
910
    }
911
912
    /**
913
     * Translate the item labels
914
     *
915
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
916
     *
917
     * @param array $result Result array
918
     * @param array $itemArray Items
919
     * @param string $table
920
     * @param string $fieldName
921
     * @return array
922
     */
923
    public function translateLabels(array $result, array $itemArray, $table, $fieldName)
924
    {
925
        $languageService = $this->getLanguageService();
926
927
        foreach ($itemArray as $key => $item) {
928
            if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
929
                && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
930
            ) {
931
                $label = $languageService->sL($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]]);
932
            } else {
933
                $label = $languageService->sL(trim($item[0]));
934
            }
935
            $value = strlen((string)$item[1]) > 0 ? $item[1] : '';
936
            $icon = !empty($item[2]) ? $item[2] : null;
937
            $groupId = $item[3] ?? null;
938
            $helpText = null;
939
            if (!empty($item[4])) {
940
                if (\is_string($item[4])) {
941
                    $helpText = $languageService->sL($item[4]);
942
                } else {
943
                    $helpText = $item[4];
944
                }
945
            }
946
            $itemArray[$key] = [
947
                $label,
948
                $value,
949
                $icon,
950
                $groupId,
951
                $helpText
952
            ];
953
        }
954
955
        return $itemArray;
956
    }
957
958
    /**
959
     * Add alternative icon using "altIcons" TSconfig
960
     *
961
     * @param array $result
962
     * @param array $items
963
     * @param string $table
964
     * @param string $fieldName
965
     *
966
     * @return array
967
     */
968
    public function addIconFromAltIcons(array $result, array $items, string $table, string $fieldName): array
969
    {
970
        foreach ($items as &$item) {
971
            if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altIcons.'][$item[1]])
972
                && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altIcons.'][$item[1]])
973
            ) {
974
                $item[2] = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altIcons.'][$item[1]];
975
            }
976
        }
977
978
        return $items;
979
    }
980
981
    /**
982
     * Sanitize incoming item array
983
     *
984
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
985
     *
986
     * @param mixed $itemArray
987
     * @param string $tableName
988
     * @param string $fieldName
989
     * @throws \UnexpectedValueException
990
     * @return array
991
     */
992
    public function sanitizeItemArray($itemArray, $tableName, $fieldName)
993
    {
994
        if (!is_array($itemArray)) {
995
            $itemArray = [];
996
        }
997
        foreach ($itemArray as $item) {
998
            if (!is_array($item)) {
999
                throw new \UnexpectedValueException(
1000
                    'An item in field ' . $fieldName . ' of table ' . $tableName . ' is not an array as expected',
1001
                    1439288036
1002
                );
1003
            }
1004
        }
1005
1006
        return $itemArray;
1007
    }
1008
1009
    /**
1010
     * Gets the record uid of the live default record. If already
1011
     * pointing to the live record, the submitted record uid is returned.
1012
     *
1013
     * @param array $result Result array
1014
     * @return int
1015
     * @throws \UnexpectedValueException
1016
     */
1017
    protected function getLiveUid(array $result)
1018
    {
1019
        $table = $result['tableName'];
1020
        $row = $result['databaseRow'];
1021
        $uid = $row['uid'];
1022
        if (BackendUtility::isTableWorkspaceEnabled($table) && (int)$row['t3ver_oid'] > 0) {
1023
            $uid = $row['t3ver_oid'];
1024
        }
1025
        return $uid;
1026
    }
1027
1028
    /**
1029
     * @return LanguageService
1030
     */
1031
    protected function getLanguageService()
1032
    {
1033
        return $GLOBALS['LANG'];
1034
    }
1035
1036
    /**
1037
     * @return BackendUserAuthentication
1038
     */
1039
    protected function getBackendUser()
1040
    {
1041
        return $GLOBALS['BE_USER'];
1042
    }
1043
}
1044