Completed
Push — master ( f66149...a7372c )
by
unknown
130:24 queued 111:56
created

BackendLayoutView::colPosListItemProcFunc()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
namespace TYPO3\CMS\Backend\View;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use TYPO3\CMS\Backend\Utility\BackendUtility;
18
use TYPO3\CMS\Core\Database\ConnectionPool;
19
use TYPO3\CMS\Core\Utility\ArrayUtility;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22
/**
23
 * Backend layout for CMS
24
 * @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
25
 */
26
class BackendLayoutView implements \TYPO3\CMS\Core\SingletonInterface
27
{
28
    /**
29
     * @var BackendLayout\DataProviderCollection
30
     */
31
    protected $dataProviderCollection;
32
33
    /**
34
     * @var array
35
     */
36
    protected $selectedCombinedIdentifier = [];
37
38
    /**
39
     * @var array
40
     */
41
    protected $selectedBackendLayout = [];
42
43
    /**
44
     * Creates this object and initializes data providers.
45
     */
46
    public function __construct()
47
    {
48
        $this->initializeDataProviderCollection();
49
    }
50
51
    /**
52
     * Initializes data providers
53
     */
54
    protected function initializeDataProviderCollection()
55
    {
56
        /** @var BackendLayout\DataProviderCollection $dataProviderCollection */
57
        $dataProviderCollection = GeneralUtility::makeInstance(
58
            BackendLayout\DataProviderCollection::class
59
        );
60
61
        $dataProviderCollection->add(
62
            'default',
63
            \TYPO3\CMS\Backend\View\BackendLayout\DefaultDataProvider::class
64
        );
65
66
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider'])) {
67
            $dataProviders = (array)$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider'];
68
            foreach ($dataProviders as $identifier => $className) {
69
                $dataProviderCollection->add($identifier, $className);
70
            }
71
        }
72
73
        $this->setDataProviderCollection($dataProviderCollection);
74
    }
75
76
    /**
77
     * @param BackendLayout\DataProviderCollection $dataProviderCollection
78
     */
79
    public function setDataProviderCollection(BackendLayout\DataProviderCollection $dataProviderCollection)
80
    {
81
        $this->dataProviderCollection = $dataProviderCollection;
82
    }
83
84
    /**
85
     * @return BackendLayout\DataProviderCollection
86
     */
87
    public function getDataProviderCollection()
88
    {
89
        return $this->dataProviderCollection;
90
    }
91
92
    /**
93
     * Gets backend layout items to be shown in the forms engine.
94
     * This method is called as "itemsProcFunc" with the accordant context
95
     * for pages.backend_layout and pages.backend_layout_next_level.
96
     *
97
     * @param array $parameters
98
     */
99
    public function addBackendLayoutItems(array $parameters)
100
    {
101
        $pageId = $this->determinePageId($parameters['table'], $parameters['row']);
102
        $pageTsConfig = (array)BackendUtility::getPagesTSconfig($pageId);
0 ignored issues
show
Bug introduced by
It seems like $pageId can also be of type boolean; however, parameter $id of TYPO3\CMS\Backend\Utilit...ity::getPagesTSconfig() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

102
        $pageTsConfig = (array)BackendUtility::getPagesTSconfig(/** @scrutinizer ignore-type */ $pageId);
Loading history...
103
        $identifiersToBeExcluded = $this->getIdentifiersToBeExcluded($pageTsConfig);
104
105
        $dataProviderContext = $this->createDataProviderContext()
106
            ->setPageId($pageId)
0 ignored issues
show
Bug introduced by
It seems like $pageId can also be of type boolean; however, parameter $pageId of TYPO3\CMS\Backend\View\B...derContext::setPageId() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

106
            ->setPageId(/** @scrutinizer ignore-type */ $pageId)
Loading history...
107
            ->setData($parameters['row'])
108
            ->setTableName($parameters['table'])
109
            ->setFieldName($parameters['field'])
110
            ->setPageTsConfig($pageTsConfig);
111
112
        $backendLayoutCollections = $this->getDataProviderCollection()->getBackendLayoutCollections($dataProviderContext);
113
        foreach ($backendLayoutCollections as $backendLayoutCollection) {
114
            $combinedIdentifierPrefix = '';
115
            if ($backendLayoutCollection->getIdentifier() !== 'default') {
116
                $combinedIdentifierPrefix = $backendLayoutCollection->getIdentifier() . '__';
117
            }
118
119
            foreach ($backendLayoutCollection->getAll() as $backendLayout) {
120
                $combinedIdentifier = $combinedIdentifierPrefix . $backendLayout->getIdentifier();
121
122
                if (in_array($combinedIdentifier, $identifiersToBeExcluded, true)) {
123
                    continue;
124
                }
125
126
                $parameters['items'][] = [
127
                    $this->getLanguageService()->sL($backendLayout->getTitle()),
128
                    $combinedIdentifier,
129
                    $backendLayout->getIconPath(),
130
                ];
131
            }
132
        }
133
    }
