Test Failed
Branch master (137376)
by Tymoteusz
20:39
created

AbstractItemProvider::addItemsFromSpecial()   D

Complexity

Conditions 43
Paths 14

Size

Total Lines 206
Code Lines 134

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 43
eloc 134
nc 14
nop 3
dl 0
loc 206
rs 4.1818
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
namespace TYPO3\CMS\Backend\Form\FormDataProvider;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Doctrine\DBAL\DBALException;
18
use TYPO3\CMS\Backend\Module\ModuleLoader;
19
use TYPO3\CMS\Backend\Utility\BackendUtility;
20
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21
use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException;
22
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
23
use TYPO3\CMS\Core\Database\ConnectionPool;
24
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
25
use TYPO3\CMS\Core\Database\Query\QueryHelper;
26
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
27
use TYPO3\CMS\Core\Database\RelationHandler;
28
use TYPO3\CMS\Core\Imaging\Icon;
29
use TYPO3\CMS\Core\Imaging\IconFactory;
30
use TYPO3\CMS\Core\Imaging\IconRegistry;
31
use TYPO3\CMS\Core\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\Utility\ArrayUtility;
37
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
use TYPO3\CMS\Core\Utility\MathUtility;
39
40
/**
41
 * Contains methods used by Data providers that handle elements
42
 * with single items like select, radio and some more.
43
 */
44
abstract class AbstractItemProvider
45
{
46
    /**
47
     * Resolve "itemProcFunc" of elements.
48
     *
49
     * @param array $result Main result array
50
     * @param string $fieldName Field name to handle item list for
51
     * @param array $items Existing items array
52
     * @return array New list of item elements
53
     */
54
    protected function resolveItemProcessorFunction(array $result, $fieldName, array $items)
55
    {
56
        $table = $result['tableName'];
57
        $config = $result['processedTca']['columns'][$fieldName]['config'];
58
59
        $pageTsProcessorParameters = null;
60
        if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'])) {
61
            $pageTsProcessorParameters = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['itemsProcFunc.'];
62
        }
63
        $processorParameters = [
64
            // Function manipulates $items directly and return nothing
65
            'items' => &$items,
66
            'config' => $config,
67
            'TSconfig' => $pageTsProcessorParameters,
68
            'table' => $table,
69
            'row' => $result['databaseRow'],
70
            'field' => $fieldName,
71
        ];
72
        if (!empty($result['flexParentDatabaseRow'])) {
73
            $processorParameters['flexParentDatabaseRow'] = $result['flexParentDatabaseRow'];
74
        }
75
76
        try {
77
            GeneralUtility::callUserFunction($config['itemsProcFunc'], $processorParameters, $this);
78
        } catch (\Exception $exception) {
79
            // The itemsProcFunc method may throw an exception, create a flash message if so
80
            $languageService = $this->getLanguageService();
81
            $fieldLabel = $fieldName;
82 View Code Duplication
            if (!empty($result['processedTca']['columns'][$fieldName]['label'])) {
83
                $fieldLabel = $languageService->sL($result['processedTca']['columns'][$fieldName]['label']);
84
            }
85
            $message = sprintf(
86
                $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:error.items_proc_func_error'),
87
                $fieldLabel,
88
                $exception->getMessage()
89
            );
90
            /** @var FlashMessage $flashMessage */
91
            $flashMessage = GeneralUtility::makeInstance(
92
                FlashMessage::class,
93
                $message,
0 ignored issues
show
Bug introduced by
$message of type string is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

93
                /** @scrutinizer ignore-type */ $message,
Loading history...
94
                '',
95
                FlashMessage::ERROR,
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Messaging\FlashMessage::ERROR of type integer is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

95
                /** @scrutinizer ignore-type */ FlashMessage::ERROR,
Loading history...
96
                true
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

96
                /** @scrutinizer ignore-type */ true
Loading history...
97
            );
98
            /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
99
            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
100
            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
101
            $defaultFlashMessageQueue->enqueue($flashMessage);
102
        }
103
104
        return $items;
105
    }
106
107
    /**
108
     * PageTsConfig addItems:
109
     *
110
     * TCEFORMS.aTable.aField[.types][.aType].addItems.aValue = aLabel,
111
     * with type specific options merged by pageTsConfig already
112
     *
113
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
114
     *
115
     * @param array $result result array
116
     * @param string $fieldName Current handle field name
117
     * @param array $items Incoming items
118
     * @return array Modified item array
119
     */
