Completed
Push — master ( 08c540...bc2036 )
by
unknown
19:56
created

PageTreeRepository::getChildPages()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 6
nop 1
dl 0
loc 25
rs 9.4888
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Backend\Tree\Repository;
19
20
use Doctrine\DBAL\Connection;
21
use TYPO3\CMS\Backend\Utility\BackendUtility;
22
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
23
use TYPO3\CMS\Core\Database\ConnectionPool;
24
use TYPO3\CMS\Core\Database\Query\QueryHelper;
25
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
27
use TYPO3\CMS\Core\DataHandling\PlainDataResolver;
28
use TYPO3\CMS\Core\Type\Bitmask\Permission;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use TYPO3\CMS\Core\Versioning\VersionState;
31
32
/**
33
 * Fetches ALL pages in the page tree, possibly overlaid with the workspace
34
 * in a sorted way.
35
 *
36
 * This works agnostic of the Backend User, allows to be used in FE as well in the future.
37
 *
38
 * @internal this class is not public API yet, as it needs to be proven stable enough first.
39
 */
40
class PageTreeRepository
41
{
42
    /**
43
     * Fields to be queried from the database
44
     *
45
     * @var string[]
46
     */
47
    protected $fields = [
48
        'uid',
49
        'pid',
50
        'sorting',
51
        'starttime',
52
        'endtime',
53
        'hidden',
54
        'fe_group',
55
        'title',
56
        'nav_title',
57
        'nav_hide',
58
        'php_tree_stop',
59
        'doktype',
60
        'is_siteroot',
61
        'module',
62
        'extendToSubpages',
63
        'content_from_pid',
64
        't3ver_oid',
65
        't3ver_wsid',
66
        't3ver_state',
67
        't3ver_stage',
68
        't3ver_move_id',
69
        'perms_userid',
70
        'perms_user',
71
        'perms_groupid',
72
        'perms_group',
73
        'perms_everybody',
74
        'mount_pid',
75
        'shortcut',
76
        'shortcut_mode',
77
        'mount_pid_ol',
78
        'url',
79
        'sys_language_uid',
80
        'l10n_parent',
81
    ];
82
83
    /**
84
     * The workspace ID to operate on
85
     *
86
     * @var int
87
     */
88
    protected $currentWorkspace = 0;
89
90
    /**
91
     * Full page tree when selected without permissions applied.
92
     *
93
     * @var array
94
     */
95
    protected $fullPageTree = [];
96
97
    /**
98
     * @var array
99
     */
100
    protected $additionalQueryRestrictions = [];
101
102
    /**
103
     * @param int $workspaceId the workspace ID to be checked for.
104
     * @param array $additionalFieldsToQuery an array with more fields that should be accessed.
105
     * @param array $additionalQueryRestrictions an array with more restrictions to add
106
     */
107
    public function __construct(int $workspaceId = 0, array $additionalFieldsToQuery = [], array $additionalQueryRestrictions = [])
108
    {
109
        $this->currentWorkspace = $workspaceId;
110
        if (!empty($additionalFieldsToQuery)) {
111
            $this->fields = array_merge($this->fields, $additionalFieldsToQuery);
112
        }
113
114
        if (!empty($additionalQueryRestrictions)) {
115
            $this->additionalQueryRestrictions = $additionalQueryRestrictions;
116
        }
117
    }
118
119
    /**
120
     * Main entry point for this repository, to fetch the tree data for a page.
121
     * Basically the page record, plus all child pages and their child pages recursively, stored within "_children" item.
122
     *
123
     * @param int $entryPoint the page ID to fetch the tree for
124
     * @param callable $callback a callback to be used to check for permissions and filter out pages not to be included.
125
     * @param array $dbMounts
126
     * @param bool $resolveUserPermissions
127
     * @return array
128
     */
129
    public function getTree(
130
        int $entryPoint,
131
        callable $callback = null,
132
        array $dbMounts = [],
133
        $resolveUserPermissions = false
134
    ): array {
135
        $this->fetchAllPages($dbMounts, $resolveUserPermissions);
136
        if ($entryPoint === 0) {
137
            $tree = $this->fullPageTree;
138
        } else {
139
            $tree = $this->findInPageTree($entryPoint, $this->fullPageTree);
140
        }
141
        if (!empty($tree) && $callback !== null) {
142
            $this->applyCallbackToChildren($tree, $callback);
143
        }
144
        return $tree;
145
    }
146
147
    /**
148
     * Removes items from a tree based on a callback, usually used for permission checks
149
     *
150
     * @param array $tree
151
     * @param callable $callback
152
     */
153
    protected function applyCallbackToChildren(array &$tree, callable $callback)
154
    {
155
        if (!isset($tree['_children'])) {
156
            return;
157
        }
158
        foreach ($tree['_children'] as $k => &$childPage) {
159
            if (!call_user_func_array($callback, [$childPage])) {
160
                unset($tree['_children'][$k]);
161
                continue;
162
            }
163
            $this->applyCallbackToChildren($childPage, $callback);
164
        }
165
    }
166
167
    /**
168
     * Get the page tree based on a given page record and a given depth
169
     *
170
     * @param array $pageTree The page record of the top level page you want to get the page tree of
171
     * @param int $depth Number of levels to fetch
172
     * @return array An array with page records and their children
173
     */
174
    public function getTreeLevels(array $pageTree, int $depth): array
175
    {
176
        $parentPageIds = [$pageTree['uid']];
177
        $groupedAndSortedPagesByPid = [];
178
        for ($i = 0; $i < $depth; $i++) {
179
            if (empty($parentPageIds)) {
180
                break;
181
            }
182
            $pageRecords = $this->getChildPageRecords($parentPageIds);
183
184
            $groupedAndSortedPagesByPid = $this->groupAndSortPages($pageRecords, $groupedAndSortedPagesByPid);
185
186
            $parentPageIds = array_column($pageRecords, 'uid');
187
        }
188
        $this->addChildrenToPage($pageTree, $groupedAndSortedPagesByPid);
189
        return $pageTree;
190
    }
191
192
    /**
193
     * Retrieve the page records based on the given parent page ids
194
     *
195
     * @param array $parentPageIds
196
     * @return array
197
     */
198
    protected function getChildPageRecords(array $parentPageIds): array
199
    {
200
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
201
            ->getQueryBuilderForTable('pages');
202
        $queryBuilder->getRestrictions()
203
            ->removeAll()
204
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
205
            ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->currentWorkspace));
