diff --git a/typo3/sysext/backend/Classes/Controller/BackendController.php b/typo3/sysext/backend/Classes/Controller/BackendController.php index f7c3472b5fcf63dc00a3e1201c43ec9a9d0b33a6..2d51a0066bbfead2aee5ec2c3d8b9933ecd77096 100644 --- a/typo3/sysext/backend/Classes/Controller/BackendController.php +++ b/typo3/sysext/backend/Classes/Controller/BackendController.php @@ -17,8 +17,10 @@ declare(strict_types=1); namespace TYPO3\CMS\Backend\Controller; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Backend\Controller\Event\AfterBackendPageRenderEvent; use TYPO3\CMS\Backend\Module\MenuModule; use TYPO3\CMS\Backend\Module\ModuleInterface; use TYPO3\CMS\Backend\Module\ModuleProvider; @@ -50,8 +52,6 @@ class BackendController { use PageRendererBackendSetupTrait; - protected string $css = ''; - /** * @var ModuleInterface[] */ @@ -65,9 +65,8 @@ class BackendController protected readonly ToolbarItemsRegistry $toolbarItemsRegistry, protected readonly ExtensionConfiguration $extensionConfiguration, protected readonly BackendViewFactory $viewFactory, + protected readonly EventDispatcherInterface $eventDispatcher, ) { - // @todo: This hook is essentially useless. - $this->executeHook('constructPostProcess'); $this->modules = $this->moduleProvider->getModulesForModuleMenu($this->getBackendUser()); } @@ -79,8 +78,6 @@ class BackendController $backendUser = $this->getBackendUser(); $pageRenderer = $this->pageRenderer; - $this->executeHook('renderPreProcess'); - $this->setUpBasicPageRendererForBackend($pageRenderer, $this->extensionConfiguration, $request, $this->getLanguageService()); $javaScriptRenderer = $pageRenderer->getJavaScriptRenderer(); @@ -131,7 +128,6 @@ class BackendController $dateFormat = ['DD-MM-Y', 'HH:mm DD-MM-Y']; // Needed for FormEngine manipulation (date picker) $pageRenderer->addInlineSetting('DateTimePicker', 'DateFormat', $dateFormat); - $pageRenderer->addCssInlineBlock('BackendInlineCSS', $this->css); $typo3Version = 'TYPO3 CMS ' . $this->typo3Version->getVersion(); $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ' [' . $typo3Version . ']' : $typo3Version; $pageRenderer->setTitle($title); @@ -148,7 +144,7 @@ class BackendController 'sitenameFirstInBackendTitle' => ($backendUser->uc['backendTitleFormat'] ?? '') === 'sitenameFirst', ]); $content = $view->render('Backend/Main'); - $this->executeHook('renderPostProcess', ['content' => &$content]); + $content = $this->eventDispatcher->dispatch(new AfterBackendPageRenderEvent($content, $view))->getContent(); $bodyTag = '<body class="scaffold t3js-scaffold' . (!$moduleMenuCollapsed && $this->modules ? ' scaffold-modulemenu-expanded' : '') . '">'; $pageRenderer->addBodyContent($bodyTag . $content); return $pageRenderer->renderResponse(); @@ -177,16 +173,6 @@ class BackendController return new JsonResponse(['topbar' => $view->render('Backend/Topbar')]); } - /** - * Adds a css snippet to the backend. This method is old and its purpose - * seems to be that hooks (see executeHook()) can add css? - * @todo: Candidate for deprecation / removal. - */ - public function addCss(string $css): void - { - $this->css .= $css; - } - /** * Renders the topbar, containing the backend logo, sitename etc. */ @@ -365,22 +351,6 @@ class BackendController return $modules; } - /** - * Executes defined hooks functions for the given identifier. - * - * These hook identifiers are valid: - * + constructPostProcess - * + renderPreProcess - * + renderPostProcess - */ - protected function executeHook(string $identifier, array $hookConfiguration = []): void - { - $options = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']; - foreach ($options[$identifier] ?? [] as $hookFunction) { - GeneralUtility::callUserFunction($hookFunction, $hookConfiguration, $this); - } - } - protected function getCollapseStateOfMenu(): bool { $backendUser = $this->getBackendUser(); diff --git a/typo3/sysext/backend/Classes/Controller/Event/AfterBackendPageRenderEvent.php b/typo3/sysext/backend/Classes/Controller/Event/AfterBackendPageRenderEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..c4c3aa531a90df3687ce21be17135b11ee044a10 --- /dev/null +++ b/typo3/sysext/backend/Classes/Controller/Event/AfterBackendPageRenderEvent.php @@ -0,0 +1,48 @@ +<?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\Controller\Event; + +use TYPO3\CMS\Core\View\ViewInterface; + +/** + * This event triggers after a page has been rendered. + * + * Listeners may update the page content string with a modified + * version if appropriate. + */ +final class AfterBackendPageRenderEvent +{ + public function __construct(private string $content, private readonly ViewInterface $view) + { + } + + public function getContent(): string + { + return $this->content; + } + + public function setContent(string $content): void + { + $this->content = $content; + } + + public function getView(): ViewInterface + { + return $this->view; + } +} diff --git a/typo3/sysext/backend/Tests/Functional/Controller/BackendControllerTest.php b/typo3/sysext/backend/Tests/Functional/Controller/BackendControllerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2bf63e6622b0db91838a81b21767cd6007116ddf --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Controller/BackendControllerTest.php @@ -0,0 +1,72 @@ +<?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\Tests\Functional\Controller; + +use Symfony\Component\DependencyInjection\Container; +use TYPO3\CMS\Backend\Controller\BackendController; +use TYPO3\CMS\Backend\Controller\Event\AfterBackendPageRenderEvent; +use TYPO3\CMS\Backend\Routing\Route; +use TYPO3\CMS\Core\Core\Bootstrap; +use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; +use TYPO3\CMS\Core\EventDispatcher\ListenerProvider; +use TYPO3\CMS\Core\Http\ServerRequest; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class BackendControllerTest extends FunctionalTestCase +{ + public function setUp(): void + { + parent::setUp(); + $this->setUpBackendUserFromFixture(1); + Bootstrap::initializeLanguageObject(); + } + + /** + * @test + */ + public function backendPageRenderEventIsTriggered(): void + { + /** @var Container $container */ + $container = $this->getContainer(); + + $state = [ + 'after-backend-page-render-listener' => null, + ]; + + // Dummy listeners that just record that the event existed. + $container->set( + 'after-backend-page-render-listener', + static function (AfterBackendPageRenderEvent $event) use (&$state) { + $state['after-backend-page-render-listener'] = $event; + } + ); + + $eventListener = GeneralUtility::makeInstance(ListenerProvider::class); + $eventListener->addListener(AfterBackendPageRenderEvent::class, 'after-backend-page-render-listener'); + + $request = (new ServerRequest()) + ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE) + ->withAttribute('route', new Route('/main', ['packageName' => 'typo3/cms-backend', '_identifier' => 'main'])); + + $subject = $this->get(BackendController::class); + $subject->mainAction($request); + + self::assertInstanceOf(AfterBackendPageRenderEvent::class, $state['after-backend-page-render-listener']); + } +} diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-97451-RemoveBackendControllerPageHooks.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-97451-RemoveBackendControllerPageHooks.rst new file mode 100644 index 0000000000000000000000000000000000000000..9459245d7ae881425ad5d08bcd4ae956e2af74fa --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-97451-RemoveBackendControllerPageHooks.rst @@ -0,0 +1,40 @@ +.. include:: /Includes.rst.txt + +======================================================= +Breaking: #97451 - Removed BackendController page hooks +======================================================= + +See :issue:`97451` + +Description +=========== + +The hooks :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['constructorPostProcess']`, +:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['renderPreProcess']`, and +:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['renderPostProcess']` have +been removed in favor of a new PSR-14 Event :php:`\TYPO3\CMS\Backend\Controller\Event\AfterBackendPageRenderEvent`. + +Additionally, the :php:`BackendController->addCss()` method has been removed without replacement, +as it is no longer used. + +Impact +====== + +Any hook implementation registered is not executed anymore in +TYPO3 v12.0+. The extension scanner will report possible usages. + +Affected Installations +====================== + +All TYPO3 installations using this hook in custom extension code. + +Migration +========= + +The hooks are removed without deprecation in order to allow extensions +to work with TYPO3 v11 (using the hook) and v12+ (using the new Event) +when implementing the Event as well without any further deprecations. +Use the :doc:`PSR-14 Event <../12.0/Feature-97451-PSR-14EventsForBackendPageController>` +as an improved replacement. + +.. index:: Backend, PHP-API, FullyScanned, ext:backend diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Feature-97451-PSR-14EventsForBackendPageController.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Feature-97451-PSR-14EventsForBackendPageController.rst new file mode 100644 index 0000000000000000000000000000000000000000..addd0e3cd1b2d8b005c4bd9a503d69f7d2dbea78 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.0/Feature-97451-PSR-14EventsForBackendPageController.rst @@ -0,0 +1,53 @@ +.. include:: /Includes.rst.txt + +================================================================== +Feature: #97451 - PSR-14 Events for modifying backend page content +================================================================== + +See :issue:`97451` + +Description +=========== +A new PSR-14 Event :php:`\TYPO3\CMS\Backend\Controller\Event\AfterBackendPageRenderEvent` has +been introduced which serves as a direct replacement for the now removed +:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['constructorPostProcess']`, +:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['renderPreProcess']`, and +:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['renderPostProcess']` +:doc:`hooks <../12.0/Breaking-97451-RemoveBackendControllerPageHooks>`. + +The new Event triggers after the page is rendered and includes +the rendered page body. Listeners may overwrite the page string if desired. + +Example +======= + +Registration of the Event in your extension's :file:`Services.yaml`: + +.. code-block:: yaml + + MyVendor\MyPackage\MyEventListener: + tags: + - name: event.listener + identifier: 'my-package/backend/after-backend-controller-render' + +The corresponding event listener class: + +.. code-block:: php + + use TYPO3\CMS\Backend\Controller\Event\AfterBackendPageRenderEvent; + + final class MyEventListener + { + public function __invoke(AfterBackendPageRenderEvent $event): void + { + $content = $event->getContent() . ' I was here'; + $event->setContent($content); + } + } + +Impact +====== + +It's now possible to modify the backend page using the new PSR-14 :php:`AfterBackendPageRenderEvent`. + +.. index:: Backend, PHP-API, ext:backend diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php index 7598197321deec04b29de543c18771a6286f94c0..7abd32237386fb2eb03bbde140d74a750a2e48be 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php @@ -784,4 +784,22 @@ return [ 'Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour.rst', ], ], + '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'typo3/backend.php\'][\'constructorPostProcess\']' => [ + 'restFiles' => [ + 'Breaking-97451-RemoveBackendControllerPageHooks.rst', + 'Feature-97451-PSR-14EventsForBackendPageController.rst', + ], + ], + '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'typo3/backend.php\'][\'renderPreProcess\']' => [ + 'restFiles' => [ + 'Breaking-97451-RemoveBackendControllerPageHooks.rst', + 'Feature-97451-PSR-14EventsForBackendPageController.rst', + ], + ], + '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'typo3/backend.php\'][\'renderPostProcess\']' => [ + 'restFiles' => [ + 'Breaking-97451-RemoveBackendControllerPageHooks.rst', + 'Feature-97451-PSR-14EventsForBackendPageController.rst', + ], + ], ]; diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php index d22e0374c2c2bb400825861733dec537aa1153aa..cbcb02cbd8806078769f68dce1585b36cdc4d668 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php @@ -5323,4 +5323,11 @@ return [ 'Deprecation-97531-ContextRelatedMethodsWithinTSFE.rst', ], ], + 'TYPO3\CMS\Backend\Controller\BackendController->addCss' => [ + 'numberOfMandatoryArguments' => 0, + 'maximumNumberOfArguments' => 0, + 'restFiles' => [ + 'Breaking-97451-RemoveBackendControllerPageHooks.rst', + ], + ], ];