[TASK] Update php-cs-fixer to 2.5.0
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Tree / Pagetree / DataProvider.php
1 <?php
2 namespace TYPO3\CMS\Backend\Tree\Pagetree;
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\Connection;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
21 use TYPO3\CMS\Core\Database\Query\QueryHelper;
22 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
23 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26 /**
27 * Page tree data provider.
28 */
29 class DataProvider extends \TYPO3\CMS\Backend\Tree\AbstractTreeDataProvider
30 {
31 /**
32 * Node limit that should be loaded for this request per mount
33 *
34 * @var int
35 */
36 protected $nodeLimit = 0;
37
38 /**
39 * Current amount of nodes
40 *
41 * @var int
42 */
43 protected $nodeCounter = 0;
44
45 /**
46 * TRUE to show the path of each mountpoint in the tree
47 *
48 * @var bool
49 */
50 protected $showRootlineAboveMounts = false;
51
52 /**
53 * Hidden Records
54 *
55 * @var array<string>
56 */
57 protected $hiddenRecords = [];
58
59 /**
60 * Process collection hook objects
61 *
62 * @var array<\TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface>
63 */
64 protected $processCollectionHookObjects = [];
65
66 /**
67 * Constructor
68 *
69 * @param int $nodeLimit (optional)
70 */
71 public function __construct($nodeLimit = null)
72 {
73 if ($nodeLimit === null) {
74 $nodeLimit = $GLOBALS['TYPO3_CONF_VARS']['BE']['pageTree']['preloadLimit'];
75 }
76 $this->nodeLimit = abs((int)$nodeLimit);
77
78 $this->showRootlineAboveMounts = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.showPathAboveMounts');
79
80 $this->hiddenRecords = GeneralUtility::trimExplode(',', $GLOBALS['BE_USER']->getTSConfigVal('options.hideRecords.pages'));
81 $hookElements = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/tree/pagetree/class.t3lib_tree_pagetree_dataprovider.php']['postProcessCollections'];
82 if (is_array($hookElements)) {
83 foreach ($hookElements as $className) {
84 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
85 $hookObject = GeneralUtility::makeInstance($className);
86 if ($hookObject instanceof \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface) {
87 $this->processCollectionHookObjects[] = $hookObject;
88 }
89 }
90 }
91 }
92
93 /**
94 * Returns the root node.
95 *
96 * @return \TYPO3\CMS\Backend\Tree\TreeNode the root node
97 */
98 public function getRoot()
99 {
100 /** @var $node \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode */
101 $node = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode::class);
102 $node->setId('root');
103 $node->setExpanded(true);
104 return $node;
105 }
106
107 /**
108 * Fetches the sub-nodes of the given node
109 *
110 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
111 * @param int $mountPoint
112 * @param int $level internally used variable as a recursion limiter
113 * @return \TYPO3\CMS\Backend\Tree\TreeNodeCollection
114 */
115 public function getNodes(\TYPO3\CMS\Backend\Tree\TreeNode $node, $mountPoint = 0, $level = 0)
116 {
117 /** @var $nodeCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
118 $nodeCollection = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection::class);
119 if ($level >= 99 || $node->getStopPageTree()) {
120 return $nodeCollection;
121 }
122 $isVirtualRootNode = false;
123 $subpages = $this->getSubpages($node->getId());
124 // check if fetching subpages the "root"-page
125 // and in case of a virtual root return the mountpoints as virtual "subpages"
126 if ((int)$node->getId() === 0) {
127 // check no temporary mountpoint is used
128 if (!(int)$GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint']) {
129 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
130 $mountPoints = array_unique($mountPoints);
131 if (!in_array(0, $mountPoints)) {
132 // using a virtual root node
133 // so then return the mount points here as "subpages" of the first node
134 $isVirtualRootNode = true;
135 $subpages = [];
136 foreach ($mountPoints as $webMountPoint) {
137 $subpages[] = [
138 'uid' => $webMountPoint,
139 'isMountPoint' => true
140 ];
141 }
142 }
143 }
144 }
145 if (is_array($subpages) && !empty($subpages)) {
146 $lastRootline = [];
147 foreach ($subpages as $subpage) {
148 if (in_array($subpage['uid'], $this->hiddenRecords)) {
149 continue;
150 }
151 // must be calculated above getRecordWithWorkspaceOverlay,
152 // because the information is lost otherwise
153 $isMountPoint = $subpage['isMountPoint'] === true;
154 if ($isVirtualRootNode) {
155 $mountPoint = (int)$subpage['uid'];
156 }
157 $subpage = $this->getRecordWithWorkspaceOverlay($subpage['uid'], true);
158 if (!$subpage) {
159 continue;
160 }
161 $subNode = Commands::getNewNode($subpage, $mountPoint);
162 $subNode->setIsMountPoint($isMountPoint);
163 if ($isMountPoint && $this->showRootlineAboveMounts) {
164 if ($subpage['pid'] > 0) {
165 $rootline = Commands::getMountPointPath($subpage['pid']);
166 } else {
167 $rootline = Commands::getMountPointPath($subpage['uid']);
168 }
169 if ($lastRootline !== $rootline) {
170 $subNode->setReadableRootline($rootline);
171 }
172 $lastRootline = $rootline;
173 }
174 if ($this->nodeCounter < $this->nodeLimit) {
175 $childNodes = $this->getNodes($subNode, $mountPoint, $level + 1);
176 $subNode->setChildNodes($childNodes);
177 $this->nodeCounter += $childNodes->count();
178 } else {
179 $subNode->setLeaf(!$this->hasNodeSubPages($subNode->getId()));
180 }
181 if (!$GLOBALS['BE_USER']->isAdmin() && (int)$subpage['editlock'] === 1) {
182 $subNode->setLabelIsEditable(false);
183 }
184 $nodeCollection->append($subNode);
185 }
186 }
187 foreach ($this->processCollectionHookObjects as $hookObject) {
188 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
189 $hookObject->postProcessGetNodes($node, $mountPoint, $level, $nodeCollection);
190 }
191 return $nodeCollection;
192 }
193
194 /**
195 * Wrapper method for \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordWSOL
196 *
197 * @param int $uid The page id
198 * @param bool $unsetMovePointers Whether to unset move pointers
199 * @return array
200 */
201 protected function getRecordWithWorkspaceOverlay($uid, $unsetMovePointers = false)
202 {
203 return BackendUtility::getRecordWSOL('pages', $uid, '*', '', true, $unsetMovePointers);
204 }
205
206 /**
207 * Returns a node collection of filtered nodes
208 *
209 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
210 * @param string $searchFilter
211 * @param int $mountPoint
212 * @return \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection the filtered nodes
213 */
214 public function getFilteredNodes(\TYPO3\CMS\Backend\Tree\TreeNode $node, $searchFilter, $mountPoint = 0)
215 {
216 /** @var $nodeCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
217 $nodeCollection = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection::class);
218 $records = $this->getSubpages(-1, $searchFilter);
219 if (!is_array($records) || empty($records)) {
220 return $nodeCollection;
221 }
222 if (count($records) > 500) {
223 return $nodeCollection;
224 }
225 // check no temporary mountpoint is used
226 $mountPoints = (int)$GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
227 if (!$mountPoints) {
228 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
229 $mountPoints = array_unique($mountPoints);
230 } else {
231 $mountPoints = [$mountPoints];
232 }
233 $isNumericSearchFilter = is_numeric($searchFilter) && $searchFilter > 0;
234 $searchFilterQuoted = preg_quote($searchFilter, '/');
235 $nodeId = (int)$node->getId();
236 $processedRecordIds = [];
237 foreach ($records as $record) {
238 if ((int)$record['t3ver_wsid'] !== (int)$GLOBALS['BE_USER']->workspace && (int)$record['t3ver_wsid'] !== 0) {
239 continue;
240 }
241 $liveVersion = BackendUtility::getLiveVersionOfRecord('pages', $record['uid'], 'uid');
242 if ($liveVersion !== null) {
243 $record = $liveVersion;
244 }
245
246 $record = Commands::getNodeRecord($record['uid'], false);
247 if ((int)$record['pid'] === -1
248 || in_array($record['uid'], $this->hiddenRecords)
249 || in_array($record['uid'], $processedRecordIds)
250 ) {
251 continue;
252 }
253 $processedRecordIds[] = $record['uid'];
254
255 $rootline = BackendUtility::BEgetRootLine($record['uid'], '', $GLOBALS['BE_USER']->workspace != 0);
256 $rootline = array_reverse($rootline);
257 if (!in_array(0, $mountPoints, true)) {
258 $isInsideMountPoints = false;
259 foreach ($rootline as $rootlineElement) {
260 if (in_array((int)$rootlineElement['uid'], $mountPoints, true)) {
261 $isInsideMountPoints = true;
262 break;
263 }
264 }
265 if (!$isInsideMountPoints) {
266 continue;
267 }
268 }
269 $reference = $nodeCollection;
270 $inFilteredRootline = false;
271 $amountOfRootlineElements = count($rootline);
272 for ($i = 0; $i < $amountOfRootlineElements; ++$i) {
273 $rootlineElement = $rootline[$i];
274 $rootlineElement['uid'] = (int)$rootlineElement['uid'];
275 $isInWebMount = (int)$GLOBALS['BE_USER']->isInWebMount($rootlineElement['uid']);
276 if (!$isInWebMount
277 || ($rootlineElement['uid'] === (int)$mountPoints[0]
278 && $rootlineElement['uid'] !== $isInWebMount)
279 ) {
280 continue;
281 }
282 if ((int)$rootlineElement['pid'] === $nodeId
283 || $rootlineElement['uid'] === $nodeId
284 || ($rootlineElement['uid'] === $isInWebMount
285 && in_array($rootlineElement['uid'], $mountPoints, true))
286 ) {
287 $inFilteredRootline = true;
288 }
289 if (!$inFilteredRootline || $rootlineElement['uid'] === $mountPoint) {
290 continue;
291 }
292 $rootlineElement = Commands::getNodeRecord($rootlineElement['uid'], false);
293 $ident = (int)$rootlineElement['sorting'] . (int)$rootlineElement['uid'];
294 if ($reference && $reference->offsetExists($ident)) {
295 /** @var $refNode \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode */
296 $refNode = $reference->offsetGet($ident);
297 $refNode->setExpanded(true);
298 $refNode->setLeaf(false);
299 $reference = $refNode->getChildNodes();
300 if ($reference == null) {
301 $reference = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection::class);
302 $refNode->setChildNodes($reference);
303 }
304 } else {
305 $refNode = Commands::getNewNode($rootlineElement, $mountPoint);
306 $replacement = '<span class="typo3-pagetree-filteringTree-highlight">$1</span>';
307 if ($isNumericSearchFilter && (int)$rootlineElement['uid'] === (int)$searchFilter) {
308 $text = str_replace('$1', $refNode->getText(), $replacement);
309 } else {
310 $text = preg_replace('/(' . $searchFilterQuoted . ')/iu', $replacement, $refNode->getText());
311 }
312 $refNode->setText($text, $refNode->getTextSourceField(), $refNode->getPrefix(), $refNode->getSuffix());
313 /** @var $childCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
314 $childCollection = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection::class);
315 if ($i + 1 >= $amountOfRootlineElements) {
316 $childNodes = $this->getNodes($refNode, $mountPoint);
317 foreach ($childNodes as $childNode) {
318 /** @var $childNode \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode */
319 $childRecord = $childNode->getRecord();
320 $childIdent = (int)$childRecord['sorting'] . (int)$childRecord['uid'];
321 $childCollection->offsetSet($childIdent, $childNode);
322 }
323 $refNode->setChildNodes($childNodes);
324 }
325 $refNode->setChildNodes($childCollection);
326 $reference->offsetSet($ident, $refNode);
327 $reference->ksort();
328 $reference = $childCollection;
329 }
330 }
331 }
332 foreach ($this->processCollectionHookObjects as $hookObject) {
333 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
334 $hookObject->postProcessFilteredNodes($node, $searchFilter, $mountPoint, $nodeCollection);
335 }
336 return $nodeCollection;
337 }
338
339 /**
340 * Returns the page tree mounts for the current user
341 *
342 * Note: If you add the search filter parameter, the nodes will be filtered by this string.
343 *
344 * @param string $searchFilter
345 * @return \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection
346 */
347 public function getTreeMounts($searchFilter = '')
348 {
349 /** @var $nodeCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
350 $nodeCollection = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection::class);
351 $isTemporaryMountPoint = false;
352 $rootNodeIsVirtual = false;
353 $mountPoints = (int)$GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
354 if (!$mountPoints) {
355 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
356 $mountPoints = array_unique($mountPoints);
357 if (!in_array(0, $mountPoints)) {
358 $rootNodeIsVirtual = true;
359 // use a virtual root
360 // the real mountpoints will be fetched in getNodes() then
361 // since those will be the "subpages" of the virtual root
362 $mountPoints = [0];
363 }
364 } else {
365 $isTemporaryMountPoint = true;
366 $mountPoints = [$mountPoints];
367 }
368 if (empty($mountPoints)) {
369 return $nodeCollection;
370 }
371
372 foreach ($mountPoints as $mountPoint) {
373 if ($mountPoint === 0) {
374 $sitename = 'TYPO3';
375 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] !== '') {
376 $sitename = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
377 }
378 $record = [
379 'uid' => 0,
380 'title' => $sitename
381 ];
382 $subNode = Commands::getNewNode($record);
383 $subNode->setLabelIsEditable(false);
384 if ($rootNodeIsVirtual) {
385 $subNode->setType('virtual_root');
386 $subNode->setIsDropTarget(false);
387 } else {
388 $subNode->setType('pages_root');
389 $subNode->setIsDropTarget(true);
390 }
391 } else {
392 if (in_array($mountPoint, $this->hiddenRecords)) {
393 continue;
394 }
395 $record = $this->getRecordWithWorkspaceOverlay($mountPoint);
396 if (!$record) {
397 continue;
398 }
399 $subNode = Commands::getNewNode($record, $mountPoint);
400 if ($this->showRootlineAboveMounts && !$isTemporaryMountPoint) {
401 $rootline = Commands::getMountPointPath($record['uid']);
402 $subNode->setReadableRootline($rootline);
403 }
404 }
405 if (count($mountPoints) <= 1) {
406 $subNode->setExpanded(true);
407 $subNode->setCls('typo3-pagetree-node-notExpandable');
408 }
409 $subNode->setIsMountPoint(true);
410 $subNode->setDraggable(false);
411 if ($searchFilter === '') {
412 $childNodes = $this->getNodes($subNode, $mountPoint);
413 } else {
414 $childNodes = $this->getFilteredNodes($subNode, $searchFilter, $mountPoint);
415 $subNode->setExpanded(true);
416 }
417 $subNode->setChildNodes($childNodes);
418 $nodeCollection->append($subNode);
419 }
420 foreach ($this->processCollectionHookObjects as $hookObject) {
421 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
422 $hookObject->postProcessGetTreeMounts($searchFilter, $nodeCollection);
423 }
424 return $nodeCollection;
425 }
426
427 /**
428 * Sets the Doctrine where clause for fetching pages
429 *
430 * @param QueryBuilder $queryBuilder
431 * @param int $id
432 * @param string $searchFilter
433 * @return QueryBuilder
434 */
435 protected function setWhereClause(QueryBuilder $queryBuilder, $id, $searchFilter = ''): QueryBuilder
436 {
437 $expressionBuilder = $queryBuilder->expr();
438 $queryBuilder->where(
439 QueryHelper::stripLogicalOperatorPrefix($GLOBALS['BE_USER']->getPagePermsClause(1))
440 );
441
442 if (is_numeric($id) && $id >= 0) {
443 $queryBuilder->andWhere(
444 $expressionBuilder->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT))
445 );
446 }
447
448 $excludedDoktypes = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.excludeDoktypes');
449 if (!empty($excludedDoktypes)) {
450 $queryBuilder->andWhere(
451 $expressionBuilder->notIn(
452 'doktype',
453 $queryBuilder->createNamedParameter(
454 GeneralUtility::intExplode(',', $excludedDoktypes, true),
455 Connection::PARAM_INT_ARRAY
456 )
457 )
458 );
459 }
460
461 if ($searchFilter !== '') {
462 $searchParts = $expressionBuilder->orX();
463 if (is_numeric($searchFilter) && $searchFilter > 0) {
464 $searchParts->add(
465 $expressionBuilder->eq('uid', $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_INT))
466 );
467 }
468 $searchFilter = '%' . $queryBuilder->escapeLikeWildcards($searchFilter) . '%';
469 $useNavTitle = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.showNavTitle');
470 $useAlias = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.searchInAlias');
471
472 $aliasExpression = '';
473 if ($useAlias) {
474 $aliasExpression = $expressionBuilder->like(
475 'alias',
476 $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
477 );
478 }
479
480 if ($useNavTitle) {
481 $searchWhereAlias = $expressionBuilder->orX(
482 $expressionBuilder->like(
483 'nav_title',
484 $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
485 ),
486 $expressionBuilder->andX(
487 $expressionBuilder->eq(
488 'nav_title',
489 $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
490 ),
491 $expressionBuilder->like(
492 'title',
493 $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
494 )
495 )
496 );
497 if (strlen($aliasExpression)) {
498 $searchWhereAlias->add($aliasExpression);
499 }
500 $searchParts->add($searchWhereAlias);
501 } else {
502 $searchParts->add(
503 $expressionBuilder->like(
504 'title',
505 $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
506 )
507 );
508
509 if (strlen($aliasExpression)) {
510 $searchParts->add($aliasExpression);
511 }
512 }
513
514 $queryBuilder->andWhere($searchParts);
515 }
516 return $queryBuilder;
517 }
518
519 /**
520 * Returns all sub-pages of a given id
521 *
522 * @param int $id
523 * @param string $searchFilter
524 * @return array
525 */
526 protected function getSubpages($id, $searchFilter = '')
527 {
528 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
529 $queryBuilder->getRestrictions()
530 ->removeAll()
531 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
532 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
533 $result = [];
534 $queryBuilder = $this->setWhereClause($queryBuilder, $id, $searchFilter);
535 $queryResult = $queryBuilder->select('uid', 't3ver_wsid')
536 ->from('pages')
537 ->orderBy('sorting')
538 ->execute();
539 while ($row = $queryResult->fetch()) {
540 $result[$row['uid']] = $row;
541 }
542 return $result;
543 }
544
545 /**
546 * Returns TRUE if the node has child's
547 *
548 * @param int $id
549 * @return bool
550 */
551 protected function hasNodeSubPages($id)
552 {
553 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
554 $queryBuilder->getRestrictions()
555 ->removeAll()
556 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
557 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
558 $queryBuilder = $this->setWhereClause($queryBuilder, $id);
559 $count = $queryBuilder->count('uid')
560 ->from('pages')
561 ->execute()
562 ->fetchColumn(0);
563 return (bool)$count;
564 }
565 }