From 4f5e778da18b642d1acc6de871c3fd96f82eef75 Mon Sep 17 00:00:00 2001 From: Benjamin Franzke <bfr@qbus.de> Date: Mon, 15 Nov 2021 10:21:06 +0100 Subject: [PATCH] [BUGFIX] Always display root page in page tree This applies to rendering of database mounts (from groups or from workspaces): They are now shown as children of the virtual root page (uid=0) as it used to be in TYPO3 v8. This is to allow workspace users to get an overview of all changes of all mounts by navigating to the virtual page uid=0. Releases: master, 11.5, 10.4 Resolves: #95854 Related: #95972 Related: #91145 Change-Id: I7f6370f327711396193cf56b63f15876350c2559 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72028 Tested-by: core-ci <typo3@b13.com> Tested-by: Benni Mack <benni@typo3.org> Tested-by: Jochen <rothjochen@gmail.com> Tested-by: Benjamin Franzke <bfr@qbus.de> Reviewed-by: Benni Mack <benni@typo3.org> Reviewed-by: Jochen <rothjochen@gmail.com> Reviewed-by: Benjamin Franzke <bfr@qbus.de> --- .../Controller/Page/TreeController.php | 104 +++--- .../Tree/Repository/PageTreeRepository.php | 50 ++- .../Controller/Page/TreeControllerTest.php | 336 ++++++++++-------- 3 files changed, 277 insertions(+), 213 deletions(-) diff --git a/typo3/sysext/backend/Classes/Controller/Page/TreeController.php b/typo3/sysext/backend/Classes/Controller/Page/TreeController.php index f2ff74207d10..be8b6fbf69d9 100644 --- a/typo3/sysext/backend/Classes/Controller/Page/TreeController.php +++ b/typo3/sysext/backend/Classes/Controller/Page/TreeController.php @@ -498,28 +498,19 @@ class TreeController protected function getAllEntryPointPageTrees(int $startPid = 0, string $query = ''): array { $backendUser = $this->getBackendUser(); - $entryPointId = $startPid > 0 ? $startPid : (int)($backendUser->uc['pageTree_temporaryMountPoint'] ?? 0); - if ($entryPointId > 0) { - $entryPointIds = [$entryPointId]; + if ($startPid === 0) { + $startPid = (int)($backendUser->uc['pageTree_temporaryMountPoint'] ?? 0); + } + + $entryPointIds = null; + if ($startPid > 0) { + $entryPointIds = [$startPid]; } elseif (!empty($this->alternativeEntryPoints)) { $entryPointIds = $this->alternativeEntryPoints; - } else { - //watch out for deleted pages returned as webmount - $entryPointIds = array_map('intval', $backendUser->returnWebmounts()); - $entryPointIds = array_unique($entryPointIds); - if (empty($entryPointIds)) { - // use a virtual root - // the real mount points will be fetched in getNodes() then - // since those will be the "sub pages" of the virtual root - $entryPointIds = [0]; - } - } - if (empty($entryPointIds)) { - return []; } - $repository = $this->getPageTreeRepository(); - $permClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW); + $repository = $this->getPageTreeRepository(); + $permClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW); if ($query !== '') { $this->levelsToFetch = 999; $repository->fetchFilteredTree( @@ -528,55 +519,72 @@ class TreeController $permClause ); } - $entryPointRecords = []; - foreach ($entryPointIds as $k => $entryPointId) { - if (in_array($entryPointId, $this->hiddenRecords, true)) { - continue; - } + if ($entryPointIds === null) { + $rootRecord = [ + 'uid' => 0, + 'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3', + ]; - if (!empty($this->backgroundColors) && is_array($this->backgroundColors)) { - try { - $entryPointRootLine = GeneralUtility::makeInstance(RootlineUtility::class, $entryPointId)->get(); - } catch (RootLineException $e) { - $entryPointRootLine = []; - } - foreach ($entryPointRootLine as $rootLineEntry) { - $parentUid = $rootLineEntry['uid']; - if (!empty($this->backgroundColors[$parentUid]) && empty($this->backgroundColors[$entryPointId])) { - $this->backgroundColors[$entryPointId] = $this->backgroundColors[$parentUid]; - } - } - } - if ($entryPointId === 0) { - $entryPointRecord = [ - 'uid' => 0, - 'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3', - ]; + //watch out for deleted pages returned as webmount + $mountPoints = array_map('intval', $backendUser->returnWebmounts()); + $mountPoints = array_unique($mountPoints); + $mountPoints = array_filter($mountPoints, fn ($id) => !in_array($id, $this->hiddenRecords, true)); + + if ($query !== '') { + $rootRecord = $repository->getTree(0, null, $mountPoints, true); } else { + $rootRecord = $repository->getTreeLevels($rootRecord, $this->levelsToFetch, $mountPoints); + } + $entryPointRecords[] = $rootRecord; + } else { + $entryPointIds = array_filter($entryPointIds, fn ($id) => !in_array($id, $this->hiddenRecords, true)); + $this->calculateBackgroundColors($entryPointIds); + foreach ($entryPointIds as $k => $entryPointId) { $entryPointRecord = BackendUtility::getRecordWSOL('pages', $entryPointId, '*', $permClause); - if ($entryPointRecord !== null && !$this->getBackendUser()->isInWebMount($entryPointId)) { + if ($entryPointRecord !== null && !$backendUser->isInWebMount($entryPointId)) { $entryPointRecord = null; } - } - if ($entryPointRecord) { + if ($entryPointRecord === null) { + continue; + } + $entryPointRecord['uid'] = (int)$entryPointRecord['uid']; if ($query === '') { $entryPointRecord = $repository->getTreeLevels($entryPointRecord, $this->levelsToFetch); } else { - $entryPointRecord = $repository->getTree((int)$entryPointRecord['uid'], null, $entryPointIds, true); + $entryPointRecord = $repository->getTree($entryPointRecord['uid'], null, $entryPointIds, true); } - } - if (is_array($entryPointRecord) && !empty($entryPointRecord)) { - $entryPointRecords[$k] = $entryPointRecord; + if (is_array($entryPointRecord) && !empty($entryPointRecord)) { + $entryPointRecords[$k] = $entryPointRecord; + } } } return $entryPointRecords; } + protected function calculateBackgroundColors(array $pageIds) + { + foreach ($pageIds as $k => $pageId) { + if (!empty($this->backgroundColors) && is_array($this->backgroundColors)) { + try { + $entryPointRootLine = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get(); + } catch (RootLineException $e) { + $entryPointRootLine = []; + } + foreach ($entryPointRootLine as $rootLineEntry) { + $parentUid = $rootLineEntry['uid']; + if (!empty($this->backgroundColors[$parentUid]) && empty($this->backgroundColors[$pageId])) { + $this->backgroundColors[$pageId] = $this->backgroundColors[$parentUid]; + } + } + } + } + } + /** * Returns the first configured domain name for a page * diff --git a/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php b/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php index 4c832fd01cf4..2688de6f7330 100644 --- a/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php +++ b/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php @@ -168,12 +168,21 @@ class PageTreeRepository * * @param array $pageTree The page record of the top level page you want to get the page tree of * @param int $depth Number of levels to fetch + * @param array $entryPointIds entryPointIds to include * @return array An array with page records and their children */ - public function getTreeLevels(array $pageTree, int $depth): array + public function getTreeLevels(array $pageTree, int $depth, array $entryPointIds = []): array { - $parentPageIds = [$pageTree['uid']]; $groupedAndSortedPagesByPid = []; + + if (count($entryPointIds) > 0) { + $pageRecords = $this->getPageRecords($entryPointIds); + $groupedAndSortedPagesByPid = $this->groupAndSortPages($pageRecords, $groupedAndSortedPagesByPid, 0); + $parentPageIds = $entryPointIds; + } else { + $parentPageIds = [$pageTree['uid']]; + } + for ($i = 0; $i < $depth; $i++) { if (empty($parentPageIds)) { break; @@ -188,13 +197,19 @@ class PageTreeRepository return $pageTree; } + protected function getChildPageRecords(array $parentPageIds): array + { + return $this->getPageRecords([], $parentPageIds); + } + /** - * Retrieve the page records based on the given parent page ids + * Retrieve the page records based on the given page or parent page ids * + * @param array $pageIds * @param array $parentPageIds * @return array */ - protected function getChildPageRecords(array $parentPageIds): array + protected function getPageRecords(array $pageIds = [], array $parentPageIds = []): array { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('pages'); @@ -209,16 +224,29 @@ class PageTreeRepository } } - $pageRecords = $queryBuilder + $queryBuilder ->select(...$this->fields) ->from('pages') ->where( - $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)), - $queryBuilder->expr()->in('pid', $queryBuilder->createNamedParameter($parentPageIds, Connection::PARAM_INT_ARRAY)) + $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)) ) ->andWhere( QueryHelper::stripLogicalOperatorPrefix($GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW)) - ) + ); + + if (count($pageIds) > 0) { + $queryBuilder->andWhere( + $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)) + ); + } + + if (count($parentPageIds) > 0) { + $queryBuilder->andWhere( + $queryBuilder->expr()->in('pid', $queryBuilder->createNamedParameter($parentPageIds, Connection::PARAM_INT_ARRAY)) + ); + } + + $pageRecords = $queryBuilder ->execute() ->fetchAllAssociative(); @@ -685,12 +713,16 @@ class PageTreeRepository * * @param array $pages * @param array $groupedAndSortedPagesByPid + * @param int|null $forcePid * @return array */ - protected function groupAndSortPages(array $pages, $groupedAndSortedPagesByPid = []): array + protected function groupAndSortPages(array $pages, $groupedAndSortedPagesByPid = [], ?int $forcePid = null): array { foreach ($pages as $key => $pageRecord) { $parentPageId = (int)$pageRecord['pid']; + if ($forcePid !== null) { + $parentPageId = $forcePid; + } $sorting = (int)$pageRecord['sorting']; while (isset($groupedAndSortedPagesByPid[$parentPageId][$sorting])) { $sorting++; diff --git a/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php b/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php index 22ae31a04b48..c1382561a904 100644 --- a/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php +++ b/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php @@ -111,99 +111,105 @@ class TreeControllerTest extends FunctionalTestCase $expected = [ [ - 'uid' => 1000, - 'title' => 'ACME Inc', + 'uid' => 0, + 'title' => 'New TYPO3 site', '_children' => [ [ - 'uid' => 1100, - 'title' => 'EN: Welcome', - '_children' => [ - ], - ], - [ - 'uid' => 1200, - 'title' => 'EN: Features', + 'uid' => 1000, + 'title' => 'ACME Inc', '_children' => [ [ - 'uid' => 1210, - 'title' => 'EN: Frontend Editing', + 'uid' => 1100, + 'title' => 'EN: Welcome', '_children' => [ ], ], [ - 'uid' => 1230, - 'title' => 'EN: Managing content', + 'uid' => 1200, + 'title' => 'EN: Features', '_children' => [ + [ + 'uid' => 1210, + 'title' => 'EN: Frontend Editing', + '_children' => [ + ], + ], + [ + 'uid' => 1230, + 'title' => 'EN: Managing content', + '_children' => [ + ], + ], ], ], - ], - ], - [ - 'uid' => 1400, - 'title' => 'EN: ACME in your Region', - '_children' => [ [ - 'uid' => 1410, - 'title' => 'EN: Groups', + 'uid' => 1400, + 'title' => 'EN: ACME in your Region', '_children' => [ + [ + 'uid' => 1410, + 'title' => 'EN: Groups', + '_children' => [ + ], + ], ], ], - ], - ], - [ - 'uid' => 1500, - 'title' => 'Internal', - '_children' => [ [ - 'uid' => 1520, - 'title' => 'Forecasts', - '_children' => [], + 'uid' => 1500, + 'title' => 'Internal', + '_children' => [ + [ + 'uid' => 1520, + 'title' => 'Forecasts', + '_children' => [], + ], + [ + 'uid' => 1530, + 'title' => 'Reports', + '_children' => [ + ], + ], + ], ], [ - 'uid' => 1530, - 'title' => 'Reports', + 'uid' => 1700, + 'title' => 'Announcements & News', + '_children' => [ + ], + ], + [ + 'uid' => 404, + 'title' => 'Page not found', + '_children' => [ + ], + ], + [ + 'uid' => 1930, + 'title' => 'Our Blog', + '_children' => [ + ], + ], + [ + 'uid' => 1990, + 'title' => 'Storage', '_children' => [ ], ], ], ], [ - 'uid' => 1700, - 'title' => 'Announcements & News', - '_children' => [ - ], - ], - [ - 'uid' => 404, - 'title' => 'Page not found', - '_children' => [ - ], - ], - [ - 'uid' => 1930, - 'title' => 'Our Blog', - '_children' => [ - ], - ], - [ - 'uid' => 1990, - 'title' => 'Storage', + 'uid' => 8110, + 'title' => 'Europe', '_children' => [ + [ + 'uid' => 811000, + 'title' => 'France', + '_children' => [], + ], ], ], ], ], - [ - 'uid' => 8110, - 'title' => 'Europe', - '_children' => [ - [ - 'uid' => 811000, - 'title' => 'France', - '_children' => [], - ], - ], - ], ]; self::assertEquals($expected, $actual); } @@ -220,27 +226,33 @@ class TreeControllerTest extends FunctionalTestCase $expected = [ [ - 'uid' => 1000, - 'title' => 'ACME Inc', - '_children' => [ + 'uid' => 0, + 'title' => 'New TYPO3 site', + '_children' =>[ [ - 'uid' => 1400, - 'title' => 'EN: ACME in your Region', + 'uid' => 1000, + 'title' => 'ACME Inc', '_children' => [ [ - 'uid' => 1410, - 'title' => 'EN: Groups', + 'uid' => 1400, + 'title' => 'EN: ACME in your Region', '_children' => [ + [ + 'uid' => 1410, + 'title' => 'EN: Groups', + '_children' => [ + ], + ], ], ], ], ], - ], - ], - [ - 'uid' => 8110, - 'title' => 'Europe', - '_children' => [ + [ + 'uid' => 8110, + 'title' => 'Europe', + '_children' => [ + ], + ], ], ], ]; @@ -321,107 +333,113 @@ class TreeControllerTest extends FunctionalTestCase $expected = [ [ - 'uid' => 1000, - 'title' => 'ACME Inc', - '_children' => [ + 'uid' => 0, + 'title' => 'New TYPO3 site', + '_children' =>[ [ - 'uid' => 1950, - 'title' => 'EN: Goodbye', + 'uid' => 1000, + 'title' => 'ACME Inc', '_children' => [ [ - 'uid' => 10015, - 'title' => 'EN: Really Goodbye', + 'uid' => 1950, + 'title' => 'EN: Goodbye', '_children' => [ + [ + 'uid' => 10015, + 'title' => 'EN: Really Goodbye', + '_children' => [ + ], + ], ], ], - ], - ], - [ - 'uid' => 1100, - 'title' => 'EN: Welcome', - '_children' => [ - ], - ], - [ - 'uid' => 1200, - 'title' => 'EN: Features modified', - '_children' => [ [ - 'uid' => 1240, - 'title' => 'EN: Managing data', - '_children' => [], + 'uid' => 1100, + 'title' => 'EN: Welcome', + '_children' => [ + ], ], [ - 'uid' => 1230, - 'title' => 'EN: Managing content', + 'uid' => 1200, + 'title' => 'EN: Features modified', '_children' => [ + [ + 'uid' => 1240, + 'title' => 'EN: Managing data', + '_children' => [], + ], + [ + 'uid' => 1230, + 'title' => 'EN: Managing content', + '_children' => [ + ], + ], ], ], - ], - ], - [ - 'uid' => 1500, - 'title' => 'Internal', - '_children' => [ [ - 'uid' => 1520, - 'title' => 'Forecasts', - '_children' => [], + 'uid' => 1500, + 'title' => 'Internal', + '_children' => [ + [ + 'uid' => 1520, + 'title' => 'Forecasts', + '_children' => [], + ], + [ + 'uid' => 1530, + 'title' => 'Reports', + '_children' => [ + ], + ], + ], ], [ - 'uid' => 1530, - 'title' => 'Reports', + 'uid' => 1700, + 'title' => 'Announcements & News', '_children' => [ + [ + // page moved in workspace 1 + // from pid 8110 to pid 1700 (visible now) + 'uid' => 811000, + 'title' => 'France', + '_children' => [], + ], + [ + // page with sub-pages moved in workspace 1 + // from pid 1510 (missing permissions) to pid 1700 (visible now) + 'uid' => 1511, + 'title' => 'Products', + '_children' => [], + ], ], ], - ], - ], - [ - 'uid' => 1700, - 'title' => 'Announcements & News', - '_children' => [ [ - // page moved in workspace 1 - // from pid 8110 to pid 1700 (visible now) - 'uid' => 811000, - 'title' => 'France', - '_children' => [], + 'uid' => 404, + 'title' => 'Page not found', + '_children' => [ + ], ], [ - // page with sub-pages moved in workspace 1 - // from pid 1510 (missing permissions) to pid 1700 (visible now) - 'uid' => 1511, - 'title' => 'Products', - '_children' => [], + 'uid' => 1930, + 'title' => 'Our Blog', + '_children' => [ + ], + ], + [ + 'uid' => 1990, + 'title' => 'Storage', + '_children' => [ + ], ], ], ], [ - 'uid' => 404, - 'title' => 'Page not found', - '_children' => [ - ], - ], - [ - 'uid' => 1930, - 'title' => 'Our Blog', - '_children' => [ - ], - ], - [ - 'uid' => 1990, - 'title' => 'Storage', + 'uid' => 8110, + 'title' => 'Europe', '_children' => [ ], ], ], ], - [ - 'uid' => 8110, - 'title' => 'Europe', - '_children' => [ - ], - ], ]; self::assertEquals($expected, $actual); } @@ -502,14 +520,20 @@ class TreeControllerTest extends FunctionalTestCase $expected = [ [ - 'uid' => 1000, - 'title' => 'ACME Inc', - '_children' => $expectedChildren, - ], - [ - 'uid' => 8110, - 'title' => 'Europe', - '_children' => [ + 'uid' => 0, + 'title' => 'New TYPO3 site', + '_children' =>[ + [ + 'uid' => 1000, + 'title' => 'ACME Inc', + '_children' => $expectedChildren, + ], + [ + 'uid' => 8110, + 'title' => 'Europe', + '_children' => [ + ], + ], ], ], ]; -- GitLab