206
207
        if (!empty($this->additionalQueryRestrictions)) {
208
            foreach ($this->additionalQueryRestrictions as $additionalQueryRestriction) {
209
                $queryBuilder->getRestrictions()->add($additionalQueryRestriction);
210
            }
211
        }
212
213
        $pageRecords = $queryBuilder
214
            ->select(...$this->fields)
215
            ->from('pages')
216
            ->where(
217
                $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
218
                $queryBuilder->expr()->in('pid', $queryBuilder->createNamedParameter($parentPageIds, Connection::PARAM_INT_ARRAY))
219
            )
220
            ->andWhere(
221
                QueryHelper::stripLogicalOperatorPrefix($GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW))
222
            )
223
            ->execute()
224
            ->fetchAll();
225
226
        // This is necessary to resolve all IDs in a workspace
227
        if ($this->currentWorkspace !== 0 && !empty($pageRecords)) {
228
            $livePagePids = [];
229
            $livePageIds = [];
230
            $movePlaceholderData = [];
231
            foreach ($pageRecords as $pageRecord) {
232
                $livePageIds[] = (int)$pageRecord['uid'];
233
                $livePagePids[(int)$pageRecord['uid']] = (int)$pageRecord['pid'];
234
                if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_PLACEHOLDER) {
235
                    $movePlaceholderData[$pageRecord['t3ver_move_id']] = [
236
                        'pid' => (int)$pageRecord['pid'],
237
                        'sorting' => (int)$pageRecord['sorting']
238
                    ];
239
                }
240
            }
241
242
            // Resolve placeholders of workspace versions
