[CLEANUP] The correct case must be used for standard PHP types in phpdoc
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / View / BackendLayoutView.php
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 */
25 class BackendLayoutView implements \TYPO3\CMS\Core\SingletonInterface
26 {
27 /**
28 * @var BackendLayout\DataProviderCollection
29 */
30 protected $dataProviderCollection;
31
32 /**
33 * @var array
34 */
35 protected $selectedCombinedIdentifier = [];
36
37 /**
38 * @var array
39 */
40 protected $selectedBackendLayout = [];
41
42 /**
43 * Creates this object and initializes data providers.
44 */
45 public function __construct()
46 {
47 $this->initializeDataProviderCollection();
48 }
49
50 /**
51 * Initializes data providers
52 */
53 protected function initializeDataProviderCollection()
54 {
55 /** @var $dataProviderCollection BackendLayout\DataProviderCollection */
56 $dataProviderCollection = GeneralUtility::makeInstance(
57 BackendLayout\DataProviderCollection::class
58 );
59
60 $dataProviderCollection->add(
61 'default',
62 \TYPO3\CMS\Backend\View\BackendLayout\DefaultDataProvider::class
63 );
64
65 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider'])) {
66 $dataProviders = (array)$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider'];
67 foreach ($dataProviders as $identifier => $className) {
68 $dataProviderCollection->add($identifier, $className);
69 }
70 }
71
72 $this->setDataProviderCollection($dataProviderCollection);
73 }
74
75 /**
76 * @param BackendLayout\DataProviderCollection $dataProviderCollection
77 */
78 public function setDataProviderCollection(BackendLayout\DataProviderCollection $dataProviderCollection)
79 {
80 $this->dataProviderCollection = $dataProviderCollection;
81 }
82
83 /**
84 * @return BackendLayout\DataProviderCollection
85 */
86 public function getDataProviderCollection()
87 {
88 return $this->dataProviderCollection;
89 }
90
91 /**
92 * Gets backend layout items to be shown in the forms engine.
93 * This method is called as "itemsProcFunc" with the accordant context
94 * for pages.backend_layout and pages.backend_layout_next_level.
95 *
96 * @param array $parameters
97 */
98 public function addBackendLayoutItems(array $parameters)
99 {
100 $pageId = $this->determinePageId($parameters['table'], $parameters['row']);
101 $pageTsConfig = (array)BackendUtility::getPagesTSconfig($pageId);
102 $identifiersToBeExcluded = $this->getIdentifiersToBeExcluded($pageTsConfig);
103
104 $dataProviderContext = $this->createDataProviderContext()
105 ->setPageId($pageId)
106 ->setData($parameters['row'])
107 ->setTableName($parameters['table'])
108 ->setFieldName($parameters['field'])
109 ->setPageTsConfig($pageTsConfig);
110
111 $backendLayoutCollections = $this->getDataProviderCollection()->getBackendLayoutCollections($dataProviderContext);
112 foreach ($backendLayoutCollections as $backendLayoutCollection) {
113 $combinedIdentifierPrefix = '';
114 if ($backendLayoutCollection->getIdentifier() !== 'default') {
115 $combinedIdentifierPrefix = $backendLayoutCollection->getIdentifier() . '__';
116 }
117
118 foreach ($backendLayoutCollection->getAll() as $backendLayout) {
119 $combinedIdentifier = $combinedIdentifierPrefix . $backendLayout->getIdentifier();
120
121 if (in_array($combinedIdentifier, $identifiersToBeExcluded, true)) {
122 continue;
123 }
124
125 $parameters['items'][] = [
126 $this->getLanguageService()->sL($backendLayout->getTitle()),
127 $combinedIdentifier,
128 $backendLayout->getIconPath(),
129 ];
130 }
131 }
132 }
133
134 /**
135 * Determines the page id for a given record of a database table.
136 *
137 * @param string $tableName
138 * @param array $data
139 * @return int|bool Returns page id or false on error
140 */
141 protected function determinePageId($tableName, array $data)
142 {
143 if (strpos($data['uid'], 'NEW') === 0) {
144 // negative uid_pid values of content elements indicate that the element
145 // has been inserted after an existing element so there is no pid to get
146 // the backendLayout for and we have to get that first
147 if ($data['pid'] < 0) {
148 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
149 ->getQueryBuilderForTable($tableName);
150 $queryBuilder->getRestrictions()
151 ->removeAll();
152 $pageId = $queryBuilder
153 ->select('pid')
154 ->from($tableName)
155 ->where(
156 $queryBuilder->expr()->eq(
157 'uid',
158 $queryBuilder->createNamedParameter(abs($data['pid']), \PDO::PARAM_INT)
159 )
160 )
161 ->execute()
162 ->fetchColumn();
163 } else {
164 $pageId = $data['pid'];
165 }
166 } elseif ($tableName === 'pages') {
167 $pageId = $data['uid'];
168 } else {
169 $pageId = $data['pid'];
170 }
171
172 return $pageId;
173 }
174
175 /**
176 * Returns the backend layout which should be used for this page.
177 *
178 * @param int $pageId
179 * @return bool|string Identifier of the backend layout to be used, or FALSE if none
180 */
181 public function getSelectedCombinedIdentifier($pageId)
182 {
183 if (!isset($this->selectedCombinedIdentifier[$pageId])) {
184 $page = $this->getPage($pageId);
185 $this->selectedCombinedIdentifier[$pageId] = (string)$page['backend_layout'];
186
187 if ($this->selectedCombinedIdentifier[$pageId] === '-1') {
188 // If it is set to "none" - don't use any
189 $this->selectedCombinedIdentifier[$pageId] = false;
190 } elseif ($this->selectedCombinedIdentifier[$pageId] === '' || $this->selectedCombinedIdentifier[$pageId] === '0') {
191 // If it not set check the root-line for a layout on next level and use this
192 // (root-line starts with current page and has page "0" at the end)
193 $rootLine = $this->getRootLine($pageId);
194 // Remove first and last element (current and root page)
195 array_shift($rootLine);
196 array_pop($rootLine);
197 foreach ($rootLine as $rootLinePage) {
198 $this->selectedCombinedIdentifier[$pageId] = (string)$rootLinePage['backend_layout_next_level'];
199 if ($this->selectedCombinedIdentifier[$pageId] === '-1') {
200 // If layout for "next level" is set to "none" - don't use any and stop searching
201 $this->selectedCombinedIdentifier[$pageId] = false;
202 break;
203 }
204 if ($this->selectedCombinedIdentifier[$pageId] !== '' && $this->selectedCombinedIdentifier[$pageId] !== '0') {
205 // Stop searching if a layout for "next level" is set
206 break;
207 }
208 }
209 }
210 }
211 // If it is set to a positive value use this
212 return $this->selectedCombinedIdentifier[$pageId];
213 }
214
215 /**
216 * Gets backend layout identifiers to be excluded
217 *
218 * @param array $pageTSconfig
219 * @return array
220 */
221 protected function getIdentifiersToBeExcluded(array $pageTSconfig)
222 {
223 $identifiersToBeExcluded = [];
224
225 if (ArrayUtility::isValidPath($pageTSconfig, 'options./backendLayout./exclude')) {
226 $identifiersToBeExcluded = GeneralUtility::trimExplode(
227 ',',
228 ArrayUtility::getValueByPath($pageTSconfig, 'options./backendLayout./exclude'),
229 true
230 );
231 }
232
233 return $identifiersToBeExcluded;
234 }
235
236 /**
237 * Gets colPos items to be shown in the forms engine.
238 * This method is called as "itemsProcFunc" with the accordant context
239 * for tt_content.colPos.
240 *
241 * @param array $parameters
242 */
243 public function colPosListItemProcFunc(array $parameters)
244 {
245 $pageId = $this->determinePageId($parameters['table'], $parameters['row']);
246
247 if ($pageId !== false) {
248 $parameters['items'] = $this->addColPosListLayoutItems($pageId, $parameters['items']);
249 }
250 }
251
252 /**
253 * Adds items to a colpos list
254 *
255 * @param int $pageId
256 * @param array $items
257 * @return array
258 */
259 protected function addColPosListLayoutItems($pageId, $items)
260 {
261 $layout = $this->getSelectedBackendLayout($pageId);
262 if ($layout && $layout['__items']) {
263 $items = $layout['__items'];
264 }
265 return $items;
266 }
267
268 /**
269 * Gets the list of available columns for a given page id
270 *
271 * @param int $id
272 * @return array $tcaItems
273 */
274 public function getColPosListItemsParsed($id)
275 {
276 $tsConfig = BackendUtility::getModTSconfig($id, 'TCEFORM.tt_content.colPos');
277 $tcaConfig = $GLOBALS['TCA']['tt_content']['columns']['colPos']['config'];
278 $tcaItems = $tcaConfig['items'];
279 $tcaItems = $this->addItems($tcaItems, $tsConfig['properties']['addItems.']);
280 if (isset($tcaConfig['itemsProcFunc']) && $tcaConfig['itemsProcFunc']) {
281 $tcaItems = $this->addColPosListLayoutItems($id, $tcaItems);
282 }
283 foreach (GeneralUtility::trimExplode(',', $tsConfig['properties']['removeItems'], true) as $removeId) {
284 foreach ($tcaItems as $key => $item) {
285 if ($item[1] == $removeId) {
286 unset($tcaItems[$key]);
287 }
288 }
289 }
290 return $tcaItems;
291 }
292
293 /**
294 * Merges items into an item-array, optionally with an icon
295 * example:
296 * TCEFORM.pages.doktype.addItems.13 = My Label
297 * TCEFORM.pages.doktype.addItems.13.icon = EXT:t3skin/icons/gfx/i/pages.gif
298 *
299 * @param array $items The existing item array
300 * @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.
301 * @return array The updated $item array
302 * @internal
303 */
304 protected function addItems($items, $iArray)
305 {
306 $languageService = static::getLanguageService();
307 if (is_array($iArray)) {
308 foreach ($iArray as $value => $label) {
309 // if the label is an array (that means it is a subelement
310 // like "34.icon = mylabel.png", skip it (see its usage below)
311 if (is_array($label)) {
312 continue;
313 }
314 // check if the value "34 = mylabel" also has a "34.icon = myimage.png"
315 if (isset($iArray[$value . '.']) && $iArray[$value . '.']['icon']) {
316 $icon = $iArray[$value . '.']['icon'];
317 } else {
318 $icon = '';
319 }
320 $items[] = [$languageService->sL($label), $value, $icon];
321 }
322 }
323 return $items;
324 }
325
326 /**
327 * Gets the selected backend layout
328 *
329 * @param int $pageId
330 * @return array|null $backendLayout
331 */
332 public function getSelectedBackendLayout($pageId)
333 {
334 if (isset($this->selectedBackendLayout[$pageId])) {
335 return $this->selectedBackendLayout[$pageId];
336 }
337 $backendLayoutData = null;
338
339 $selectedCombinedIdentifier = $this->getSelectedCombinedIdentifier($pageId);
340 // If no backend layout is selected, use default
341 if (empty($selectedCombinedIdentifier)) {
342 $selectedCombinedIdentifier = 'default';
343 }
344
345 $backendLayout = $this->getDataProviderCollection()->getBackendLayout($selectedCombinedIdentifier, $pageId);
346 // If backend layout is not found available anymore, use default
347 if (is_null($backendLayout)) {
348 $selectedCombinedIdentifier = 'default';
349 $backendLayout = $this->getDataProviderCollection()->getBackendLayout($selectedCombinedIdentifier, $pageId);
350 }
351
352 if (!empty($backendLayout)) {
353 /** @var $parser \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser */
354 $parser = GeneralUtility::makeInstance(\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::class);
355 /** @var \TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher $conditionMatcher */
356 $conditionMatcher = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher::class);
357 $parser->parse($parser->checkIncludeLines($backendLayout->getConfiguration()), $conditionMatcher);
358
359 $backendLayoutData = [];
360 $backendLayoutData['config'] = $backendLayout->getConfiguration();
361 $backendLayoutData['__config'] = $parser->setup;
362 $backendLayoutData['__items'] = [];
363 $backendLayoutData['__colPosList'] = [];
364
365 // create items and colPosList
366 if (!empty($backendLayoutData['__config']['backend_layout.']['rows.'])) {
367 foreach ($backendLayoutData['__config']['backend_layout.']['rows.'] as $row) {
368 if (!empty($row['columns.'])) {
369 foreach ($row['columns.'] as $column) {
370 if (!isset($column['colPos'])) {
371 continue;
372 }
373 $backendLayoutData['__items'][] = [
374 $this->getColumnName($column),
375 $column['colPos'],
376 null
377 ];
378 $backendLayoutData['__colPosList'][] = $column['colPos'];
379 }
380 }
381 }
382 }
383
384 $this->selectedBackendLayout[$pageId] = $backendLayoutData;
385 }
386
387 return $backendLayoutData;
388 }
389
390 /**
391 * Get default columns layout
392 *
393 * @return string Default four column layout
394 * @static
395 */
396 public static function getDefaultColumnLayout()
397 {
398 return '
399 backend_layout {
400 colCount = 4
401 rowCount = 1
402 rows {
403 1 {
404 columns {
405 1 {
406 name = LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:colPos.I.0
407 colPos = 1
408 }
409 2 {
410 name = LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:colPos.I.1
411 colPos = 0
412 }
413 3 {
414 name = LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:colPos.I.2
415 colPos = 2
416 }
417 4 {
418 name = LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:colPos.I.3
419 colPos = 3
420 }
421 }
422 }
423 }
424 }
425 ';
426 }
427
428 /**
429 * Gets a page record.
430 *
431 * @param int $pageId
432 * @return null|array
433 */
434 protected function getPage($pageId)
435 {
436 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
437 ->getQueryBuilderForTable('pages');
438 $queryBuilder->getRestrictions()
439 ->removeAll();
440 $page = $queryBuilder
441 ->select('uid', 'pid', 'backend_layout')
442 ->from('pages')
443 ->where(
444 $queryBuilder->expr()->eq(
445 'uid',
446 $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
447 )
448 )
449 ->execute()
450 ->fetch();
451 BackendUtility::workspaceOL('pages', $page);
452
453 return $page;
454 }
455
456 /**
457 * Gets the page root-line.
458 *
459 * @param int $pageId
460 * @return array
461 */
462 protected function getRootLine($pageId)
463 {
464 return BackendUtility::BEgetRootLine($pageId, '', true);
465 }
466
467 /**
468 * @return BackendLayout\DataProviderContext
469 */
470 protected function createDataProviderContext()
471 {
472 return GeneralUtility::makeInstance(BackendLayout\DataProviderContext::class);
473 }
474
475 /**
476 * @return \TYPO3\CMS\Lang\LanguageService
477 */
478 protected function getLanguageService()
479 {
480 return $GLOBALS['LANG'];
481 }
482
483 /**
484 * Get column name from colPos item structure
485 *
486 * @param array $column
487 * @return string
488 */
489 protected function getColumnName($column)
490 {
491 $columnName = $column['name'];
492
493 if (GeneralUtility::isFirstPartOfStr($columnName, 'LLL:')) {
494 $columnName = $this->getLanguageService()->sL($columnName);
495 }
496
497 return $columnName;
498 }
499 }