120
    protected function addItemsFromPageTsConfig(array $result, $fieldName, array $items)
121
    {
122
        $table = $result['tableName'];
123
        if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
124
            && is_array($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'])
125
        ) {
126
            $addItemsArray = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['addItems.'];
127
            foreach ($addItemsArray as $value => $label) {
128
                // If the value ends with a dot, it is a subelement like "34.icon = mylabel.png", skip it
129
                if (substr($value, -1) === '.') {
130
                    continue;
131
                }
132
                // Check if value "34 = mylabel" also has a "34.icon = myImage.png"
133
                $iconIdentifier = null;
134
                if (isset($addItemsArray[$value . '.'])
135
                    && is_array($addItemsArray[$value . '.'])
136
                    && !empty($addItemsArray[$value . '.']['icon'])
137
                ) {
138
                    $iconIdentifier = $addItemsArray[$value . '.']['icon'];
139
                }
140
                $items[] = [$label, $value, $iconIdentifier];
141
            }
142
        }
143
        return $items;
144
    }
145
146
    /**
147
     * TCA config "special" evaluation. Add them to $items
148
     *
149
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
150
     *
151
     * @param array $result Result array
152
     * @param string $fieldName Current handle field name
153
     * @param array $items Incoming items
154
     * @return array Modified item array
155
     * @throws \UnexpectedValueException
156
     */
157
    protected function addItemsFromSpecial(array $result, $fieldName, array $items)
158
    {
159
        // Guard
160
        if (empty($result['processedTca']['columns'][$fieldName]['config']['special'])
161
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['special'])
162
        ) {
163
            return $items;
164
        }
165
166
        $languageService = $this->getLanguageService();
167
        $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
168
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
169
170
        $special = $result['processedTca']['columns'][$fieldName]['config']['special'];
171
        switch (true) {
172
            case $special === 'tables':
173
                foreach ($GLOBALS['TCA'] as $currentTable => $_) {
174
                    if (!empty($GLOBALS['TCA'][$currentTable]['ctrl']['adminOnly'])) {
175
                        // Hide "admin only" tables
176
                        continue;
177
                    }
178
                    $label = !empty($GLOBALS['TCA'][$currentTable]['ctrl']['title']) ? $GLOBALS['TCA'][$currentTable]['ctrl']['title'] : '';
179
                    $icon = $iconFactory->mapRecordTypeToIconIdentifier($currentTable, []);
180
                    $helpText = [];
181
                    $languageService->loadSingleTableDescription($currentTable);
182
                    // @todo: check if this actually works, currently help texts are missing
183
                    $helpTextArray = $GLOBALS['TCA_DESCR'][$currentTable]['columns'][''];
184
                    if (!empty($helpTextArray['description'])) {
185
                        $helpText['description'] = $helpTextArray['description'];
186
                    }
187
                    $items[] = [$label, $currentTable, $icon, $helpText];
188
                }
189
                break;
190
            case $special === 'pagetypes':
191
                if (isset($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
192
                    && is_array($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
193
                ) {
194
                    $specialItems = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
195
                    foreach ($specialItems as $specialItem) {
196
                        if (!is_array($specialItem) || $specialItem[1] === '--div--') {
197
                            // Skip non arrays and divider items
198
                            continue;
199
                        }
200
                        $label = $specialItem[0];
201
                        $value = $specialItem[1];
202
                        $icon = $iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $specialItem[1]]);
203
                        $items[] = [$label, $value, $icon];
204
                    }
205
                }
206
                break;
207
            case $special === 'exclude':
208
                $excludeArrays = $this->getExcludeFields();
209
                foreach ($excludeArrays as $excludeArray) {
210
                    // If the field comes from a FlexForm, the syntax is more complex
211
                    if ($excludeArray['origin'] === 'flexForm') {
212
                        // The field comes from a plugins FlexForm
213
                        // Add header if not yet set for plugin section
214
                        if (!isset($items[$excludeArray['sectionHeader']])) {
215
                            // there is no icon handling for plugins - we take the icon from the table
216
                            $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], []);
217
                            $items[$excludeArray['sectionHeader']] = [
218
                                $excludeArray['sectionHeader'],
219
                                '--div--',
220
                                $icon
221
                            ];
222
                        }
223
                    } else {
224
                        // Add header if not yet set for table
225
                        if (!isset($items[$excludeArray['table']])) {
226
                            $icon = $iconFactory->mapRecordTypeToIconIdentifier($excludeArray['table'], []);
227
                            $items[$excludeArray['table']] = [
228
                                $GLOBALS['TCA'][$excludeArray['table']]['ctrl']['title'],
229
                                '--div--',
230
                                $icon
231
                            ];
232
                        }
233
                    }