243
            $resolver = GeneralUtility::makeInstance(
244
                PlainDataResolver::class,
245
                'pages',
246
                $livePageIds
247
            );
248
            $resolver->setWorkspaceId($this->currentWorkspace);
249
            $resolver->setKeepDeletePlaceholder(false);
250
            $resolver->setKeepMovePlaceholder(false);
251
            $resolver->setKeepLiveIds(false);
252
            $recordIds = $resolver->get();
253
254
            if (!empty($recordIds)) {
255
                $queryBuilder->getRestrictions()->removeAll();
256
                $pageRecords = $queryBuilder
257
                    ->select(...$this->fields)
258
                    ->from('pages')
259
                    ->where(
260
                        $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($recordIds, Connection::PARAM_INT_ARRAY))
261
                    )
262
                    ->execute()
263
                    ->fetchAll();
264
265
                foreach ($pageRecords as &$pageRecord) {
266
                    if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_PLACEHOLDER) {
267
                        $liveRecord = BackendUtility::getRecord('pages', $pageRecord['t3ver_move_id']);
268
                        $pageRecord['uid'] = (int)$liveRecord['uid'];
269
                        $pageRecord['t3ver_oid'] = (int)$pageRecord['t3ver_move_id'];
270
                        $pageRecord['title'] = $liveRecord['title'];
271
                    } elseif ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_POINTER && !empty($movePlaceholderData[$pageRecord['t3ver_oid']])) {
272
                        $pageRecord['uid'] = $pageRecord['t3ver_oid'];
273
                        $pageRecord['sorting'] = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['sorting'];
274
                        $pageRecord['t3ver_state'] = VersionState::MOVE_PLACEHOLDER;
275
                        $pageRecord['pid'] = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['pid'];
276
                    } elseif ((int)$pageRecord['t3ver_oid'] > 0) {
277
                        $liveRecord = BackendUtility::getRecord('pages', $pageRecord['t3ver_oid']);
278
                        $pageRecord['sorting'] = (int)$liveRecord['sorting'];
279
                        $pageRecord['uid'] = (int)$liveRecord['uid'];
280
                        $pageRecord['pid'] = (int)$liveRecord['pid'];
281
                    }
282
                }
283
            } else {
284
                $pageRecords = [];
285
            }
286
        }
287
        foreach ($pageRecords as &$pageRecord) {
288
            $pageRecord['uid'] = (int)$pageRecord['uid'];
289
        }
290
291
        return $pageRecords;
292
    }
293
294
    public function hasChildren(int $pid): bool
295
    {
296
        $pageRecords = $this->getChildPageRecords([$pid]);
297
        return !empty($pageRecords);
298
    }
299
300
    /**
301
     * Fetch all non-deleted pages, regardless of permissions. That's why it's internal.
302
     *
303
     * @param array $dbMounts
304
     * @param bool $resolveUserPermissions
305
     * @return array the full page tree of the whole installation
306
     */
307
    protected function fetchAllPages(array $dbMounts, bool $resolveUserPermissions = false): array
308
    {
309
        if (!empty($this->fullPageTree)) {
310
            return $this->fullPageTree;
311
        }
312
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
313
            ->getQueryBuilderForTable('pages');
314
        $queryBuilder->getRestrictions()
315
            ->removeAll()
316
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
317
            ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->currentWorkspace));
318
319
        if (!empty($this->additionalQueryRestrictions)) {
320
            foreach ($this->additionalQueryRestrictions as $additionalQueryRestriction) {
321
                $queryBuilder->getRestrictions()->add($additionalQueryRestriction);
322
            }
323
        }
324
325
        $query = $queryBuilder
326
            ->select(...$this->fields)
327
            ->from('pages')
328
            ->where(
329
            // Only show records in default language
330
                $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
331
            );
332
333
        if ($resolveUserPermissions) {
334
            $query->andWhere(
335
                QueryHelper::stripLogicalOperatorPrefix($GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW))
336
            );
337
        }
338
339
        $pageRecords = $query->execute()->fetchAll();