134
135
    /**
136
     * Determines the page id for a given record of a database table.
137
     *
138
     * @param string $tableName
139
     * @param array $data
140
     * @return int|bool Returns page id or false on error
141
     */
142
    protected function determinePageId($tableName, array $data)
143
    {
144
        if (strpos($data['uid'], 'NEW') === 0) {
145
            // negative uid_pid values of content elements indicate that the element
146
            // has been inserted after an existing element so there is no pid to get
147
            // the backendLayout for and we have to get that first
148
            if ($data['pid'] < 0) {
149
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
150
                    ->getQueryBuilderForTable($tableName);
151
                $queryBuilder->getRestrictions()
152
                    ->removeAll();
153
                $pageId = $queryBuilder
154
                    ->select('pid')
155
                    ->from($tableName)
156
                    ->where(
157
                        $queryBuilder->expr()->eq(
158
                            'uid',
159
                            $queryBuilder->createNamedParameter(abs($data['pid']), \PDO::PARAM_INT)
160
                        )
161
                    )
162
                    ->execute()
163
                    ->fetchColumn();
164
            } else {
165
                $pageId = $data['pid'];
166
            }
167
        } elseif ($tableName === 'pages') {
168
            $pageId = $data['uid'];
169
        } else {
170
            $pageId = $data['pid'];
171
        }
172
173
        return $pageId;
174
    }
175
176
    /**
177
     * Returns the backend layout which should be used for this page.
178
     *
179
     * @param int $pageId
180
     * @return bool|string Identifier of the backend layout to be used, or FALSE if none
181
     */
182
    public function getSelectedCombinedIdentifier($pageId)
183
    {
184
        if (!isset($this->selectedCombinedIdentifier[$pageId])) {
185
            $page = $this->getPage($pageId);
186
            $this->selectedCombinedIdentifier[$pageId] = (string)$page['backend_layout'];
187
188
            if ($this->selectedCombinedIdentifier[$pageId] === '-1') {
189
                // If it is set to "none" - don't use any
190
                $this->selectedCombinedIdentifier[$pageId] = false;
191
            } elseif ($this->selectedCombinedIdentifier[$pageId] === '' || $this->selectedCombinedIdentifier[$pageId] === '0') {
192
                // If it not set check the root-line for a layout on next level and use this
193
                // (root-line starts with current page and has page "0" at the end)
194
                $rootLine = $this->getRootLine($pageId);
195
                // Remove first and last element (current and root page)
196
                array_shift($rootLine);
197
                array_pop($rootLine);
198
                foreach ($rootLine as $rootLinePage) {
199
                    $this->selectedCombinedIdentifier[$pageId] = (string)$rootLinePage['backend_layout_next_level'];
200
                    if ($this->selectedCombinedIdentifier[$pageId] === '-1') {
201
                        // If layout for "next level" is set to "none" - don't use any and stop searching
202
                        $this->selectedCombinedIdentifier[$pageId] = false;
203
                        break;
204
                    }
205
                    if ($this->selectedCombinedIdentifier[$pageId] !== '' && $this->selectedCombinedIdentifier[$pageId] !== '0') {
206
                        // Stop searching if a layout for "next level" is set
207
                        break;
208
                    }
209
                }
210
            }
211
        }
212
        // If it is set to a positive value use this
213
        return $this->selectedCombinedIdentifier[$pageId];
214
    }
