Skip to content

Commit

Permalink
[BUGFIX] Limit amount of data fetched by the page tree
Browse files Browse the repository at this point in the history
Page tree will fetch just 2 levels of pages plus pages which
are expanded on the initial load.
Next levels are fetched on demand via Ajax when expanding the node.
Search work server side now (hit enter). To clear search, click on "x"
button.
If you select a page when filtering, it's kept selected after
removing the filter.

Releases: master, 10.4, 9.5
Resolves: #88943
Resolves: #88098
Resolves: #88259
Change-Id: Ie83839ce801c509f24c1e2c1dc516bce9599d55e
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62086
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Sybille Peters <sypets@gmx.de>
Tested-by: Dennis Prinse <dennis@dennisprinse.com>
Tested-by: Marcus Schwemer <ms@schwemer.de>
Tested-by: Uwe Trotzek <trotzek@citeq.de>
Tested-by: Richard Haeser <richard@maxserv.com>
Reviewed-by: Marcus Schwemer <ms@schwemer.de>
Reviewed-by: Richard Haeser <richard@maxserv.com>
  • Loading branch information
tmotyl authored and Richard Haeser committed Jul 23, 2020
1 parent 9dc190a commit fb61db4
Show file tree
Hide file tree
Showing 8 changed files with 703 additions and 132 deletions.
187 changes: 141 additions & 46 deletions typo3/sysext/backend/Classes/Controller/Page/TreeController.php
Expand Up @@ -102,13 +102,47 @@ class TreeController
*/
protected $iconFactory;

/**
* Number of tree levels which should be returned on the first page tree load
*
* @var int
*/
protected $levelsToFetch = 2;

/**
* When set to true all nodes returend by API will be expanded
* @var bool
*/
protected $expandAllNodes = false;

/**
* Constructor to set up common objects needed in various places.
*/
public function __construct()
{
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$this->useNavTitle = (bool)($this->getBackendUser()->getTSConfig()['options.']['pageTree.']['showNavTitle'] ?? false);
}

protected function initializeConfiguration()
{
$userTsConfig = $this->getBackendUser()->getTSConfig();
$this->hiddenRecords = GeneralUtility::intExplode(
',',
$userTsConfig['options.']['hideRecords.']['pages'] ?? '',
true
);
$this->backgroundColors = $userTsConfig['options.']['pageTree.']['backgroundColor.'] ?? [];
$this->addIdAsPrefix = (bool)($userTsConfig['options.']['pageTree.']['showPageIdWithTitle'] ?? false);
$this->addDomainName = (bool)($userTsConfig['options.']['pageTree.']['showDomainNameWithTitle'] ?? false);
$this->useNavTitle = (bool)($userTsConfig['options.']['pageTree.']['showNavTitle'] ?? false);
$this->showMountPathAboveMounts = (bool)($userTsConfig['options.']['pageTree.']['showPathAboveMounts'] ?? false);
$backendUserConfiguration = GeneralUtility::makeInstance(BackendUserConfiguration::class);
$this->expandedState = $backendUserConfiguration->get('BackendComponents.States.Pagetree');
if (is_object($this->expandedState) && is_object($this->expandedState->stateHash)) {
$this->expandedState = (array)$this->expandedState->stateHash;
} else {
$this->expandedState = $this->expandedState['stateHash'] ?: [];
}
}

