Commit 4f5e778d authored by Benjamin Franzke's avatar Benjamin Franzke
Browse files

[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's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Jochen's avatarJochen <rothjochen@gmail.com>
Tested-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Jochen's avatarJochen <rothjochen@gmail.com>
Reviewed-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
parent 90cca745
......@@ -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
*
......
......@@ -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++;
......
......@@ -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,