340
341
        $ids = array_column($pageRecords, 'uid');
342
        foreach ($dbMounts as $mount) {
343
            $entryPointRootLine = BackendUtility::BEgetRootLine($mount, '', false, $this->fields);
344
            foreach ($entryPointRootLine as $page) {
345
                $pageId = (int)$page['uid'];
346
                if (in_array($pageId, $ids) || $pageId === 0) {
347
                    continue;
348
                }
349
                $pageRecords[] = $page;
350
                $ids[] = $pageId;
351
            }
352
        }
353
354
        $livePagePids = [];
355
        $movePlaceholderData = [];
356
        // This is necessary to resolve all IDs in a workspace
357
        if ($this->currentWorkspace !== 0 && !empty($pageRecords)) {
358
            $livePageIds = [];
359
            foreach ($pageRecords as $pageRecord) {
360
                $livePageIds[] = (int)$pageRecord['uid'];
361
                $livePagePids[(int)$pageRecord['uid']] = (int)$pageRecord['pid'];
362
                if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_PLACEHOLDER) {
363
                    $movePlaceholderData[$pageRecord['t3ver_move_id']] = [
364
                        'pid' => (int)$pageRecord['pid'],
365
                        'sorting' => (int)$pageRecord['sorting']
366
                    ];
367
                }
368
            }
369
            // Resolve placeholders of workspace versions
370
            $resolver = GeneralUtility::makeInstance(
371
                PlainDataResolver::class,
372
                'pages',
373
                $livePageIds
374
            );
375
            $resolver->setWorkspaceId($this->currentWorkspace);
376
            $resolver->setKeepDeletePlaceholder(false);
377
            $resolver->setKeepMovePlaceholder(false);
378
            $resolver->setKeepLiveIds(false);
379
            $recordIds = $resolver->get();
380
381
            $queryBuilder->getRestrictions()->removeAll();
382
            $pageRecords = $queryBuilder
383
                ->select(...$this->fields)
384
                ->from('pages')
385
                ->where(
386
                    $queryBuilder->expr()->in('uid', $recordIds)
387
                )
388
                ->execute()
389
                ->fetchAll();
390
        }
391
392
        // Now set up sorting, nesting (tree-structure) for all pages based on pid+sorting fields
393
        $groupedAndSortedPagesByPid = [];
394
        foreach ($pageRecords as $pageRecord) {
395
            $parentPageId = (int)$pageRecord['pid'];
396
            // In case this is a record from a workspace
397
            // The uid+pid of the live-version record is fetched
398
            // This is done in order to avoid fetching records again (e.g. via BackendUtility::workspaceOL()
399
            if ((int)$pageRecord['t3ver_oid'] > 0) {
400
                // When a move pointer is found, the pid+sorting of the MOVE_PLACEHOLDER should be used (this is the
401
                // workspace record holding this information), also the t3ver_state is set to the MOVE_PLACEHOLDER
402
                // because the record is then added
403
                if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_POINTER && !empty($movePlaceholderData[$pageRecord['t3ver_oid']])) {
404
                    $parentPageId = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['pid'];
405
                    $pageRecord['sorting'] = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['sorting'];
406
                    $pageRecord['t3ver_state'] = VersionState::MOVE_PLACEHOLDER;
407
                } else {
408
                    // Just a record in a workspace (not moved etc)
409
                    $parentPageId = (int)$livePagePids[$pageRecord['t3ver_oid']];
410
                }
411
                // this is necessary so the links to the modules are still pointing to the live IDs
412
                $pageRecord['uid'] = (int)$pageRecord['t3ver_oid'];
413
                $pageRecord['pid'] = $parentPageId;
414
            }
415
416
            $sorting = (int)$pageRecord['sorting'];
417
            while (isset($groupedAndSortedPagesByPid[$parentPageId][$sorting])) {
418
                $sorting++;
419
            }
420
            $groupedAndSortedPagesByPid[$parentPageId][$sorting] = $pageRecord;
421
        }
422
423
        $this->fullPageTree = [
424
            'uid' => 0,
425
            'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3'
426
        ];
