From 59760c7ae379d5dc00c45890b8ca04698bb1a4a5 Mon Sep 17 00:00:00 2001 From: Benni Mack <benni@typo3.org> Date: Wed, 4 Sep 2024 10:47:45 +0200 Subject: [PATCH] [FEATURE] Add Event to modify the results of the PageTreeRepository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new PSR-14 Event "AfterRawPageRowPreparedEvent" is added to allow to filter out, or remove or re-sort items / children for the page tree in the TYPO3 Backend. This is typically the case for e.g. sorting subpages within a page by e.g. the creation date (news as pages). Resolves: #104832 Releases: main Change-Id: Id9597897e06bba94957e50cbcfee69d4c2bb960c Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/85864 Reviewed-by: Jochen Roth <rothjochen@gmail.com> Tested-by: Jochen Roth <rothjochen@gmail.com> Tested-by: core-ci <typo3@b13.com> Reviewed-by: Stefan Bürk <stefan@buerk.tech> Reviewed-by: Benni Mack <benni@typo3.org> Tested-by: Stefan Bürk <stefan@buerk.tech> Tested-by: Benni Mack <benni@typo3.org> --- .../Controller/Page/TreeController.php | 73 ++++------- .../AfterRawPageRowPreparedEvent.php | 45 +++++++ .../Tree/Repository/PageTreeRepository.php | 10 +- .../Controller/Page/TreeControllerTest.php | 118 ++++++++++++++++-- .../Repository/PageTreeRepositoryTest.php | 54 ++++++++ ...tToAlterTheResultsOfPageTreeRepository.rst | 56 +++++++++ 6 files changed, 291 insertions(+), 65 deletions(-) create mode 100644 typo3/sysext/backend/Classes/Tree/Repository/AfterRawPageRowPreparedEvent.php create mode 100644 typo3/sysext/core/Documentation/Changelog/13.3/Feature-104832-PSR-14EventToAlterTheResultsOfPageTreeRepository.rst diff --git a/typo3/sysext/backend/Classes/Controller/Page/TreeController.php b/typo3/sysext/backend/Classes/Controller/Page/TreeController.php index 8fae2694c283..90c1e253260c 100644 --- a/typo3/sysext/backend/Classes/Controller/Page/TreeController.php +++ b/typo3/sysext/backend/Classes/Controller/Page/TreeController.php @@ -20,6 +20,7 @@ namespace TYPO3\CMS\Backend\Controller\Page; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Backend\Attribute\AsController; use TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent; use TYPO3\CMS\Backend\Dto\Tree\Label\Label; use TYPO3\CMS\Backend\Dto\Tree\PageTreeItem; @@ -44,42 +45,33 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; * Controller providing data to the page tree * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. */ +#[AsController] class TreeController { /** * Option to use the nav_title field for outputting in the tree items, set via userTS. - * - * @var bool */ - protected $useNavTitle = false; + protected bool $useNavTitle = false; /** * Option to prefix the page ID when outputting the tree items, set via userTS. - * - * @var bool */ - protected $addIdAsPrefix = false; + protected bool $addIdAsPrefix = false; /** * Option to prefix the domain name of sys_domains when outputting the tree items, set via userTS. - * - * @var bool */ - protected $addDomainName = false; + protected bool $addDomainName = false; /** * Option to add the rootline path above each mount point, set via userTS. - * - * @var bool */ - protected $showMountPathAboveMounts = false; + protected bool $showMountPathAboveMounts = false; /** * A list of pages not to be shown. - * - * @var array */ - protected $hiddenRecords = []; + protected array $hiddenRecords = []; /** * An array of background colors for a branch in the tree, set via userTS. @@ -91,37 +83,23 @@ class TreeController /** * An array of labels for a branch in the tree, set via userTS. - * - * @var array */ protected array $labels = []; /** * Contains the state of all items that are expanded. - * - * @var array */ - protected $expandedState = []; - - /** - * Instance of the icon factory, to be used for generating the items. - * - * @var IconFactory - */ - protected $iconFactory; + protected array $expandedState = []; /** * Number of tree levels which should be returned on the first page tree load - * - * @var int */ - protected $levelsToFetch = 2; + protected int $levelsToFetch = 2; /** * When set to true all nodes returend by API will be expanded - * @var bool */ - protected $expandAllNodes = false; + protected bool $expandAllNodes = false; /** * Used in the record link picker to limit the page tree only to a specific list @@ -129,20 +107,16 @@ class TreeController */ protected array $alternativeEntryPoints = []; - protected UriBuilder $uriBuilder; - protected PageTreeRepository $pageTreeRepository; protected bool $userHasAccessToModifyPagesAndToDefaultLanguage = false; - /** - * Constructor to set up common objects needed in various places. - */ - public function __construct() - { - $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class); - $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); - } + public function __construct( + protected readonly IconFactory $iconFactory, + protected readonly UriBuilder $uriBuilder, + protected readonly EventDispatcherInterface $eventDispatcher, + protected readonly SiteFinder $siteFinder, + ) {} protected function initializeConfiguration(ServerRequestInterface $request) { @@ -568,7 +542,7 @@ class TreeController $entryPointRecord = $this->pageTreeRepository->getTree($entryPointRecord['uid'], null, $entryPointIds); } - if (is_array($entryPointRecord) && !empty($entryPointRecord)) { + if ($entryPointRecord !== []) { $entryPointRecords[$k] = $entryPointRecord; } } @@ -582,16 +556,13 @@ class TreeController */ protected function getDomainNameForPage(int $pageId): string { - $domain = ''; - $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); try { - $site = $siteFinder->getSiteByRootPageId($pageId); - $domain = (string)$site->getBase(); - } catch (SiteNotFoundException $e) { + $site = $this->siteFinder->getSiteByRootPageId($pageId); + return (string)$site->getBase(); + } catch (SiteNotFoundException) { // No site found } - - return $domain; + return ''; } /** @@ -678,7 +649,7 @@ class TreeController mountPoint: (int)($item['mountPoint'] ?? 0), ); }, - GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch( + $this->eventDispatcher->dispatch( new AfterPageTreeItemsPreparedEvent($request, $items) )->getItems() ); diff --git a/typo3/sysext/backend/Classes/Tree/Repository/AfterRawPageRowPreparedEvent.php b/typo3/sysext/backend/Classes/Tree/Repository/AfterRawPageRowPreparedEvent.php new file mode 100644 index 000000000000..8403c6f36a6c --- /dev/null +++ b/typo3/sysext/backend/Classes/Tree/Repository/AfterRawPageRowPreparedEvent.php @@ -0,0 +1,45 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Backend\Tree\Repository; + +/** + * Listeners to this event will be able to modify a page with the special _children key, + * or completely change e.g. a title. + */ +final class AfterRawPageRowPreparedEvent +{ + public function __construct( + private array $rawPage, + private readonly int $workspaceId + ) {} + + public function getRawPage(): array + { + return $this->rawPage; + } + + public function setRawPage(array $rawPage): void + { + $this->rawPage = $rawPage; + } + + public function getWorkspaceId(): int + { + return $this->workspaceId; + } +} diff --git a/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php b/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php index e7f31db08314..c646209829a4 100644 --- a/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php +++ b/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php @@ -17,6 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Backend\Tree\Repository; +use Psr\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Database\Connection; @@ -67,6 +68,8 @@ class PageTreeRepository protected ?string $additionalWhereClause = null; + protected EventDispatcherInterface $eventDispatcher; + /** * @param int $workspaceId the workspace ID to be checked for. * @param array $additionalFieldsToQuery an array with more fields that should be accessed. @@ -111,6 +114,8 @@ class PageTreeRepository ], $additionalFieldsToQuery); $this->additionalQueryRestrictions = $additionalQueryRestrictions; + // @todo: use DI in the future + $this->eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class); $this->quotedFields = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('pages') ->quoteIdentifiersForSelect($this->fields); @@ -478,6 +483,9 @@ class PageTreeRepository { $page['_children'] = $groupedAndSortedPagesByPid[(int)$page['uid']] ?? []; ksort($page['_children']); + + $event = $this->eventDispatcher->dispatch(new AfterRawPageRowPreparedEvent($page, $this->currentWorkspace)); + $page = $event->getRawPage(); foreach ($page['_children'] as &$child) { $this->addChildrenToPage($child, $groupedAndSortedPagesByPid); } @@ -757,8 +765,6 @@ class PageTreeRepository /** * Group pages by parent page and sort pages based on sorting property - * - * @param array $groupedAndSortedPagesByPid */ protected function groupAndSortPages(array $pages, array $groupedAndSortedPagesByPid = []): array { diff --git a/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php b/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php index f50e4a4f1972..cd6e748fd605 100644 --- a/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php +++ b/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php @@ -19,9 +19,11 @@ namespace TYPO3\CMS\Backend\Tests\Functional\Controller\Page; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; +use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\DependencyInjection\Container; use TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent; use TYPO3\CMS\Backend\Controller\Page\TreeController; +use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Tests\Functional\Tree\Repository\Fixtures\Tree\NormalizeTreeTrait; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Context\Context; @@ -29,7 +31,9 @@ use TYPO3\CMS\Core\Context\WorkspaceAspect; use TYPO3\CMS\Core\EventDispatcher\ListenerProvider; use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Http\Uri; +use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Localization\LanguageServiceFactory; +use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait; use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory; use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter; @@ -61,7 +65,7 @@ final class TreeControllerTest extends FunctionalTestCase $factory = DataHandlerFactory::fromYamlFile($scenarioFile); $writer = DataHandlerWriter::withBackendUser($this->backendUser); $writer->invokeFactory($factory); - static::failIfArrayIsNotEmpty($writer->getErrors()); + self::failIfArrayIsNotEmpty($writer->getErrors()); }, function () { $this->backendUser = $this->setUpBackendUser(1); $GLOBALS['LANG'] = $this->get(LanguageServiceFactory::class)->createFromUserPreferences($this->backendUser); @@ -74,7 +78,16 @@ final class TreeControllerTest extends FunctionalTestCase #[Test] public function getAllEntryPointPageTrees(): void { - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees'); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -189,7 +202,16 @@ final class TreeControllerTest extends FunctionalTestCase public function getAllEntryPointPageTreesWithRootPageAsMountPoint(): void { $this->backendUser->setWebMounts([0, 7000]); - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees'); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -301,7 +323,16 @@ final class TreeControllerTest extends FunctionalTestCase #[Test] public function getAllEntryPointPageTreesWithSearch(): void { - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees', 0, 'Groups'); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -345,7 +376,16 @@ final class TreeControllerTest extends FunctionalTestCase #[Test] public function getSubtreeForAccessiblePage(): void { - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees', 1200); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -377,7 +417,16 @@ final class TreeControllerTest extends FunctionalTestCase #[Test] public function getSubtreeForNonAccessiblePage(): void { - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees', 1510); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -390,7 +439,16 @@ final class TreeControllerTest extends FunctionalTestCase #[Test] public function getSubtreeForPageOutsideMountPoint(): void { - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees', 7000); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -404,7 +462,16 @@ final class TreeControllerTest extends FunctionalTestCase public function getAllEntryPointPageTreesWithMountPointPreservesOrdering(): void { $this->backendUser->setWebmounts([1210, 1100]); - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees'); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -439,7 +506,16 @@ final class TreeControllerTest extends FunctionalTestCase $this->backendUser->workspace = 1; $context = $this->get(Context::class); $context->setAspect('workspace', new WorkspaceAspect(1)); - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees'); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -624,7 +700,16 @@ final class TreeControllerTest extends FunctionalTestCase $context = $this->get(Context::class); $context->setAspect('workspace', new WorkspaceAspect(1)); // the record was changed from live "Groups" to "Teams modified" in a workspace - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees', 0, $search); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -658,7 +743,16 @@ final class TreeControllerTest extends FunctionalTestCase $this->backendUser->workspace = 1; $context = $this->get(Context::class); $context->setAspect('workspace', new WorkspaceAspect(1)); - $subject = $this->getAccessibleMock(TreeController::class, null); + $subject = $this->getAccessibleMock( + TreeController::class, + null, + [ + $this->get(IconFactory::class), + $this->get(UriBuilder::class), + $this->get(EventDispatcherInterface::class), + $this->get(SiteFinder::class), + ] + ); $actual = $subject->_call('getAllEntryPointPageTrees', 1200); $keepProperties = array_flip(['uid', 'title', '_children']); $actual = $this->sortTreeArray($actual); @@ -711,7 +805,7 @@ final class TreeControllerTest extends FunctionalTestCase $request = new ServerRequest(new Uri('https://example.com')); - (new TreeController())->fetchDataAction($request); + $this->get(TreeController::class)->fetchDataAction($request); self::assertInstanceOf(AfterPageTreeItemsPreparedEvent::class, $afterPageTreeItemsPreparedEvent); self::assertEquals($request, $afterPageTreeItemsPreparedEvent->getRequest()); diff --git a/typo3/sysext/backend/Tests/Functional/Tree/Repository/PageTreeRepositoryTest.php b/typo3/sysext/backend/Tests/Functional/Tree/Repository/PageTreeRepositoryTest.php index 094df4450f13..755648c80087 100644 --- a/typo3/sysext/backend/Tests/Functional/Tree/Repository/PageTreeRepositoryTest.php +++ b/typo3/sysext/backend/Tests/Functional/Tree/Repository/PageTreeRepositoryTest.php @@ -19,8 +19,11 @@ namespace TYPO3\CMS\Backend\Tests\Functional\Tree\Repository; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; +use Symfony\Component\DependencyInjection\Container; use TYPO3\CMS\Backend\Tests\Functional\Tree\Repository\Fixtures\Tree\NormalizeTreeTrait; +use TYPO3\CMS\Backend\Tree\Repository\AfterRawPageRowPreparedEvent; use TYPO3\CMS\Backend\Tree\Repository\PageTreeRepository; +use TYPO3\CMS\Core\EventDispatcher\ListenerProvider; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; final class PageTreeRepositoryTest extends FunctionalTestCase @@ -547,4 +550,55 @@ final class PageTreeRepositoryTest extends FunctionalTestCase $actual = $this->normalizeTreeArray($actual, $keepProperties); self::assertEquals($expectedResult, $actual[0]); } + + #[Test] + public function afterRawPageRowPreparedEventIsCalled() + { + $afterRawPageRowPreparedEvent = []; + $expectedResult = [ + 'uid' => 1, + 'title' => 'Home', + '_children' => [ + [ + 'uid' => 2, + 'title' => 'Main Area', + '_children' => [ + [ + 'uid' => 21, + 'title' => 'Main Area Sub 2', + '_children' => [ + // event listener drops children uid 31 + ], + ], + ], + ], + ], + ]; + + /** @var Container $container */ + $container = $this->get('service_container'); + $container->set( + 'after-raw-page-row-prepared-listener', + static function (AfterRawPageRowPreparedEvent $event) use (&$afterRawPageRowPreparedEvent) { + $afterRawPageRowPreparedEvent[] = $event; + $rawPage = $event->getRawPage(); + if ($rawPage['uid'] === 21) { + $rawPage['_children'] = []; + $event->setRawPage($rawPage); + } + } + ); + + $eventListener = $container->get(ListenerProvider::class); + $eventListener->addListener(AfterRawPageRowPreparedEvent::class, 'after-raw-page-row-prepared-listener'); + + $pageTreeRepository = new PageTreeRepository(0); + $pageTreeRepository->fetchFilteredTree('-30,31', [1], ''); + $actual = $pageTreeRepository->getTree(1, null, [1]); + $actual = $this->sortTreeArray([$actual]); + $keepProperties = array_flip(['uid', 'title', '_children']); + $actual = $this->normalizeTreeArray($actual, $keepProperties); + self::assertEquals($expectedResult, $actual[0]); + self::assertCount(4, $afterRawPageRowPreparedEvent); + } } diff --git a/typo3/sysext/core/Documentation/Changelog/13.3/Feature-104832-PSR-14EventToAlterTheResultsOfPageTreeRepository.rst b/typo3/sysext/core/Documentation/Changelog/13.3/Feature-104832-PSR-14EventToAlterTheResultsOfPageTreeRepository.rst new file mode 100644 index 000000000000..6497a6cc9750 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/13.3/Feature-104832-PSR-14EventToAlterTheResultsOfPageTreeRepository.rst @@ -0,0 +1,56 @@ +.. include:: /Includes.rst.txt + +.. _feature-104832-1725537890: + +========================================================================== +Feature: #104832 - PSR-14 Event to alter the results of PageTreeRepository +========================================================================== + +See :issue:`104832` + +Description +=========== + +Up until TYPO3 v9, it was possible to alter the rendering of one of TYPO3's +superpowers — the page tree in the TYPO3 Backend User Interface. + +This was done via a "Hook", but was removed due to the migration towards a +SVG-based tree rendering. + +As the Page Tree Rendering has evolved, and the hook system has been replaced +in favor of PSR-14 Events, a new event :php:`TYPO3\CMS\Backend\Tree\Repository\AfterRawPageRowPreparedEvent` +has been introduced. + + +Example +======= + +The event listener class, using the PHP attribute :php:`#[AsEventListener]` for +registration, will rmoeve any children for displaying for the page with the +UID 123: + +.. code-block:: php + + use TYPO3\CMS\Core\Attribute\AsEventListener; + use TYPO3\CMS\Backend\Tree\Repository\AfterRawPageRowPreparedEvent; + + final class MyEventListener + { + #[AsEventListener] + public function __invoke(AfterRawPageRowPreparedEvent $event): void + { + $rawPage = $event->getRawPage(); + if ((int)$rawPage['uid'] === 123) { + $rawPage['_children'] = []; + $event->setRawPage($rawPage); + } + } + } + +Impact +====== + +Using the new PSR-14 event, it's now possible to modify the populated +:php:`page` properties or its children records. + +.. index:: Backend, PHP-API, ext:backend -- GitLab