234
                    // Add help text
235
                    $helpText = [];
236
                    $languageService->loadSingleTableDescription($excludeArray['table']);
237
                    $helpTextArray = $GLOBALS['TCA_DESCR'][$excludeArray['table']]['columns'][$excludeArray['table']];
238
                    if (!empty($helpTextArray['description'])) {
239
                        $helpText['description'] = $helpTextArray['description'];
240
                    }
241
                    // Item configuration:
242
                    $items[] = [
243
                        rtrim($excludeArray['origin'] === 'flexForm' ? $excludeArray['fieldLabel'] : $languageService->sL($GLOBALS['TCA'][$excludeArray['table']]['columns'][$excludeArray['fieldName']]['label']), ':') . ' (' . $excludeArray['fieldName'] . ')',
244
                        $excludeArray['table'] . ':' . $excludeArray['fullField'] ,
245
                        'empty-empty',
246
                        $helpText
247
                    ];
248
                }
249
                break;
250
            case $special === 'explicitValues':
251
                $theTypes = $this->getExplicitAuthFieldValues();
252
                $icons = [
253
                    'ALLOW' => 'status-status-permission-granted',
254
                    'DENY' => 'status-status-permission-denied'
255
                ];
256
                // Traverse types:
257
                foreach ($theTypes as $tableFieldKey => $theTypeArrays) {
258
                    if (is_array($theTypeArrays['items'])) {
259
                        // Add header:
260
                        $items[] = [
261
                            $theTypeArrays['tableFieldLabel'],
262
                            '--div--',
263
                        ];
264
                        // Traverse options for this field:
265
                        foreach ($theTypeArrays['items'] as $itemValue => $itemContent) {
266
                            // Add item to be selected:
267
                            $items[] = [
268
                                '[' . $itemContent[2] . '] ' . $itemContent[1],
269
                                $tableFieldKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $itemContent[0],
270
                                $icons[$itemContent[0]]
271
                            ];
272
                        }
273
                    }
274
                }
275
                break;
276
            case $special === 'languages':
277
                foreach ($result['systemLanguageRows'] as $language) {
278
                    if ($language['uid'] !== -1) {
279
                        $items[] = [
280
                            0 => $language['title'] . ' [' . $language['uid'] . ']',
281
                            1 => $language['uid'],
282
                            2 => $language['flagIconIdentifier']
283
                        ];
284
                    }
285
                }
286
                break;
287
            case $special === 'custom':
288
                $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'];
289
                if (is_array($customOptions)) {
290
                    foreach ($customOptions as $coKey => $coValue) {
291
                        if (is_array($coValue['items'])) {
292
                            // Add header:
293
                            $items[] = [
294
                                $languageService->sL($coValue['header']),
295
                                '--div--'
296
                            ];
297
                            // Traverse items:
298
                            foreach ($coValue['items'] as $itemKey => $itemCfg) {
299
                                $icon = 'empty-empty';
300
                                $helpText = [];
301
                                if (!empty($itemCfg[1])) {
302
                                    if ($iconRegistry->isRegistered($itemCfg[1])) {
303
                                        // Use icon identifier when registered
304
                                        $icon = $itemCfg[1];
305
                                    }
306
                                }
307
                                if (!empty($itemCfg[2])) {
308
                                    $helpText['description'] = $languageService->sL($itemCfg[2]);
309
                                }
310
                                $items[] = [
311
                                    $languageService->sL($itemCfg[0]),
312
                                    $coKey . ':' . preg_replace('/[:|,]/', '', $itemKey),
313
                                    $icon,
314
                                    $helpText
315
                                ];
316
                            }
317
                        }
318
                    }
319
                }
320
                break;
321
            case $special === 'modListGroup' || $special === 'modListUser':
322
                /** @var ModuleLoader $loadModules */
323
                $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