427
        $this->addChildrenToPage($this->fullPageTree, $groupedAndSortedPagesByPid);
428
        return $this->fullPageTree;
429
    }
430
431
    /**
432
     * Adds the property "_children" to a page record with the child pages
433
     *
434
     * @param array $page
435
     * @param array[] $groupedAndSortedPagesByPid
436
     */
437
    protected function addChildrenToPage(array &$page, array &$groupedAndSortedPagesByPid)
438
    {
439
        $page['_children'] = $groupedAndSortedPagesByPid[(int)$page['uid']] ?? [];
440
        ksort($page['_children']);
441
        foreach ($page['_children'] as &$child) {
442
            $this->addChildrenToPage($child, $groupedAndSortedPagesByPid);
443
        }
444
    }
445
446
    /**
447
     * Looking for a page by traversing the tree
448
     *
449
     * @param int $pageId the page ID to search for
450
     * @param array $pages the page tree to look for the page
451
     * @return array Array of the tree data, empty array if nothing was found
452
     */
453
    protected function findInPageTree(int $pageId, array $pages): array
454
    {
455
        foreach ($pages['_children'] as $childPage) {
456
            if ((int)$childPage['uid'] === $pageId) {
457
                return $childPage;
458
            }
459
            $result = $this->findInPageTree($pageId, $childPage);
460
            if (!empty($result)) {
461
                return $result;
462
            }
463
        }
464
        return [];
465
    }
466
467
    /**
468
     * Retrieve the page tree based on the given search filter
469
     *
470
     * @param string $searchFilter
471
     * @param array $allowedMountPointPageIds
472
     * @param string $additionalWhereClause
473
     * @return array
474
     */
475
    public function fetchFilteredTree(string $searchFilter, array $allowedMountPointPageIds, string $additionalWhereClause): array
476
    {
477
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
478
            ->getQueryBuilderForTable('pages');
479
        $queryBuilder->getRestrictions()
480
            ->removeAll()
481
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
482
483
        if (!empty($this->additionalQueryRestrictions)) {
484
            foreach ($this->additionalQueryRestrictions as $additionalQueryRestriction) {
485
                $queryBuilder->getRestrictions()->add($additionalQueryRestriction);
486
            }
487
        }
488
489
        $expressionBuilder = $queryBuilder->expr();
490
491
        if ($this->currentWorkspace === 0) {
492
            // Only include ws_id=0
493
            $workspaceIdExpression = $expressionBuilder->eq('t3ver_wsid', 0);
494
        } else {
495
            // Include live records PLUS records from the given workspace
496
            $workspaceIdExpression = $expressionBuilder->in(
497
                't3ver_wsid',
498
                [0, $this->currentWorkspace]
499
            );
500
        }
501
502
        $queryBuilder = $queryBuilder
503
            ->select(...$this->fields)
504
            ->from('pages')
505
            ->where(
506
                // Only show records in default language
507
                $expressionBuilder->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
508
                $workspaceIdExpression,
509
                QueryHelper::stripLogicalOperatorPrefix($additionalWhereClause)
510
            );
511
512
        $searchParts = $expressionBuilder->orX();
513
        if (is_numeric($searchFilter) && $searchFilter > 0) {
514
            $searchParts->add(
515
                $expressionBuilder->eq('uid', $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_INT))
516
            );
517
        }
518
        $searchFilter = '%' . $queryBuilder->escapeLikeWildcards($searchFilter) . '%';
519
520
        $searchWhereAlias = $expressionBuilder->orX(
521
            $expressionBuilder->like(
522
                'nav_title',
523
                $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
524
            ),
525
            $expressionBuilder->like(
526
                'title',
527
                $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
528
            )
529
        );
530
        $searchParts->add($searchWhereAlias);
531
532
        $queryBuilder->andWhere($searchParts);
533
        $pageRecords = $queryBuilder
534
            ->execute()
535
            ->fetchAll();
536
537
        $livePagePids = [];
