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