2 namespace TYPO3\CMS\Backend\Tree\Pagetree
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Backend\Utility\BackendUtility
;
18 use TYPO3\CMS\Core\Database\ConnectionPool
;
19 use TYPO3\CMS\Core\Database\Query\QueryBuilder
;
20 use TYPO3\CMS\Core\Database\Query\QueryHelper
;
21 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction
;
22 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
;
23 use TYPO3\CMS\Core\Utility\GeneralUtility
;
26 * Page tree data provider.
28 class DataProvider
extends \TYPO3\CMS\Backend\Tree\AbstractTreeDataProvider
31 * Node limit that should be loaded for this request per mount
35 protected $nodeLimit = 0;
38 * Current amount of nodes
42 protected $nodeCounter = 0;
45 * TRUE to show the path of each mountpoint in the tree
49 protected $showRootlineAboveMounts = false;
56 protected $hiddenRecords = [];
59 * Process collection hook objects
61 * @var array<\TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface>
63 protected $processCollectionHookObjects = [];
68 * @param int $nodeLimit (optional)
70 public function __construct($nodeLimit = null)
72 if ($nodeLimit === null) {
73 $nodeLimit = $GLOBALS['TYPO3_CONF_VARS']['BE']['pageTree']['preloadLimit'];
75 $this->nodeLimit
= abs((int)$nodeLimit);
77 $this->showRootlineAboveMounts
= $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.showPathAboveMounts');
79 $this->hiddenRecords
= GeneralUtility
::trimExplode(',', $GLOBALS['BE_USER']->getTSConfigVal('options.hideRecords.pages'));
80 $hookElements = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/tree/pagetree/class.t3lib_tree_pagetree_dataprovider.php']['postProcessCollections'];
81 if (is_array($hookElements)) {
82 foreach ($hookElements as $classRef) {
83 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
84 $hookObject = GeneralUtility
::getUserObj($classRef);
85 if ($hookObject instanceof \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface
) {
86 $this->processCollectionHookObjects
[] = $hookObject;
93 * Returns the root node.
95 * @return \TYPO3\CMS\Backend\Tree\TreeNode the root node
97 public function getRoot()
99 /** @var $node \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode */
100 $node = GeneralUtility
::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode
::class);
101 $node->setId('root');
102 $node->setExpanded(true);
107 * Fetches the sub-nodes of the given node
109 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
110 * @param int $mountPoint
111 * @param int $level internally used variable as a recursion limiter
112 * @return \TYPO3\CMS\Backend\Tree\TreeNodeCollection
114 public function getNodes(\TYPO3\CMS\Backend\Tree\TreeNode
$node, $mountPoint = 0, $level = 0)
116 /** @var $nodeCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
117 $nodeCollection = GeneralUtility
::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection
::class);
118 if ($level >= 99 ||
$node->getStopPageTree()) {
119 return $nodeCollection;
121 $isVirtualRootNode = false;
122 $subpages = $this->getSubpages($node->getId());
123 // check if fetching subpages the "root"-page
124 // and in case of a virtual root return the mountpoints as virtual "subpages"
125 if ((int)$node->getId() === 0) {
126 // check no temporary mountpoint is used
127 if (!(int)$GLOBALS['BE_USER']->uc
['pageTree_temporaryMountPoint']) {
128 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
129 $mountPoints = array_unique($mountPoints);
130 if (!in_array(0, $mountPoints)) {
131 // using a virtual root node
132 // so then return the mount points here as "subpages" of the first node
133 $isVirtualRootNode = true;
135 foreach ($mountPoints as $webMountPoint) {
137 'uid' => $webMountPoint,
138 'isMountPoint' => true
144 if (is_array($subpages) && !empty($subpages)) {
145 foreach ($subpages as $subpage) {
146 if (in_array($subpage['uid'], $this->hiddenRecords
)) {
149 // must be calculated above getRecordWithWorkspaceOverlay,
150 // because the information is lost otherwise
151 $isMountPoint = $subpage['isMountPoint'] === true;
152 if ($isVirtualRootNode) {
153 $mountPoint = (int)$subpage['uid'];
155 $subpage = $this->getRecordWithWorkspaceOverlay($subpage['uid'], true);
159 $subNode = Commands
::getNewNode($subpage, $mountPoint);
160 $subNode->setIsMountPoint($isMountPoint);
161 if ($isMountPoint && $this->showRootlineAboveMounts
) {
162 $rootline = Commands
::getMountPointPath($subpage['uid']);
163 $subNode->setReadableRootline($rootline);
165 if ($this->nodeCounter
< $this->nodeLimit
) {
166 $childNodes = $this->getNodes($subNode, $mountPoint, $level +
1);
167 $subNode->setChildNodes($childNodes);
168 $this->nodeCounter +
= $childNodes->count();
170 $subNode->setLeaf(!$this->hasNodeSubPages($subNode->getId()));
172 if (!$GLOBALS['BE_USER']->isAdmin() && (int)$subpage['editlock'] === 1) {
173 $subNode->setLabelIsEditable(false);
175 $nodeCollection->append($subNode);
178 foreach ($this->processCollectionHookObjects
as $hookObject) {
179 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
180 $hookObject->postProcessGetNodes($node, $mountPoint, $level, $nodeCollection);
182 return $nodeCollection;
186 * Wrapper method for \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordWSOL
188 * @param int $uid The page id
189 * @param bool $unsetMovePointers Whether to unset move pointers
192 protected function getRecordWithWorkspaceOverlay($uid, $unsetMovePointers = false)
194 return BackendUtility
::getRecordWSOL('pages', $uid, '*', '', true, $unsetMovePointers);
198 * Returns a node collection of filtered nodes
200 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
201 * @param string $searchFilter
202 * @param int $mountPoint
203 * @return \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection the filtered nodes
205 public function getFilteredNodes(\TYPO3\CMS\Backend\Tree\TreeNode
$node, $searchFilter, $mountPoint = 0)
207 /** @var $nodeCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
208 $nodeCollection = GeneralUtility
::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection
::class);
209 $records = $this->getSubpages(-1, $searchFilter);
210 if (!is_array($records) ||
empty($records)) {
211 return $nodeCollection;
212 } elseif (count($records) > 500) {
213 return $nodeCollection;
215 // check no temporary mountpoint is used
216 $mountPoints = (int)$GLOBALS['BE_USER']->uc
['pageTree_temporaryMountPoint'];
218 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
219 $mountPoints = array_unique($mountPoints);
221 $mountPoints = [$mountPoints];
223 $isNumericSearchFilter = is_numeric($searchFilter) && $searchFilter > 0;
224 $searchFilterQuoted = preg_quote($searchFilter, '/');
225 $nodeId = (int)$node->getId();
226 $processedRecordIds = [];
227 foreach ($records as $record) {
228 if ((int)$record['t3ver_wsid'] !== (int)$GLOBALS['BE_USER']->workspace
&& (int)$record['t3ver_wsid'] !== 0) {
231 $liveVersion = BackendUtility
::getLiveVersionOfRecord('pages', $record['uid'], 'uid');
232 if ($liveVersion !== null) {
233 $record = $liveVersion;
236 $record = Commands
::getNodeRecord($record['uid'], false);
237 if ((int)$record['pid'] === -1
238 ||
in_array($record['uid'], $this->hiddenRecords
)
239 ||
in_array($record['uid'], $processedRecordIds)
243 $processedRecordIds[] = $record['uid'];
245 $rootline = BackendUtility
::BEgetRootLine($record['uid'], '', $GLOBALS['BE_USER']->workspace
!= 0);
246 $rootline = array_reverse($rootline);
247 if (!in_array(0, $mountPoints, true)) {
248 $isInsideMountPoints = false;
249 foreach ($rootline as $rootlineElement) {
250 if (in_array((int)$rootlineElement['uid'], $mountPoints, true)) {
251 $isInsideMountPoints = true;
255 if (!$isInsideMountPoints) {
259 $reference = $nodeCollection;
260 $inFilteredRootline = false;
261 $amountOfRootlineElements = count($rootline);
262 for ($i = 0; $i < $amountOfRootlineElements; ++
$i) {
263 $rootlineElement = $rootline[$i];
264 $rootlineElement['uid'] = (int)$rootlineElement['uid'];
265 $isInWebMount = (int)$GLOBALS['BE_USER']->isInWebMount($rootlineElement['uid']);
267 ||
($rootlineElement['uid'] === (int)$mountPoints[0]
268 && $rootlineElement['uid'] !== $isInWebMount)
272 if ((int)$rootlineElement['pid'] === $nodeId
273 ||
$rootlineElement['uid'] === $nodeId
274 ||
($rootlineElement['uid'] === $isInWebMount
275 && in_array($rootlineElement['uid'], $mountPoints, true))
277 $inFilteredRootline = true;
279 if (!$inFilteredRootline ||
$rootlineElement['uid'] === $mountPoint) {
282 $rootlineElement = Commands
::getNodeRecord($rootlineElement['uid'], false);
283 $ident = (int)$rootlineElement['sorting'] . (int)$rootlineElement['uid'];
284 if ($reference && $reference->offsetExists($ident)) {
285 /** @var $refNode \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode */
286 $refNode = $reference->offsetGet($ident);
287 $refNode->setExpanded(true);
288 $refNode->setLeaf(false);
289 $reference = $refNode->getChildNodes();
290 if ($reference == null) {
291 $reference = GeneralUtility
::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection
::class);
292 $refNode->setChildNodes($reference);
295 $refNode = Commands
::getNewNode($rootlineElement, $mountPoint);
296 $replacement = '<span class="typo3-pagetree-filteringTree-highlight">$1</span>';
297 if ($isNumericSearchFilter && (int)$rootlineElement['uid'] === (int)$searchFilter) {
298 $text = str_replace('$1', $refNode->getText(), $replacement);
300 $text = preg_replace('/(' . $searchFilterQuoted . ')/i', $replacement, $refNode->getText());
302 $refNode->setText($text, $refNode->getTextSourceField(), $refNode->getPrefix(), $refNode->getSuffix());
303 /** @var $childCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
304 $childCollection = GeneralUtility
::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection
::class);
305 if ($i +
1 >= $amountOfRootlineElements) {
306 $childNodes = $this->getNodes($refNode, $mountPoint);
307 foreach ($childNodes as $childNode) {
308 /** @var $childNode \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode */
309 $childRecord = $childNode->getRecord();
310 $childIdent = (int)$childRecord['sorting'] . (int)$childRecord['uid'];
311 $childCollection->offsetSet($childIdent, $childNode);
313 $refNode->setChildNodes($childNodes);
315 $refNode->setChildNodes($childCollection);
316 $reference->offsetSet($ident, $refNode);
318 $reference = $childCollection;
322 foreach ($this->processCollectionHookObjects
as $hookObject) {
323 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
324 $hookObject->postProcessFilteredNodes($node, $searchFilter, $mountPoint, $nodeCollection);
326 return $nodeCollection;
330 * Returns the page tree mounts for the current user
332 * Note: If you add the search filter parameter, the nodes will be filtered by this string.
334 * @param string $searchFilter
335 * @return \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection
337 public function getTreeMounts($searchFilter = '')
339 /** @var $nodeCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
340 $nodeCollection = GeneralUtility
::makeInstance(\TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection
::class);
341 $isTemporaryMountPoint = false;
342 $rootNodeIsVirtual = false;
343 $mountPoints = (int)$GLOBALS['BE_USER']->uc
['pageTree_temporaryMountPoint'];
345 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
346 $mountPoints = array_unique($mountPoints);
347 if (!in_array(0, $mountPoints)) {
348 $rootNodeIsVirtual = true;
349 // use a virtual root
350 // the real mountpoints will be fetched in getNodes() then
351 // since those will be the "subpages" of the virtual root
355 $isTemporaryMountPoint = true;
356 $mountPoints = [$mountPoints];
358 if (empty($mountPoints)) {
359 return $nodeCollection;
362 foreach ($mountPoints as $mountPoint) {
363 if ($mountPoint === 0) {
365 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] !== '') {
366 $sitename = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
372 $subNode = Commands
::getNewNode($record);
373 $subNode->setLabelIsEditable(false);
374 if ($rootNodeIsVirtual) {
375 $subNode->setType('virtual_root');
376 $subNode->setIsDropTarget(false);
378 $subNode->setType('pages_root');
379 $subNode->setIsDropTarget(true);
382 if (in_array($mountPoint, $this->hiddenRecords
)) {
385 $record = $this->getRecordWithWorkspaceOverlay($mountPoint);
389 $subNode = Commands
::getNewNode($record, $mountPoint);
390 if ($this->showRootlineAboveMounts
&& !$isTemporaryMountPoint) {
391 $rootline = Commands
::getMountPointPath($record['uid']);
392 $subNode->setReadableRootline($rootline);
395 if (count($mountPoints) <= 1) {
396 $subNode->setExpanded(true);
397 $subNode->setCls('typo3-pagetree-node-notExpandable');
399 $subNode->setIsMountPoint(true);
400 $subNode->setDraggable(false);
401 if ($searchFilter === '') {
402 $childNodes = $this->getNodes($subNode, $mountPoint);
404 $childNodes = $this->getFilteredNodes($subNode, $searchFilter, $mountPoint);
405 $subNode->setExpanded(true);
407 $subNode->setChildNodes($childNodes);
408 $nodeCollection->append($subNode);
410 foreach ($this->processCollectionHookObjects
as $hookObject) {
411 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
412 $hookObject->postProcessGetTreeMounts($searchFilter, $nodeCollection);
414 return $nodeCollection;
418 * Sets the Doctrine where clause for fetching pages
420 * @param QueryBuilder $queryBuilder
422 * @param string $searchFilter
423 * @return QueryBuilder
425 protected function setWhereClause(QueryBuilder
$queryBuilder, $id, $searchFilter = ''): QueryBuilder
427 $expressionBuilder = $queryBuilder->expr();
428 $queryBuilder->where(
429 QueryHelper
::stripLogicalOperatorPrefix($GLOBALS['BE_USER']->getPagePermsClause(1))
432 if (is_numeric($id) && $id >= 0) {
433 $queryBuilder->andWhere(
434 $expressionBuilder->eq('pid', (int)$id)
438 $excludedDoktypes = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.excludeDoktypes');
439 if (!empty($excludedDoktypes)) {
440 $queryBuilder->andWhere(
441 $expressionBuilder->notIn('doktype', GeneralUtility
::intExplode(',', $excludedDoktypes))
445 if ($searchFilter !== '') {
446 $searchParts = $expressionBuilder->orX();
447 if (is_numeric($searchFilter) && $searchFilter > 0) {
449 $expressionBuilder->eq('uid', (int)$searchFilter)
452 $searchFilter = '%' . $queryBuilder->escapeLikeWildcards($searchFilter) . '%';
453 $useNavTitle = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.showNavTitle');
454 $useAlias = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.searchInAlias');
456 $aliasExpression = '';
458 $aliasExpression = $expressionBuilder->like('alias', $queryBuilder->createNamedParameter($searchFilter));
462 $searchWhereAlias = $expressionBuilder->orX(
463 $expressionBuilder->like('nav_title', $queryBuilder->createNamedParameter($searchFilter)),
464 $expressionBuilder->andX(
465 $expressionBuilder->eq('nav_title', $queryBuilder->createNamedParameter('')),
466 $expressionBuilder->like('title', $queryBuilder->createNamedParameter($searchFilter))
469 if (strlen($aliasExpression)) {
470 $searchWhereAlias->add($aliasExpression);
472 $searchParts->add($searchWhereAlias);
475 $expressionBuilder->like('title', $queryBuilder->createNamedParameter($searchFilter))
478 if (strlen($aliasExpression)) {
479 $searchParts->add($aliasExpression);
483 $queryBuilder->andWhere($searchParts);
485 return $queryBuilder;
489 * Returns all sub-pages of a given id
492 * @param string $searchFilter
495 protected function getSubpages($id, $searchFilter = '')
497 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)->getQueryBuilderForTable('pages');
498 $queryBuilder->getRestrictions()
500 ->add(GeneralUtility
::makeInstance(DeletedRestriction
::class))
501 ->add(GeneralUtility
::makeInstance(BackendWorkspaceRestriction
::class));
503 $queryBuilder = $this->setWhereClause($queryBuilder, $id, $searchFilter);
504 $queryResult = $queryBuilder->select('uid', 't3ver_wsid')
508 while ($row = $queryResult->fetch()) {
509 $result[$row['uid']] = $row;
515 * Returns TRUE if the node has child's
520 protected function hasNodeSubPages($id)
522 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)->getQueryBuilderForTable('pages');
523 $queryBuilder->getRestrictions()
525 ->add(GeneralUtility
::makeInstance(DeletedRestriction
::class))
526 ->add(GeneralUtility
::makeInstance(BackendWorkspaceRestriction
::class));
527 $queryBuilder = $this->setWhereClause($queryBuilder, $id);
528 $count = $queryBuilder->count('uid')