215
216
    /**
217
     * Gets backend layout identifiers to be excluded
218
     *
219
     * @param array $pageTSconfig
220
     * @return array
221
     */
222
    protected function getIdentifiersToBeExcluded(array $pageTSconfig)
223
    {
224
        $identifiersToBeExcluded = [];
225
226
        if (ArrayUtility::isValidPath($pageTSconfig, 'options./backendLayout./exclude')) {
227
            $identifiersToBeExcluded = GeneralUtility::trimExplode(
228
                ',',
229
                ArrayUtility::getValueByPath($pageTSconfig, 'options./backendLayout./exclude'),
230
                true
231
            );
232
        }
233
234
        return $identifiersToBeExcluded;
235
    }
236
237
    /**
238
     * Gets colPos items to be shown in the forms engine.
239
     * This method is called as "itemsProcFunc" with the accordant context
240
     * for tt_content.colPos.
241
     *
242
     * @param array $parameters
243
     */
244
    public function colPosListItemProcFunc(array $parameters)
245
    {
246
        $pageId = $this->determinePageId($parameters['table'], $parameters['row']);
247
248
        if ($pageId !== false) {
249
            $parameters['items'] = $this->addColPosListLayoutItems($pageId, $parameters['items']);
0 ignored issues
show
Bug introduced by
It seems like $pageId can also be of type true; however, parameter $pageId of TYPO3\CMS\Backend\View\B...ColPosListLayoutItems() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

249
            $parameters['items'] = $this->addColPosListLayoutItems(/** @scrutinizer ignore-type */ $pageId, $parameters['items']);
Loading history...
250
        }
251
    }
252
253
    /**
254
     * Adds items to a colpos list
255
     *
256
     * @param int $pageId
257
     * @param array $items
258
     * @return array
259
     */
260
    protected function addColPosListLayoutItems($pageId, $items)
261
    {
262
        $layout = $this->getSelectedBackendLayout($pageId);
263
        if ($layout && $layout['__items']) {
264
            $items = $layout['__items'];
265
        }
266
        return $items;
267
    }
268
269
    /**
270
     * Gets the list of available columns for a given page id
271
     *
272
     * @param int $id
273
     * @return array $tcaItems
274
     */
275
    public function getColPosListItemsParsed($id)
276
    {
277
        $tsConfig = BackendUtility::getPagesTSconfig($id)['TCEFORM.']['tt_content.']['colPos.'] ?? [];
278
        $tcaConfig = $GLOBALS['TCA']['tt_content']['columns']['colPos']['config'];
279
        $tcaItems = $tcaConfig['items'];
280
        $tcaItems = $this->addItems($tcaItems, $tsConfig['addItems.']);
281
        if (isset($tcaConfig['itemsProcFunc']) && $tcaConfig['itemsProcFunc']) {
282
            $tcaItems = $this->addColPosListLayoutItems($id, $tcaItems);
283
        }
284
        if (!empty($tsConfig['removeItems'])) {
285
            foreach (GeneralUtility::trimExplode(',', $tsConfig['removeItems'], true) as $removeId) {
286
                foreach ($tcaItems as $key => $item) {
287
                    if ($item[1] == $removeId) {
288
                        unset($tcaItems[$key]);
289
                    }
290
                }
291
            }
292
        }
293
        return $tcaItems;
294
    }
295
296
    /**
297
     * Merges items into an item-array, optionally with an icon
298
     * example:
299
     * TCEFORM.pages.doktype.addItems.13 = My Label
300
     * TCEFORM.pages.doktype.addItems.13.icon = EXT:t3skin/icons/gfx/i/pages.gif
301
     *
302
     * @param array $items The existing item array
303
     * @param array $iArray An array of items to add. NOTICE: The keys are mapped to values, and the values and mapped to be labels. No possibility of adding an icon.
304
     * @return array The updated $item array
305
     * @internal
306
     */
307
    protected function addItems($items, $iArray)