324
                $loadModules->load($GLOBALS['TBE_MODULES']);
325
                $modList = $special === 'modListUser' ? $loadModules->modListUser : $loadModules->modListGroup;
326
                if (is_array($modList)) {
327
                    foreach ($modList as $theMod) {
328
                        $moduleLabels = $loadModules->getLabelsForModule($theMod);
329
                        list($mainModule, $subModule) = explode('_', $theMod, 2);
330
                        // Icon:
331
                        if (!empty($subModule)) {
332
                            $icon = $loadModules->modules[$mainModule]['sub'][$subModule]['iconIdentifier'];
333
                        } else {
334
                            $icon = $loadModules->modules[$theMod]['iconIdentifier'];
335
                        }
336
                        // Add help text
337
                        $helpText = [
338
                            'title' => $languageService->sL($moduleLabels['shortdescription']),
339
                            'description' => $languageService->sL($moduleLabels['description'])
340
                        ];
341
342
                        $label = '';
343
                        // Add label for main module if this is a submodule
344
                        if (!empty($subModule)) {
345
                            $mainModuleLabels = $loadModules->getLabelsForModule($mainModule);
346
                            $label .= $languageService->sL($mainModuleLabels['title']) . '>';
347
                        }
348
                        // Add modules own label now
349
                        $label .= $languageService->sL($moduleLabels['title']);
350
351
                        // Item configuration
352
                        $items[] = [$label, $theMod, $icon, $helpText];
353
                    }
354
                }
355
                break;
356
            default:
357
                throw new \UnexpectedValueException(
358
                    'Unknown special value ' . $special . ' for field ' . $fieldName . ' of table ' . $result['tableName'],
359
                    1439298496
360
                );
361
        }
362
        return $items;
363
    }
364
365
    /**
366
     * TCA config "fileFolder" evaluation. Add them to $items
367
     *
368
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
369
     *
370
     * @param array $result Result array
371
     * @param string $fieldName Current handle field name
372
     * @param array $items Incoming items
373
     * @return array Modified item array
374
     * @throws \RuntimeException
375
     */
376
    protected function addItemsFromFolder(array $result, $fieldName, array $items)
377
    {
378
        if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
379
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
380
        ) {
381
            return $items;
382
        }
383
384
        $fileFolderRaw = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
385
        $fileFolder = GeneralUtility::getFileAbsFileName($fileFolderRaw);
386
        if ($fileFolder === '') {
387
            throw new \RuntimeException(
388
                'Invalid folder given for item processing: ' . $fileFolderRaw . ' for table ' . $result['tableName'] . ', field ' . $fieldName,
389
                1479399227
390
            );
391
        }
392
        $fileFolder = rtrim($fileFolder, '/') . '/';
393
394
        if (@is_dir($fileFolder)) {
395
            $fileExtensionList = '';
396
            if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
397
                && is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
398
            ) {
399
                $fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
400
            }
401
            $recursionLevels = isset($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'])
402
                ? MathUtility::forceIntegerInRange($result['processedTca']['columns'][$fieldName]['config']['fileFolder_recursions'], 0, 99)
403
                : 99;
404
            $fileArray = GeneralUtility::getAllFilesAndFoldersInPath([], $fileFolder, $fileExtensionList, 0, $recursionLevels);
405
            $fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
406
            foreach ($fileArray as $fileReference) {
407
                $fileInformation = pathinfo($fileReference);
408
                $icon = GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($fileInformation['extension']))
409
                    ? $fileFolder . $fileReference
410
                    : '';
411
                $items[] = [
412
                    $fileReference,
413
                    $fileReference,
414
                    $icon
415
                ];
416
            }
417
        }
418
419
        return $items;
420
    }
421
422
    /**
423
     * TCA config "foreign_table" evaluation. Add them to $items
424
     *
425
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
426
     *
427
     * @param array $result Result array
428
     * @param string $fieldName Current handle field name
429
     * @param array $items Incoming items
430
     * @return array Modified item array
431
     * @throws \UnexpectedValueException
432
     */
433
    protected function addItemsFromForeignTable(array $result, $fieldName, array $items)
