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