538
        if ($this->currentWorkspace !== 0 && !empty($pageRecords)) {
539
            $livePageIds = [];
540
            foreach ($pageRecords as $pageRecord) {
541
                $livePageIds[] = (int)$pageRecord['uid'];
542
                $livePagePids[(int)$pageRecord['uid']] = (int)$pageRecord['pid'];
543
                if ((int)$pageRecord['t3ver_oid'] > 0) {
544
                    $livePagePids[(int)$pageRecord['t3ver_oid']] = (int)$pageRecord['pid'];
545
                }
546
                if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_PLACEHOLDER) {
547
                    $movePlaceholderData[$pageRecord['t3ver_move_id']] = [
548
                        'pid' => (int)$pageRecord['pid'],
549
                        'sorting' => (int)$pageRecord['sorting']
550
                    ];
551
                }
552
            }
553
            // Resolve placeholders of workspace versions
554
            $resolver = GeneralUtility::makeInstance(
555
                PlainDataResolver::class,
556
                'pages',
557
                $livePageIds
558
            );
559
            $resolver->setWorkspaceId($this->currentWorkspace);
560
            $resolver->setKeepDeletePlaceholder(false);
561
            $resolver->setKeepMovePlaceholder(false);
562
            $resolver->setKeepLiveIds(false);
563
            $recordIds = $resolver->get();
564
565
            $pageRecords = [];
566
            if (!empty($recordIds)) {
567
                $queryBuilder->getRestrictions()->removeAll();
568
                $queryBuilder
569
                    ->select(...$this->fields)
570
                    ->from('pages')
571
                    ->where(
572
                        $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($recordIds, Connection::PARAM_INT_ARRAY))
573
                    );
574
                $queryBuilder->andWhere($searchParts);
575
                $pageRecords = $queryBuilder
576
                    ->execute()
577
                    ->fetchAll();
578
            }
579
        }
580
581
        $pages = [];
582
        foreach ($pageRecords as $pageRecord) {
583
            // In case this is a record from a workspace
584
            // The uid+pid of the live-version record is fetched
585
            // This is done in order to avoid fetching records again (e.g. via BackendUtility::workspaceOL()
586
            if ((int)$pageRecord['t3ver_oid'] > 0) {
587
                // This probably should also remove the live version
588
                if ((int)$pageRecord['t3ver_state'] === VersionState::DELETE_PLACEHOLDER) {
589
                    continue;
590
                }
591
                // When a move pointer is found, the pid+sorting of the MOVE_PLACEHOLDER should be used (this is the
592
                // workspace record holding this information), also the t3ver_state is set to the MOVE_PLACEHOLDER
593
                // because the record is then added
594
                if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_POINTER && !empty($movePlaceholderData[$pageRecord['t3ver_oid']])) {
595
                    $parentPageId = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['pid'];
596
                    $pageRecord['sorting'] = (int)$movePlaceholderData[$pageRecord['t3ver_oid']]['sorting'];
597
                    $pageRecord['t3ver_state'] = VersionState::MOVE_PLACEHOLDER;
598
                } else {
599
                    // Just a record in a workspace (not moved etc)
600
                    $parentPageId = (int)$livePagePids[$pageRecord['t3ver_oid']];
601
                }
602
                // this is necessary so the links to the modules are still pointing to the live IDs
603
                $pageRecord['uid'] = (int)$pageRecord['t3ver_oid'];
604
                $pageRecord['pid'] = $parentPageId;
605
            }
606
            $pages[(int)$pageRecord['uid']] = $pageRecord;
607
        }
608
        unset($pageRecords);
609
610
        $pages = $this->filterPagesOnMountPoints($pages, $allowedMountPointPageIds);
611
612
        $groupedAndSortedPagesByPid = $this->groupAndSortPages($pages);
613
614
        $this->fullPageTree = [
615
            'uid' => 0,
616
            'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3'
617
        ];
618
        $this->addChildrenToPage($this->fullPageTree, $groupedAndSortedPagesByPid);
619
620
        return $this->fullPageTree;
621
    }