434
    {
435
        // Guard
436
        if (empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
437
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
438
        ) {
439
            return $items;
440
        }
441
442
        $languageService = $this->getLanguageService();
443
444
        $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
445
446 View Code Duplication
        if (!is_array($GLOBALS['TCA'][$foreignTable])) {
447
            throw new \UnexpectedValueException(
448
                'Field ' . $fieldName . ' of table ' . $result['tableName'] . ' reference to foreign table '
449
                . $foreignTable . ', but this table is not defined in TCA',
450
                1439569743
451
            );
452
        }
453
454
        $queryBuilder = $this->buildForeignTableQueryBuilder($result, $fieldName);
455
        try {
456
            $queryResult = $queryBuilder->execute();
457
        } catch (DBALException $e) {
458
            $databaseError = $e->getPrevious()->getMessage();
459
        }
460
461
        // Early return on error with flash message
462
        if (!empty($databaseError)) {
463
            $msg = $databaseError . '. ';
464
            $msg .= $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch');
465
            $msgTitle = $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:error.database_schema_mismatch_title');
466
            /** @var $flashMessage FlashMessage */
467
            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, true);
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Messaging\FlashMessage::ERROR of type integer is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

467
            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, /** @scrutinizer ignore-type */ FlashMessage::ERROR, true);
Loading history...
Bug introduced by
$msg of type string is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

467
            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, /** @scrutinizer ignore-type */ $msg, $msgTitle, FlashMessage::ERROR, true);
Loading history...
Bug introduced by
true of type true is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

467
            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, /** @scrutinizer ignore-type */ true);
Loading history...
468
            /** @var $flashMessageService FlashMessageService */
469
            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
470
            /** @var $defaultFlashMessageQueue FlashMessageQueue */
471
            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
472
            $defaultFlashMessageQueue->enqueue($flashMessage);
473
            return $items;
474
        }
475
476
        $labelPrefix = '';
477 View Code Duplication
        if (!empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'])) {
478
            $labelPrefix = $result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'];
479
            $labelPrefix = $languageService->sL($labelPrefix);
480
        }
481
482
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
483
        $fileRepository = GeneralUtility::makeInstance(FileRepository::class);