/**
Expand Down Expand Up @@ -178,29 +212,51 @@ protected function getDokTypes(): array
*/
public function fetchDataAction(ServerRequestInterface $request): ResponseInterface
{
$userTsConfig = $this->getBackendUser()->getTSConfig();
$this->hiddenRecords = GeneralUtility::intExplode(',', $userTsConfig['options.']['hideRecords.']['pages'] ?? '', true);
$this->backgroundColors = $userTsConfig['options.']['pageTree.']['backgroundColor.'] ?? [];
$this->addIdAsPrefix = (bool)($userTsConfig['options.']['pageTree.']['showPageIdWithTitle'] ?? false);
$this->addDomainName = (bool)($userTsConfig['options.']['pageTree.']['showDomainNameWithTitle'] ?? false);
$this->showMountPathAboveMounts = (bool)($userTsConfig['options.']['pageTree.']['showPathAboveMounts'] ?? false);
$backendUserConfiguration = GeneralUtility::makeInstance(BackendUserConfiguration::class);
$this->expandedState = $backendUserConfiguration->get('BackendComponents.States.Pagetree');
if (is_object($this->expandedState) && is_object($this->expandedState->stateHash)) {
$this->expandedState = (array)$this->expandedState->stateHash;
} else {
$this->expandedState = $this->expandedState['stateHash'] ?: [];
}
$this->initializeConfiguration();

// Fetching a part of a pagetree
$items = [];
if (!empty($request->getQueryParams()['pid'])) {
$entryPoints = [(int)$request->getQueryParams()['pid']];
// Fetching a part of a page tree
$entryPoints = $this->getAllEntryPointPageTrees((int)$request->getQueryParams()['pid']);
$mountPid = (int)($request->getQueryParams()['mount'] ?? 0);
$parentDepth = (int)($request->getQueryParams()['pidDepth'] ?? 0);
$this->levelsToFetch = $parentDepth + $this->levelsToFetch;
foreach ($entryPoints as $page) {
$items = array_merge($items, $this->pagesToFlatArray($page, $mountPid, $parentDepth));
}
} else {
$entryPoints = $this->getAllEntryPointPageTrees();
foreach ($entryPoints as $page) {
$items = array_merge($items, $this->pagesToFlatArray($page, (int)$page['uid']));
}
}

return new JsonResponse($items);
}

/**
* Returns JSON representing page tree filtered by keyword
*
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
public function filterDataAction(ServerRequestInterface $request): ResponseInterface
{
$searchQuery = $request->getQueryParams()['q'] ?? '';
if (trim($searchQuery) === '') {
return new JsonResponse([]);
}

$this->initializeConfiguration();
$this->expandAllNodes = true;

$items = [];
$entryPoints = $this->getAllEntryPointPageTrees(0, $searchQuery);

foreach ($entryPoints as $page) {
$items = array_merge($items, $this->pagesToFlatArray($page, (int)$page['uid']));
if (!empty($page)) {
$items = array_merge($items, $this->pagesToFlatArray($page, (int)$page['uid']));
}
}

return new JsonResponse($items);
Expand Down Expand Up @@ -254,7 +310,10 @@ protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0

$stopPageTree = !empty($page['php_tree_stop']) && $depth > 0;
$identifier = $entryPoint . '_' . $pageId;
$expanded = !empty($page['expanded']) || (isset($this->expandedState[$identifier]) && $this->expandedState[$identifier]);
$expanded = !empty($page['expanded'])
|| (isset($this->expandedState[$identifier]) && $this->expandedState[$identifier])
|| $this->expandAllNodes;

$backgroundColor = !empty($this->backgroundColors[$pageId]) ? $this->backgroundColors[$pageId] : ($inheritedData['backgroundColor'] ?? '');

$suffix = '';
Expand Down Expand Up @@ -309,8 +368,11 @@ protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0
&& $backendUser->checkLanguageAccess(0)
];

if (!empty($page['_children'])) {
if (!empty($page['_children']) || $this->getPageTreeRepository()->hasChildren($pageId)) {
$item['hasChildren'] = true;
if ($depth >= $this->levelsToFetch) {
$page = $this->getPageTreeRepository()->getTreeLevels($page, 1);
}
}
if (!empty($prefix)) {
$item['prefix'] = htmlspecialchars($prefix);
Expand All @@ -324,7 +386,7 @@ protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0
if ($icon->getOverlayIcon()) {
$item['overlayIcon'] = $icon->getOverlayIcon()->getIdentifier();
}
if ($expanded) {
if ($expanded && is_array($page['_children']) && !empty($page['_children'])) {
$item['expanded'] = $expanded;
}
if ($backgroundColor) {
Expand All @@ -346,9 +408,10 @@ protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0
}

$items[] = $item;
if (!$stopPageTree && is_array($page['_children'])) {
if (!$stopPageTree && is_array($page['_children']) && !empty($page['_children']) && ($depth < $this->levelsToFetch || $expanded)) {
$siblingsCount = count($page['_children']);
$siblingsPosition = 0;
$items[key($items)]['loaded'] = true;
foreach ($page['_children'] as $child) {
$child['siblingsCount'] = $siblingsCount;
$child['siblingsPosition'] = ++$siblingsPosition;
Expand All @@ -358,12 +421,7 @@ protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0
return $items;
}

/**
* Fetches all entry points for the page tree that the user is allowed to see
*
* @return array
*/
protected function getAllEntryPointPageTrees(): array
protected function getPageTreeRepository(): PageTreeRepository
{
$backendUser = $this->getBackendUser();
$userTsConfig = $backendUser->getTSConfig();
Expand All @@ -375,57 +433,94 @@ protected function getAllEntryPointPageTrees(): array
}
$additionalQueryRestrictions[] = GeneralUtility::makeInstance(PagePermissionRestriction::class, GeneralUtility::makeInstance(Context::class)->getAspect('backend.user'), Permission::PAGE_SHOW);

