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