484
485
        while ($foreignRow = $queryResult->fetch()) {
486
            BackendUtility::workspaceOL($foreignTable, $foreignRow);
487
            if (is_array($foreignRow)) {
488
                // If the foreign table sets selicon_field, this field can contain an image
489
                // that represents this specific row.
490
                $iconFieldName = '';
491
                $isReferenceField = false;
492
                if (!empty($GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'])) {
493
                    $iconFieldName = $GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field'];
494
                    if ($GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['type'] === 'inline'
495
                        && $GLOBALS['TCA'][$foreignTable]['columns'][$iconFieldName]['config']['foreign_table'] === 'sys_file_reference') {
496
                        $isReferenceField = true;
497
                    }
498
                }
499
                $icon = '';
500
                if ($isReferenceField) {
501
                    $references = $fileRepository->findByRelation($foreignTable, $iconFieldName, $foreignRow['uid']);
502
                    if (is_array($references) && !empty($references)) {
503
                        $icon = reset($references);
504
                        $icon = $icon->getPublicUrl();
505
                    }
506
                } else {
507
                    $iconPath = '';
508
                    if (!empty($GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field_path'])) {
509
                        $iconPath = $GLOBALS['TCA'][$foreignTable]['ctrl']['selicon_field_path'];
510
                    }
511
                    if ($iconFieldName && $iconPath && $foreignRow[$iconFieldName]) {
512
                        // Prepare the row icon if available
513
                        $iParts = GeneralUtility::trimExplode(',', $foreignRow[$iconFieldName], true);
514
                        $icon = $iconPath . '/' . trim($iParts[0]);
515
                    } else {
516
                        // Else, determine icon based on record type, or a generic fallback
517
                        $icon = $iconFactory->mapRecordTypeToIconIdentifier($foreignTable, $foreignRow);
518
                    }
519
                }
520
                // Add the item
521
                $items[] = [
522
                    $labelPrefix . BackendUtility::getRecordTitle($foreignTable, $foreignRow),
523
                    $foreignRow['uid'],
524
                    $icon
525
                ];
526
            }
527
        }
528
529
        return $items;
530
    }
531
532
    /**
533
     * Remove items using "keepItems" pageTsConfig
534
     *
535
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
536
     *
537
     * @param array $result Result array
538
     * @param string $fieldName Current handle field name
539
     * @param array $items Incoming items
540
     * @return array Modified item array
541
     */
542
    protected function removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
543
    {
544
        $table = $result['tableName'];
545 View Code Duplication
        if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
546
            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
547
        ) {
548
            return $items;
549
        }
550
551
        // If keepItems is set but is an empty list all current items get removed
552
        if ($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'] === '') {
553
            return [];
554
        }
555
556
        return ArrayUtility::keepItemsInArray(
557
            $items,
558
            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'],
559
            function ($value) {
0 ignored issues
show
Bug introduced by
function(...) { /* ... */ } of type callable is incompatible with the type string expected by parameter $getValueFunc of TYPO3\CMS\Core\Utility\A...ity::keepItemsInArray(). ( Ignorable by Annotation )

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

559
            /** @scrutinizer ignore-type */ function ($value) {
Loading history...
560
                return $value[1];
561
            }
562
        );
563
    }
564
565
    /**
566
     * Remove items using "removeItems" pageTsConfig
567
     *
568
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
569
     *
570
     * @param array $result Result array
571
     * @param string $fieldName Current handle field name
572
     * @param array $items Incoming items
573
     * @return array Modified item array
574
     */
575
    protected function removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
576
    {
577
        $table = $result['tableName'];
578
        if (!isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
579
            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
580
            || $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'] === ''
581
        ) {
582
            return $items;
583
        }
584
585
        $removeItems = array_flip(GeneralUtility::trimExplode(
586
            ',',
587
            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'],
588
            true
589
        ));
590
        foreach ($items as $key => $itemValues) {
591
            if (isset($removeItems[$itemValues[1]])) {
592
                unset($items[$key]);
593
            }
594
        }
595
596
        return $items;
597
    }
598
599
    /**
600
     * Remove items user restriction on language field
601
     *
602
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
603
     *
604
     * @param array $result Result array
605
     * @param string $fieldName Current handle field name
606
     * @param array $items Incoming items
607
     * @return array Modified item array
608
     */
609
    protected function removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
610
    {
611
        // Guard clause returns if not a language field is handled
612 View Code Duplication
        if (empty($result['processedTca']['ctrl']['languageField'])
613
            || $result['processedTca']['ctrl']['languageField'] !== $fieldName
614
        ) {
615
            return $items;
616
        }
617
618
        $backendUser = $this->getBackendUser();
619
        foreach ($items as $key => $itemValues) {
620
            if (!$backendUser->checkLanguageAccess($itemValues[1])) {
621
                unset($items[$key]);
622
            }
623
        }
624
625
        return $items;
626
    }
627
628
    /**
629
     * Remove items by user restriction on authMode items
630
     *
631
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
632
     *
633
     * @param array $result Result array
634
     * @param string $fieldName Current handle field name
635
     * @param array $items Incoming items
636
     * @return array Modified item array
637
     */
638
    protected function removeItemsByUserAuthMode(array $result, $fieldName, array $items)
639
    {
640
        // Guard clause returns early if no authMode field is configured
641 View Code Duplication
        if (!isset($result['processedTca']['columns'][$fieldName]['config']['authMode'])
642
            || !is_string($result['processedTca']['columns'][$fieldName]['config']['authMode'])
643
        ) {
644
            return $items;
645
        }
646
647
        $backendUser = $this->getBackendUser();
648
        $authMode = $result['processedTca']['columns'][$fieldName]['config']['authMode'];
649
        foreach ($items as $key => $itemValues) {
650
            // @todo: checkAuthMode() uses $GLOBAL access for "individual" authMode - get rid of this
651
            if (!$backendUser->checkAuthMode($result['tableName'], $fieldName, $itemValues[1], $authMode)) {
652
                unset($items[$key]);
653
            }
654
        }
655
656
        return $items;
657
    }
658
659
    /**
660
     * Remove items if doktype is handled for non admin users
661
     *
662
     * Used by TcaSelectItems and TcaSelectTreeItems data providers
663
     *
664
     * @param array $result Result array
665
     * @param string $fieldName Current handle field name
666
     * @param array $items Incoming items
667
     * @return array Modified item array
668
     */
669
    protected function removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
670
    {
671
        $table = $result['tableName'];
672
        $backendUser = $this->getBackendUser();
673
        // Guard clause returns if not correct table and field or if user is admin
674
        if ($table !== 'pages' || $fieldName !== 'doktype' || $backendUser->isAdmin()
675
        ) {
676
            return $items;
677
        }
678
679
        $allowedPageTypes = $backendUser->groupData['pagetypes_select'];
680
        foreach ($items as $key => $itemValues) {
681
            if (!GeneralUtility::inList($allowedPageTypes, $itemValues[1])) {
682
                unset($items[$key]);
683
            }
684
        }
685
686
        return $items;
687
    }
688
689
    /**
690
     * Remove items if sys_file_storage is not allowed for non-admin users.
691
     *
692
     * Used by TcaSelectItems data providers
693
     *
694
     * @param array $result Result array
695
     * @param string $fieldName Current handle field name
696
     * @param array $items Incoming items
697
     * @return array Modified item array
698
     */
699
    protected function removeItemsByUserStorageRestriction(array $result, $fieldName, array $items)
700
    {
701
        $referencedTableName = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'] ?? null;
702
        if ($referencedTableName !== 'sys_file_storage') {
703
            return $items;
704
        }
705
706
        $allowedStorageIds = array_map(
707
            function (\TYPO3\CMS\Core\Resource\ResourceStorage $storage) {
708
                return $storage->getUid();
709
            },
710
            $this->getBackendUser()->getFileStorages()
711
        );
712
713
        return array_filter(
714
            $items,
715
            function (array $item) use ($allowedStorageIds) {
716
                $itemValue = $item[1] ?? null;
717
                return empty($itemValue)
718
                    || in_array((int)$itemValue, $allowedStorageIds, true);
719
            }
720
        );
721
    }
722
723
    /**
724
     * Returns an array with the exclude fields as defined in TCA and FlexForms
725
     * Used for listing the exclude fields in be_groups forms.
726
     *
727
     * @return array Array of arrays with excludeFields (fieldName, table:fieldName) from TCA
728
     *               and FlexForms (fieldName, table:extKey;sheetName;fieldName)
729
     */
730
    protected function getExcludeFields()
731
    {
732
        $languageService = $this->getLanguageService();
733
        $finalExcludeArray = [];
734
735
        // Fetch translations for table names
736
        $tableToTranslation = [];
737
        // All TCA keys
738
        foreach ($GLOBALS['TCA'] as $table => $conf) {
739
            $tableToTranslation[$table] = $languageService->sL($conf['ctrl']['title']);
740
        }
741
        // Sort by translations
742
        asort($tableToTranslation);
743
        foreach ($tableToTranslation as $table => $translatedTable) {
744
            $excludeArrayTable = [];
745
746
            // All field names configured and not restricted to admins
747
            if (is_array($GLOBALS['TCA'][$table]['columns'])
748
                && empty($GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
749
                && (empty($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
750
            ) {
751
                foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
752
                    if ($GLOBALS['TCA'][$table]['columns'][$field]['exclude']) {
753
                        // Get human readable names of fields
754
                        $translatedField = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$field]['label']);
755
                        // Add entry, key 'labels' needed for sorting
756
                        $excludeArrayTable[] = [
757
                            'labels' => $translatedTable . ':' . $translatedField,
758
                            'sectionHeader' => $translatedTable,
759
                            'table' => $table,
760
                            'tableField' => $field,
761
                            'fieldName' => $field,
762
                            'fullField' => $field,
763
                            'fieldLabel' => $translatedField,
764
                            'origin' => 'tca',
765
                        ];
766
                    }
767
                }
768
            }
769
            // All FlexForm fields
770
            $flexFormArray = $this->getRegisteredFlexForms($table);
771
            foreach ($flexFormArray as $tableField => $flexForms) {
772
                // Prefix for field label, e.g. "Plugin Options:"
773
                $labelPrefix = '';
774 View Code Duplication
                if (!empty($GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
775
                    $labelPrefix = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
776
                }
777
                // Get all sheets
778
                foreach ($flexForms as $extIdent => $extConf) {
779
                    // Get all fields in sheet
780
                    foreach ($extConf['sheets'] as $sheetName => $sheet) {
781
                        if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
782
                            continue;
783
                        }
784
                        foreach ($sheet['ROOT']['el'] as $pluginFieldName => $field) {
785
                            // Use only fields that have exclude flag set
786
                            if (empty($field['TCEforms']['exclude'])) {
787
                                continue;
788
                            }
789
                            $fieldLabel = !empty($field['TCEforms']['label'])
790
                                ? $languageService->sL($field['TCEforms']['label'])
791
                                : $pluginFieldName;
792
                            $excludeArrayTable[] = [
793
                                'labels' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ': ') . ':' . $fieldLabel,
794
                                'sectionHeader' => trim(($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent), ':'),
795
                                'table' => $table,
796
                                'tableField' => $tableField,
797
                                'extIdent' => $extIdent,
798
                                'fieldName' => $pluginFieldName,
799
                                'fullField' => $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $pluginFieldName,
800
                                'fieldLabel' => $fieldLabel,
801
                                'origin' => 'flexForm',
802
                            ];
803
                        }
804
                    }
805
                }
806
            }
807
            // Sort fields by the translated value
808
            if (!empty($excludeArrayTable)) {
809
                usort($excludeArrayTable, function (array $array1, array $array2) {
810
                    $array1 = reset($array1);
811
                    $array2 = reset($array2);
812
                    if (is_string($array1) && is_string($array2)) {
813
                        return strcasecmp($array1, $array2);
814
                    }
815
                    return 0;
816
                });
817
                $finalExcludeArray = array_merge($finalExcludeArray, $excludeArrayTable);
818
            }
819
        }
820
821
        return $finalExcludeArray;
822
    }