$repository = GeneralUtility::makeInstance(
return GeneralUtility::makeInstance(
PageTreeRepository::class,
(int)$backendUser->workspace,
[],
$additionalQueryRestrictions
);
}

$entryPoints = (int)($backendUser->uc['pageTree_temporaryMountPoint'] ?? 0);
if ($entryPoints > 0) {
$entryPoints = [$entryPoints];
/**
* Fetches all pages for all tree entry points the user is allowed to see
*
* @param int $startPid
* @param string $query The search query can either be a string to be found in the title or the nav_title of a page or the uid of a page.
* @return array
*/
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];
} else {
$entryPoints = array_map('intval', $backendUser->returnWebmounts());
$entryPoints = array_unique($entryPoints);
if (empty($entryPoints)) {
//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
$entryPoints = [0];
$entryPointIds = [0];
}
}
if (empty($entryPoints)) {
if (empty($entryPointIds)) {
return [];
}
$repository = $this->getPageTreeRepository();

if ($query !== '') {
$this->levelsToFetch = 999;
$repository->fetchFilteredTree($query);
}

foreach ($entryPoints as $k => &$entryPoint) {
if (in_array($entryPoint, $this->hiddenRecords, true)) {
unset($entryPoints[$k]);
$entryPointRecords = [];
foreach ($entryPointIds as $k => $entryPointId) {
if (in_array($entryPointId, $this->hiddenRecords, true)) {
continue;
}

if (!empty($this->backgroundColors) && is_array($this->backgroundColors)) {
try {
$entryPointRootLine = GeneralUtility::makeInstance(RootlineUtility::class, $entryPoint)->get();
$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[$entryPoint])) {
$this->backgroundColors[$entryPoint] = $this->backgroundColors[$parentUid];
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'
];
} else {
$permClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
$entryPointRecord = BackendUtility::getRecord('pages', $entryPointId, '*', $permClause);

if ($entryPointRecord !== null && !$this->getBackendUser()->isInWebMount($entryPointId)) {
$entryPointRecord = null;
}
}
if ($entryPointRecord) {
if ($query === '') {
$entryPointRecord = $repository->getTreeLevels($entryPointRecord, $this->levelsToFetch);
} else {
$entryPointRecord = $repository->getTree($entryPointRecord['uid'], null, $entryPointIds);
}
}

$entryPoint = $repository->getTree($entryPoint, null, $entryPoints);
if (!is_array($entryPoint)) {
unset($entryPoints[$k]);
if (is_array($entryPointRecord) && !empty($entryPointRecord)) {
$entryPointRecords[$k] = $entryPointRecord;
}
}

return $entryPoints;
return $entryPointRecords;
}

/**
Expand Down

0 comments on commit fb61db4

Please sign in to comment.