622
623
    /**
624
     * Filter all records outside of the allowed mount points
625
     *
626
     * @param array $pages
627
     * @param array $mountPoints
628
     * @return array
629
     */
630
    protected function filterPagesOnMountPoints(array $pages, array $mountPoints): array
631
    {
632
        foreach ($pages as $key => $pageRecord) {
633
            $rootline = BackendUtility::BEgetRootLine(
634
                $pageRecord['uid'],
635
                '',
636
                $this->currentWorkspace !== 0,
637
                $this->fields
638
            );
639
            $rootline = array_reverse($rootline);
640
            if (!in_array(0, $mountPoints, true)) {
641
                $isInsideMountPoints = false;
642
                foreach ($rootline as $rootlineElement) {
643
                    if (in_array((int)$rootlineElement['uid'], $mountPoints, true)) {
644
                        $isInsideMountPoints = true;
645
                        break;
646
                    }
647
                }
648
                if (!$isInsideMountPoints) {
649
                    unset($pages[$key]);
650
                    //skip records outside of the allowed mount points
651
                    continue;
652
                }
653
            }
654
655
            $inFilteredRootline = false;
656
            $amountOfRootlineElements = count($rootline);
657
            for ($i = 0; $i < $amountOfRootlineElements; ++$i) {
658
                $rootlineElement = $rootline[$i];
659
                $rootlineElement['uid'] = (int)$rootlineElement['uid'];
660
                $isInWebMount = false;
661
                if ($rootlineElement['uid'] > 0) {
662
                    $isInWebMount = (int)$this->getBackendUser()->isInWebMount($rootlineElement);
663
                }
664
665
                if (!$isInWebMount
0 ignored issues
show
Bug Best Practice introduced by
The expression $isInWebMount of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
666
                    || ($rootlineElement['uid'] === (int)$mountPoints[0]
667
                        && $rootlineElement['uid'] !== $isInWebMount)
668
                ) {
669
                    continue;
670
                }
671
                if ($this->getBackendUser()->isAdmin() || ($rootlineElement['uid'] === $isInWebMount && in_array($rootlineElement['uid'], $mountPoints, true))) {
672
                    $inFilteredRootline = true;
673
                }
674
                if (!$inFilteredRootline) {
675
                    continue;
676
                }
677
678
                if (!isset($pages[$rootlineElement['uid']])) {
679
                    $pages[$rootlineElement['uid']] = $rootlineElement;
680
                }
681
            }
682
        }
683
        // Make sure the mountpoints show up in page tree even when parent pages are not accessible pages
684
        foreach ($mountPoints as $mountPoint) {
685
            if ($mountPoint !== 0) {
686
                if (!array_key_exists($mountPoint, $pages)) {
687
                    $pages[$mountPoint] = BackendUtility::getRecord('pages', $mountPoint);
688
                    $pages[$mountPoint]['uid'] = (int)$pages[$mountPoint]['uid'];
689
                }
690
                $pages[$mountPoint]['pid'] = 0;
691
            }
692
        }
693
694
        return $pages;
695
    }
696
697
    /**
698
     * Group pages by parent page and sort pages based on sorting property
699
     *
700
     * @param array $pages
701
     * @param array $groupedAndSortedPagesByPid
702
     * @return array
703
     */
704
    protected function groupAndSortPages(array $pages, $groupedAndSortedPagesByPid = []): array
705
    {
706
        foreach ($pages as $key => $pageRecord) {
707
            $parentPageId = (int)$pageRecord['pid'];
708
            $sorting = (int)$pageRecord['sorting'];
709
            while (isset($groupedAndSortedPagesByPid[$parentPageId][$sorting])) {
710
                $sorting++;
711
            }
712
            $groupedAndSortedPagesByPid[$parentPageId][$sorting] = $pageRecord;
713
        }
714
715
        return $groupedAndSortedPagesByPid;
716
    }
717
718
    /**
719
     * @return BackendUserAuthentication
720
     */
721
    protected function getBackendUser(): BackendUserAuthentication
722
    {
723
        return $GLOBALS['BE_USER'];
724
    }
725
}
726