[FEATURE] Add search for alias as option to the pagetree filter
[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 foreach ($records as $record) {
225 $record = Commands::getNodeRecord($record['uid']);
226 if (intval($record['pid']) === -1 || in_array($record['uid'], $this->hiddenRecords)) {
227 continue;
228 }
229 $rootline = BackendUtility::BEgetRootLine($record['uid'], '', $GLOBALS['BE_USER']->workspace != 0);
230 $rootline = array_reverse($rootline);
231 if ($nodeId === 0) {
232 array_shift($rootline);
233 }
234 if ($mountPoints != array(0)) {
235 $isInsideMountPoints = FALSE;
236 foreach ($rootline as $rootlineElement) {
237 if (in_array(intval($rootlineElement['uid']), $mountPoints, TRUE)) {
238 $isInsideMountPoints = TRUE;
239 break;
240 }
241 }
242 if (!$isInsideMountPoints) {
243 continue;
244 }
245 }
246 $reference = $nodeCollection;
247 $inFilteredRootline = FALSE;
248 $amountOfRootlineElements = count($rootline);
249 for ($i = 0; $i < $amountOfRootlineElements; ++$i) {
250 $rootlineElement = $rootline[$i];
251 $isInWebMount = $GLOBALS['BE_USER']->isInWebMount($rootlineElement['uid']);
252 if (!$isInWebMount
253 || (intval($rootlineElement['uid']) === intval($mountPoints[0])
254 && intval($rootlineElement['uid']) !== intval($isInWebMount))
255 ) {
256 continue;
257 }
258 if (intval($rootlineElement['pid']) === $nodeId
259 || intval($rootlineElement['uid']) === $nodeId
260 || (intval($rootlineElement['uid']) === intval($isInWebMount)
261 && in_array(intval($rootlineElement['uid']), $mountPoints, TRUE))
262 ) {
263 $inFilteredRootline = TRUE;
264 }
265 if (!$inFilteredRootline || intval($rootlineElement['uid']) === intval($mountPoint)) {
266 continue;
267 }
268 $rootlineElement = Commands::getNodeRecord($rootlineElement['uid']);
269 $ident = intval($rootlineElement['sorting']) . intval($rootlineElement['uid']);
270 if ($reference && $reference->offsetExists($ident)) {
271 /** @var $refNode \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode */
272 $refNode = $reference->offsetGet($ident);
273 $refNode->setExpanded(TRUE);
274 $refNode->setLeaf(FALSE);
275 $reference = $refNode->getChildNodes();
276 if ($reference == NULL) {
277 $reference = GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Tree\\Pagetree\\PagetreeNodeCollection');
278 $refNode->setChildNodes($reference);
279 }
280 } else {
281 $refNode = Commands::getNewNode($rootlineElement, $mountPoint);
282 $replacement = '<span class="typo3-pagetree-filteringTree-highlight">$1</span>';
283 if ($isNumericSearchFilter && intval($rootlineElement['uid']) === intval($searchFilter)) {
284 $text = str_replace('$1', $refNode->getText(), $replacement);
285 } else {
286 $text = preg_replace('/(' . $searchFilterQuoted . ')/i', $replacement, $refNode->getText());
287 }
288 $refNode->setText($text, $refNode->getTextSourceField(), $refNode->getPrefix(), $refNode->getSuffix());
289 /** @var $childCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
290 $childCollection = GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Tree\\Pagetree\\PagetreeNodeCollection');
291 if ($i + 1 >= $amountOfRootlineElements) {
292 $childNodes = $this->getNodes($refNode, $mountPoint);
293 foreach ($childNodes as $childNode) {
294 /** @var $childNode \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode */
295 $childRecord = $childNode->getRecord();
296 $childIdent = intval($childRecord['sorting']) . intval($childRecord['uid']);
297 $childCollection->offsetSet($childIdent, $childNode);
298 }
299 $refNode->setChildNodes($childNodes);
300 }
301 $refNode->setChildNodes($childCollection);
302 $reference->offsetSet($ident, $refNode);
303 $reference->ksort();
304 $reference = $childCollection;
305 }
306 }
307 }
308 foreach ($this->processCollectionHookObjects as $hookObject) {
309 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
310 $hookObject->postProcessFilteredNodes($node, $searchFilter, $mountPoint, $nodeCollection);
311 }
312 return $nodeCollection;
313 }
314
315 /**
316 * Returns the page tree mounts for the current user
317 *
318 * Note: If you add the search filter parameter, the nodes will be filtered by this string.
319 *
320 * @param string $searchFilter
321 * @return \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection
322 */
323 public function getTreeMounts($searchFilter = '') {
324 /** @var $nodeCollection \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection */
325 $nodeCollection = GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Tree\\Pagetree\\PagetreeNodeCollection');
326 $isTemporaryMountPoint = FALSE;
327 $rootNodeIsVirtual = FALSE;
328 $mountPoints = intval($GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint']);
329 if (!$mountPoints) {
330 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
331 $mountPoints = array_unique($mountPoints);
332 if (!in_array(0, $mountPoints)) {
333 $rootNodeIsVirtual = TRUE;
334 // use a virtual root
335 // the real mountpoints will be fetched in getNodes() then
336 // since those will be the "subpages" of the virtual root
337 $mountPoints = array(0);
338 }
339 } else {
340 $isTemporaryMountPoint = TRUE;
341 $mountPoints = array($mountPoints);
342 }
343 if (!count($mountPoints)) {
344 return $nodeCollection;
345 }
346
347 foreach ($mountPoints as $mountPoint) {
348 if ($mountPoint === 0) {
349 $sitename = 'TYPO3';
350 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] !== '') {
351 $sitename = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
352 }
353 $record = array(
354 'uid' => 0,
355 'title' => $sitename
356 );
357 $subNode = Commands::getNewNode($record);
358 $subNode->setLabelIsEditable(FALSE);
359 if ($rootNodeIsVirtual) {
360 $subNode->setType('virtual_root');
361 $subNode->setIsDropTarget(FALSE);
362 } else {
363 $subNode->setType('pages_root');
364 $subNode->setIsDropTarget(TRUE);
365 }
366 } else {
367 if (in_array($mountPoint, $this->hiddenRecords)) {
368 continue;
369 }
370 $record = $this->getRecordWithWorkspaceOverlay($mountPoint);
371 if (!$record) {
372 continue;
373 }
374 $subNode = Commands::getNewNode($record, $mountPoint);
375 if ($this->showRootlineAboveMounts && !$isTemporaryMountPoint) {
376 $rootline = Commands::getMountPointPath($record['uid']);
377 $subNode->setReadableRootline($rootline);
378 }
379 }
380 if (count($mountPoints) <= 1) {
381 $subNode->setExpanded(TRUE);
382 $subNode->setCls('typo3-pagetree-node-notExpandable');
383 }
384 $subNode->setIsMountPoint(TRUE);
385 $subNode->setDraggable(FALSE);
386 if ($searchFilter === '') {
387 $childNodes = $this->getNodes($subNode, $mountPoint);
388 } else {
389 $childNodes = $this->getFilteredNodes($subNode, $searchFilter, $mountPoint);
390 $subNode->setExpanded(TRUE);
391 }
392 $subNode->setChildNodes($childNodes);
393 $nodeCollection->append($subNode);
394 }
395 foreach ($this->processCollectionHookObjects as $hookObject) {
396 /** @var $hookObject \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface */
397 $hookObject->postProcessGetTreeMounts($searchFilter, $nodeCollection);
398 }
399 return $nodeCollection;
400 }
401
402 /**
403 * Returns the where clause for fetching pages
404 *
405 * @param integer $id
406 * @param string $searchFilter
407 * @return string
408 */
409 protected function getWhereClause($id, $searchFilter = '') {
410 $where = $GLOBALS['BE_USER']->getPagePermsClause(1) . BackendUtility::deleteClause('pages') . BackendUtility::versioningPlaceholderClause('pages');
411 if (is_numeric($id) && $id >= 0) {
412 $where .= ' AND pid= ' . $GLOBALS['TYPO3_DB']->fullQuoteStr(intval($id), 'pages');
413 }
414
415 $excludedDoktypes = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.excludeDoktypes');
416 if (!empty($excludedDoktypes)) {
417 $excludedDoktypes = $GLOBALS['TYPO3_DB']->fullQuoteArray(GeneralUtility::intExplode(',', $excludedDoktypes), 'pages');
418 $where .= ' AND doktype NOT IN (' . implode(',', $excludedDoktypes) . ')';
419 }
420
421 if ($searchFilter !== '') {
422 if (is_numeric($searchFilter) && $searchFilter > 0) {
423 $searchWhere .= 'uid = ' . intval($searchFilter) . ' OR ';
424 }
425 $searchFilter = $GLOBALS['TYPO3_DB']->fullQuoteStr('%' . $searchFilter . '%', 'pages');
426 $useNavTitle = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.showNavTitle');
427 $useAlias = $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.searchInAlias');
428
429 $searchWhereAlias = '';
430 if ($useAlias) {
431 $searchWhereAlias = ' OR alias LIKE ' . $searchFilter;
432 }
433
434 if ($useNavTitle) {
435 $searchWhere .= '(nav_title LIKE ' . $searchFilter .
436 ' OR (nav_title = "" AND title LIKE ' . $searchFilter . ')' . $searchWhereAlias . ')';
437 } else {
438 $searchWhere .= 'title LIKE ' . $searchFilter . $searchWhereAlias;
439 }
440
441 $where .= ' AND (' . $searchWhere . ')';
442 }
443 return $where;
444 }
445
446 /**
447 * Returns all sub-pages of a given id
448 *
449 * @param integer $id
450 * @param string $searchFilter
451 * @return array
452 */
453 protected function getSubpages($id, $searchFilter = '') {
454 $where = $this->getWhereClause($id, $searchFilter);
455 return $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', 'pages', $where, '', 'sorting', '', 'uid');
456 }
457
458 /**
459 * Returns TRUE if the node has child's
460 *
461 * @param integer $id
462 * @return boolean
463 */
464 protected function hasNodeSubPages($id) {
465 $where = $this->getWhereClause($id);
466 $subpage = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('uid', 'pages', $where, '', 'sorting', '', 'uid');
467 $returnValue = TRUE;
468 if (!$subpage['uid']) {
469 $returnValue = FALSE;
470 }
471 return $returnValue;
472 }
473
474 }
475
476
477 ?>