823
824
    /**
825
     * Returns FlexForm data structures it finds. Used in select "special" for be_groups
826
     * to set "exclude" flags for single flex form fields.
827
     *
828
     * This only finds flex forms registered in 'ds' config sections.
829
     * This does not resolve other sophisticated flex form data structure references.
830
     *
831
     * @todo: This approach is limited and doesn't find everything. It works for casual tt_content plugins, though:
832
     * @todo: The data structure identifier determination depends on data row, but we don't have all rows at hand here.
833
     * @todo: The code thus "guesses" some standard data structure identifier scenarios and tries to resolve those.
834
     * @todo: This guessing can not be solved in a good way. A general registry of "all" possible data structures is
835
     * @todo: probably not wanted, since that wouldn't work for truly dynamic DS calculations. Probably the only
836
     * @todo: thing we could do here is a hook to allow extensions declaring specific data structures to
837
     * @todo: allow backend admins to set exclude flags for certain fields in those cases.
838
     *
839
     * @param string $table Table to handle
840
     * @return array Data structures
841
     */
842
    protected function getRegisteredFlexForms($table)
843
    {
844
        if (empty($table) || empty($GLOBALS['TCA'][$table]['columns'])) {
845
            return [];
846
        }
847
        $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
848
        $flexForms = [];
849
        foreach ($GLOBALS['TCA'][$table]['columns'] as $tableField => $fieldConf) {
850
            if (!empty($fieldConf['config']['type']) && !empty($fieldConf['config']['ds']) && $fieldConf['config']['type'] === 'flex') {
851
                $flexForms[$tableField] = [];
852
                foreach (array_keys($fieldConf['config']['ds']) as $flexFormKey) {
853
                    // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
854
                    $identFields = GeneralUtility::trimExplode(',', $flexFormKey);
855
                    $extIdent = $identFields[0];
856
                    if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
857
                        $extIdent = $identFields[1];
858
                    }
859
                    $flexFormDataStructureIdentifier = json_encode([
860
                        'type' => 'tca',
861
                        'tableName' => $table,
862
                        'fieldName' => $tableField,
863
                        'dataStructureKey' => $flexFormKey,
864
                    ]);
865
                    try {
866
                        $dataStructure = $flexFormTools->parseDataStructureByIdentifier($flexFormDataStructureIdentifier);
867
                        $flexForms[$tableField][$extIdent] = $dataStructure;
868
                    } catch (InvalidIdentifierException $e) {
869
                        // Deliberately empty: The DS identifier is guesswork and the flex ds parser throws
870
                        // this exception if it can not resolve to a valid data structure. This is "ok" here
871
                        // and the exception is just eaten.
872
                    }
873
                }
874
            }
875
        }
876
        return $flexForms;
877
    }
878
879
    /**
880
     * Returns an array with explicit Allow/Deny fields.
881
     * Used for listing these field/value pairs in be_groups forms
882
     *
883
     * @return array Array with information from all of $GLOBALS['TCA']
884
     */
885
    protected function getExplicitAuthFieldValues()
886
    {
887
        $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

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