308
    {
309
        $languageService = static::getLanguageService();
0 ignored issues
show
Bug Best Practice introduced by
The method TYPO3\CMS\Backend\View\B...w::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

309
        /** @scrutinizer ignore-call */ 
310
        $languageService = static::getLanguageService();
Loading history...
310
        if (is_array($iArray)) {
0 ignored issues
show
introduced by
The condition is_array($iArray) is always true.
Loading history...
311
            foreach ($iArray as $value => $label) {
312
                // if the label is an array (that means it is a subelement
313
                // like "34.icon = mylabel.png", skip it (see its usage below)
314
                if (is_array($label)) {
315
                    continue;
316
                }
317
                // check if the value "34 = mylabel" also has a "34.icon = myimage.png"
318
                if (isset($iArray[$value . '.']) && $iArray[$value . '.']['icon']) {
319
                    $icon = $iArray[$value . '.']['icon'];
320
                } else {
321
                    $icon = '';
322
                }
323
                $items[] = [$languageService->sL($label), $value, $icon];
324
            }
325
        }
326
        return $items;
327
    }
328
329
    /**
330
     * Gets the selected backend layout
331
     *
332
     * @param int $pageId
333
     * @return array|null $backendLayout
334
     */
335
    public function getSelectedBackendLayout($pageId)
336
    {
337
        if (isset($this->selectedBackendLayout[$pageId])) {
338
            return $this->selectedBackendLayout[$pageId];
339
        }
340
        $backendLayoutData = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $backendLayoutData is dead and can be removed.
Loading history...
341
342
        $selectedCombinedIdentifier = $this->getSelectedCombinedIdentifier($pageId);
343
        // If no backend layout is selected, use default
344
        if (empty($selectedCombinedIdentifier)) {
345
            $selectedCombinedIdentifier = 'default';
346
        }
347
348
        $backendLayout = $this->getDataProviderCollection()->getBackendLayout($selectedCombinedIdentifier, $pageId);
349
        // If backend layout is not found available anymore, use default
350
        if ($backendLayout === null) {
351
            $selectedCombinedIdentifier = 'default';
352
            $backendLayout = $this->getDataProviderCollection()->getBackendLayout($selectedCombinedIdentifier, $pageId);
353
        }
354
355
        return $backendLayout->getConfigurationArray();
356
    }
357
358
    /**
359
     * Get default columns layout
360
     *
361
     * @return string Default four column layout
362
     * @static
363
     */
364
    public static function getDefaultColumnLayout()
365
    {
366
        return '
367
		backend_layout {
368
			colCount = 1
369
			rowCount = 1
370
			rows {
371
				1 {
372
					columns {
373
						1 {
374
							name = LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:colPos.I.1
375
							colPos = 0
376
						}
377
					}
378
				}
379
			}
380
		}
381
		';
382
    }
383
384
    /**
385
     * Gets a page record.
386
     *
387
     * @param int $pageId
388
     * @return array|null
389
     */
390
    protected function getPage($pageId)
391
    {
392
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
393
            ->getQueryBuilderForTable('pages');
394
        $queryBuilder->getRestrictions()
395
            ->removeAll();
396
        $page = $queryBuilder
397
            ->select('uid', 'pid', 'backend_layout')
398
            ->from('pages')
399
            ->where(
400
                $queryBuilder->expr()->eq(
401
                    'uid',
402
                    $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
403
                )
404
            )
405
            ->execute()
406
            ->fetch();
407
        BackendUtility::workspaceOL('pages', $page);
408
409
        return $page;
410
    }
411
412
    /**
413
     * Gets the page root-line.
414
     *
415
     * @param int $pageId
416
     * @return array
417
     */
418
    protected function getRootLine($pageId)
419
    {
420
        return BackendUtility::BEgetRootLine($pageId, '', true);
421
    }
422
423
    /**
424
     * @return BackendLayout\DataProviderContext
425
     */
426
    protected function createDataProviderContext()
427
    {
428
        return GeneralUtility::makeInstance(BackendLayout\DataProviderContext::class);
429
    }
430
431
    /**
432
     * @return \TYPO3\CMS\Core\Localization\LanguageService
433
     */
434
    protected function getLanguageService()
435
    {
436
        return $GLOBALS['LANG'];
437
    }
438
}
439