From e8a8c2e1efbd3139ee9b84ae2fe44e83d483e525 Mon Sep 17 00:00:00 2001 From: Benni Mack <benni@typo3.org> Date: Tue, 5 Jul 2022 13:30:19 +0200 Subject: [PATCH] [!!!][FEATURE] Introduce PSR-14 events when storing cached content in frontend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change adds two new PSR-14 Events * \TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent * \TYPO3\CMS\Frontend\Event\AfterCachedPageIsPersistedEvent which replace $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached'] $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'] and $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache'] $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['insertPageIncache'] hooks. Those hooks were much important and powerful, receiving even more power by the new Events, which allow manipulating content stored in cache, or when a page was cached in the Frontend. Additionally, the TSFE->generatePage_postProcessing() method does now require a ServerRequestInterface as first paramter, to be able to pass the current request to the new events. Resolves: #97862 Releases: main Change-Id: Ibd97d22ec1c4a726a78164b234bf8b4ce41f9dca Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/75039 Tested-by: Oliver Bartsch <bo@cedev.de> Tested-by: core-ci <typo3@b13.com> Tested-by: Stefan Bürk <stefan@buerk.tech> Tested-by: Benni Mack <benni@typo3.org> Reviewed-by: Oliver Bartsch <bo@cedev.de> Reviewed-by: Stefan Bürk <stefan@buerk.tech> Reviewed-by: Nikita Hovratov <nikita.h@live.de> Reviewed-by: Benni Mack <benni@typo3.org> --- ...sRelatedToGeneratingPageContentRemoved.rst | 77 ++++++++++++++++++ ...rontendPageGenerationAndCacheBehaviour.rst | 78 +++++++++++++++++++ .../TypoScriptFrontendController.php | 76 +++++++----------- .../AfterCacheableContentIsGeneratedEvent.php | 67 ++++++++++++++++ .../Event/AfterCachedPageIsPersistedEvent.php | 69 ++++++++++++++++ .../frontend/Classes/Http/RequestHandler.php | 4 +- ...FrontendGenerationPageIndexingTrigger.php} | 68 ++++++++-------- .../Configuration/Services.yaml | 6 ++ typo3/sysext/indexed_search/ext_localconf.php | 2 - .../Php/ArrayDimensionMatcher.php | 24 ++++++ .../Php/MethodArgumentRequiredMatcher.php | 7 ++ 11 files changed, 394 insertions(+), 84 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/12.0/Breaking-97862-HooksRelatedToGeneratingPageContentRemoved.rst create mode 100644 typo3/sysext/core/Documentation/Changelog/12.0/Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour.rst create mode 100644 typo3/sysext/frontend/Classes/Event/AfterCacheableContentIsGeneratedEvent.php create mode 100644 typo3/sysext/frontend/Classes/Event/AfterCachedPageIsPersistedEvent.php rename typo3/sysext/indexed_search/Classes/{Hook/TypoScriptFrontendHook.php => EventListener/FrontendGenerationPageIndexingTrigger.php} (63%) diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-97862-HooksRelatedToGeneratingPageContentRemoved.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-97862-HooksRelatedToGeneratingPageContentRemoved.rst new file mode 100644 index 000000000000..bfa1601e8c9e --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-97862-HooksRelatedToGeneratingPageContentRemoved.rst @@ -0,0 +1,77 @@ +.. include:: /Includes.rst.txt + +.. _breaking-97862-1657195630: + +=================================================================== +Breaking: #97862 - Hooks related to generating page content removed +=================================================================== + +See :issue:`97862` + +Description +=========== + +The existing TYPO3 hooks in the process of generating a TYPO3 Frontend page + +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached']` +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all']` +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache']` +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['insertPageIncache']` + +have been removed. These hooks have been used to execute custom PHP code after +a page is generated in the TYPO3 frontend and ready to be stored in cache. + +Due to the removal of the hooks and the introduction of the new PSR-14 Events +has the method signature of :php:`TypoScriptFrontendController->generatePage_postProcessing()` +been changed. The method now requires a :php:`ServerRequestInterface` as first +argument. + +Impact +====== + +Extension code that hooks into these places will not be executed anymore in +TYPO3 v12+. + +Extension code calling :php:`TypoScriptFrontendController->generatePage_postProcessing()` +without providing a :php:`ServerRequestInterface` as first argument +will trigger a PHP `ArgumentCountError`. + +Affected installations +====================== + +TYPO3 installations with custom extensions using these hooks such as static file +generation or modifying the page content cache, which is highly likely in +third-party extensions. The extension scanner will detect usages as +strong match. + +Extensions, manually calling :php:`TypoScriptFrontendController->generatePage_postProcessing()` +without providing a :php:`ServerRequestInterface` as first argument. The +extension scanner will detect usages as weak match. + +Migration +========= + +Use one of the two newly introduced :doc:`PSR-14 events <../12.0/Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour>`: + +* :php:`TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent` +* :php:`TYPO3\CMS\Frontend\Event\AfterCachedPageIsPersistedEvent` + +Extensions using the hooks can be made compatible with TYPO3 v11 and TYPO3 v12 +by registering a PSR-14-based Event Listener while keeping the legacy hook +in place. + +The :php:`AfterCacheableContentIsGeneratedEvent` acts as a replacement for + +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached']` +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all']` +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache']` + +whereas the :php:`AfterCachedPageIsPersistedEvent` is the replacement for + +:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['insertPageIncache']`. + +Provide a :php:`ServerRequestInterface` as first argument when calling +:php:`TypoScriptFrontendController->generatePage_postProcessing()` in custom +extension code. + +.. index:: Frontend, PHP-API, FullyScanned, ext:frontend diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour.rst new file mode 100644 index 000000000000..b53aee9af459 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.0/Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour.rst @@ -0,0 +1,78 @@ +.. include:: /Includes.rst.txt + +.. _feature-97862-1657195761: + +================================================================================================= +Feature: #97862 - New PSR-14 events for manipulating frontend page generation and cache behaviour +================================================================================================= + +See :issue:`97862` + +Description +=========== + +Two new PSR-14 Events have been added: + +* :php:`TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent` +* :php:`TYPO3\CMS\Frontend\Event\AfterCachedPageIsPersistedEvent` + +They are added in favor of the :doc:`removed hooks <../12.0/Breaking-97862-HooksRelatedToGeneratingPageContentRemoved>`: + +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached']` +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all']` +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache']` +* :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['insertPageIncache']` + +Both events are called when the content of a page has been +generated in the TYPO3 Frontend. + +Example +======= + +Registration of the `AfterCacheableContentIsGeneratedEvent` in your extensions' :file:`Services.yaml`: + +.. code-block:: yaml + + MyVendor\MyPackage\Backend\MyEventListener: + tags: + - name: event.listener + identifier: 'my-package/content-modifier' + +The corresponding event listener class: + +.. code-block:: php + + use TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent; + + class MyEventListener { + + public function __invoke(AfterCacheableContentIsGeneratedEvent $event): void + { + // Only do this when caching is enabled + if (!$event->isCachingEnabled()) { + return; + } + $event->getController()->content = str_replace('foo', 'bar', $event->getController()->content); + } + } + +Impact +====== + +The Event `AfterCacheableContentIsGeneratedEvent` can be used +to decide if a page should be stored in cache and is executed right after +all cacheable content is generated. It can also be used to manipulate +the content before it is stored in TYPO3's page cache. The Event is used +in indexed search to index cacheable content. + +The :php:`AfterCacheableContentIsGeneratedEvent` contains the +information if a generated page is able to store in cache via the +:php:`$event->isCachingEnabled()` method. This can be used to +differentiate between the previous hooks `contentPostProc-cached` and +`contentPostProc-all` (do something regardless if caching is enabled or not). + +The :php:`AfterCachedPageIsPersistedEvent` is commonly used to +generate a static file cache. This event is only called if the +page was actually stored in TYPO3's page cache. + +.. index:: Frontend, PHP-API, ext:frontend diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php index 6f5219a26ed9..9c38f617b379 100644 --- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php +++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php @@ -68,6 +68,8 @@ use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication; use TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator; use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent; +use TYPO3\CMS\Frontend\Event\AfterCachedPageIsPersistedEvent; use TYPO3\CMS\Frontend\Event\AfterPageAndLanguageIsResolvedEvent; use TYPO3\CMS\Frontend\Event\AfterPageWithRootLineIsResolvedEvent; use TYPO3\CMS\Frontend\Event\BeforePageIsResolvedEvent; @@ -1629,42 +1631,16 @@ class TypoScriptFrontendController implements LoggerAwareInterface } /** - * Set cache content to $this->content - */ - protected function realPageCacheContent() - { - // seconds until a cached page is too old - $cacheTimeout = $this->get_cache_timeout(); - $timeOutTime = $GLOBALS['EXEC_TIME'] + $cacheTimeout; - $usePageCache = true; - // Hook for deciding whether page cache should be written to the cache backend or not - // NOTE: as hooks are called in a loop, the last hook will have the final word (however each - // hook receives the current status of the $usePageCache flag) - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache'] ?? [] as $className) { - $usePageCache = GeneralUtility::makeInstance($className)->usePageCache($this, $usePageCache); - } - // Write the page to cache, if necessary - if ($usePageCache) { - $this->setPageCacheContent($this->content, $this->config, $timeOutTime); - } - // Hook for cache post processing (eg. writing static files!) - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['insertPageIncache'] ?? [] as $className) { - GeneralUtility::makeInstance($className)->insertPageIncache($this, $timeOutTime); - } - } - - /** - * Sets cache content; Inserts the content string into the cache_pages cache. + * Sets cache content; Inserts the content string into the pages cache. * * @param string $content The content to store in the HTML field of the cache table - * @param mixed $data The additional cache_data array, fx. $this->config + * @param array $data The additional cache_data array, fx. $this->config * @param int $expirationTstamp Expiration timestamp - * @see realPageCacheContent() + * @see populatePageDataFromCache() */ - protected function setPageCacheContent($content, $data, $expirationTstamp) + protected function setPageCacheContent(string $content, array $data, int $expirationTstamp): array { $cacheData = [ - 'identifier' => $this->newHash, 'page_id' => $this->id, 'content' => $content, 'cache_data' => $data, @@ -1672,7 +1648,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface 'tstamp' => $GLOBALS['EXEC_TIME'], ]; $this->cacheExpires = $expirationTstamp; - $this->pageCacheTags[] = 'pageId_' . $cacheData['page_id']; + $this->pageCacheTags[] = 'pageId_' . $this->id; // Respect the page cache when content of pid is shown if ($this->id !== $this->contentPid) { $this->pageCacheTags[] = 'pageId_' . $this->contentPid; @@ -1681,9 +1657,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface $tags = GeneralUtility::trimExplode(',', $this->page['cache_tags'], true); $this->pageCacheTags = array_merge($this->pageCacheTags, $tags); } + $this->pageCacheTags = array_unique($this->pageCacheTags); // Add the cache themselves as well, because they are fetched by getPageCacheTags() $cacheData['cacheTags'] = $this->pageCacheTags; $this->pageCache->set($this->newHash, $cacheData, $this->pageCacheTags, $expirationTstamp - $GLOBALS['EXEC_TIME']); + return $cacheData; } /** @@ -1878,30 +1856,30 @@ class TypoScriptFrontendController implements LoggerAwareInterface * * This includes caching the page, indexing the page (if configured) and setting sysLastChanged */ - public function generatePage_postProcessing() + public function generatePage_postProcessing(ServerRequestInterface $request) { $this->setAbsRefPrefix(); - // This is to ensure, that the page is NOT cached if the no_cache parameter was set before the page was generated. This is a safety precaution, as it could have been unset by some script. + // This is to ensure, that the page is NOT cached if the no_cache parameter was set before the page was generated. + // This is a safety precaution, as it could have been unset by some script. if ($this->no_cacheBeforePageGen) { $this->set_no_cache('no_cache has been set before the page was generated - safety check', true); } - // Hook for post-processing of page content cached/non-cached: - $_params = ['pObj' => &$this]; - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'] ?? [] as $_funcRef) { - GeneralUtility::callUserFunction($_funcRef, $_params, $this); - } - // Processing if caching is enabled: - if (!$this->no_cache) { - // Hook for post-processing of page content before being cached: - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached'] ?? [] as $_funcRef) { - GeneralUtility::callUserFunction($_funcRef, $_params, $this); - } - } - // Storing for cache: - if (!$this->no_cache) { - $this->realPageCacheContent(); + $eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class); + $event = new AfterCacheableContentIsGeneratedEvent($request, $this, $this->newHash, !$this->no_cache); + $event = $eventDispatcher->dispatch($event); + + // Processing if caching is enabled + if ($event->isCachingEnabled()) { + // Seconds until a cached page is too old + $cacheTimeout = $this->get_cache_timeout(); + $timeOutTime = $GLOBALS['EXEC_TIME'] + $cacheTimeout; + // Write the page to cache + $cachedInformation = $this->setPageCacheContent($this->content, $this->config, $timeOutTime); + + // Event for cache post processing (eg. writing static files) + $event = new AfterCachedPageIsPersistedEvent($request, $this, $this->newHash, $cachedInformation, $cacheTimeout); + $eventDispatcher->dispatch($event); } - // Sets sys-last-change: $this->setSysLastChanged(); } diff --git a/typo3/sysext/frontend/Classes/Event/AfterCacheableContentIsGeneratedEvent.php b/typo3/sysext/frontend/Classes/Event/AfterCacheableContentIsGeneratedEvent.php new file mode 100644 index 000000000000..8b8fde4f8a36 --- /dev/null +++ b/typo3/sysext/frontend/Classes/Event/AfterCacheableContentIsGeneratedEvent.php @@ -0,0 +1,67 @@ +<?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\Frontend\Event; + +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; + +/** + * Event that allows to enhance or change content (also depending if caching is enabled). + * Think of $this->isCachingEnabled() as the same as $TSFE->no_cache. + * Depending on disable or enabling caching, the cache is then not stored in the pageCache. + */ +final class AfterCacheableContentIsGeneratedEvent +{ + public function __construct( + private readonly ServerRequestInterface $request, + private readonly TypoScriptFrontendController $controller, + private readonly string $cacheIdentifier, + private bool $usePageCache + ) { + } + + public function getRequest(): ServerRequestInterface + { + return $this->request; + } + + public function getController(): TypoScriptFrontendController + { + return $this->controller; + } + + public function isCachingEnabled(): bool + { + return $this->usePageCache; + } + + public function disableCaching(): void + { + $this->usePageCache = false; + } + + public function enableCaching(): void + { + $this->usePageCache = true; + } + + public function getCacheIdentifier(): string + { + return $this->cacheIdentifier; + } +} diff --git a/typo3/sysext/frontend/Classes/Event/AfterCachedPageIsPersistedEvent.php b/typo3/sysext/frontend/Classes/Event/AfterCachedPageIsPersistedEvent.php new file mode 100644 index 000000000000..0ce5690d6b52 --- /dev/null +++ b/typo3/sysext/frontend/Classes/Event/AfterCachedPageIsPersistedEvent.php @@ -0,0 +1,69 @@ +<?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\Frontend\Event; + +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; + +/** + * Event that is used directly after all cached content is stored in + * the page cache. + * + * If a page is called from the cache, this event is NOT fired. + * This event is also NOT FIRED when $TSFE->no_cache (or manipulated via AfterCacheableContentIsGeneratedEvent) + * is set. + */ +final class AfterCachedPageIsPersistedEvent +{ + public function __construct( + private readonly ServerRequestInterface $request, + private readonly TypoScriptFrontendController $controller, + private readonly string $cacheIdentifier, + private readonly array $cacheData, + private readonly int $cacheLifetime + ) { + } + + public function getRequest(): ServerRequestInterface + { + return $this->request; + } + + public function getController(): TypoScriptFrontendController + { + return $this->controller; + } + + public function getCacheIdentifier(): string + { + return $this->cacheIdentifier; + } + + public function getCacheData(): array + { + return $this->cacheData; + } + + /** + * The amount of seconds until the cache entry is invalid. + */ + public function getCacheLifetime(): int + { + return $this->cacheLifetime; + } +} diff --git a/typo3/sysext/frontend/Classes/Http/RequestHandler.php b/typo3/sysext/frontend/Classes/Http/RequestHandler.php index b54ce368c3ac..8e86c93d03b7 100644 --- a/typo3/sysext/frontend/Classes/Http/RequestHandler.php +++ b/typo3/sysext/frontend/Classes/Http/RequestHandler.php @@ -143,7 +143,7 @@ class RequestHandler implements RequestHandlerInterface $this->timeTracker->pull($this->timeTracker->LR ? $controller->content : ''); $this->timeTracker->decStackPointer(); - $controller->generatePage_postProcessing(); + $controller->generatePage_postProcessing($request); $this->timeTracker->pull(); } $controller->releaseLocks(); @@ -151,7 +151,7 @@ class RequestHandler implements RequestHandlerInterface // Render non-cached page parts by replacing placeholders which are taken from cache or added during page generation if ($controller->isINTincScript()) { if (!$controller->isGeneratePage()) { - // When page was generated, this was already called. Avoid calling this twice. + // When the page was generated, this was already called. Avoid calling this twice. $controller->preparePageContentGeneration($request); // Make sure all FAL resources are prefixed with absPrefPrefix diff --git a/typo3/sysext/indexed_search/Classes/Hook/TypoScriptFrontendHook.php b/typo3/sysext/indexed_search/Classes/EventListener/FrontendGenerationPageIndexingTrigger.php similarity index 63% rename from typo3/sysext/indexed_search/Classes/Hook/TypoScriptFrontendHook.php rename to typo3/sysext/indexed_search/Classes/EventListener/FrontendGenerationPageIndexingTrigger.php index 838e647d8d56..8a3ead69eea1 100644 --- a/typo3/sysext/indexed_search/Classes/Hook/TypoScriptFrontendHook.php +++ b/typo3/sysext/indexed_search/Classes/EventListener/FrontendGenerationPageIndexingTrigger.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -13,72 +15,77 @@ * The TYPO3 project - inspiring people to share! */ -namespace TYPO3\CMS\IndexedSearch\Hook; +namespace TYPO3\CMS\IndexedSearch\EventListener; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; -use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\LanguageAspect; use TYPO3\CMS\Core\PageTitle\PageTitleProviderManager; use TYPO3\CMS\Core\TimeTracker\TimeTracker; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; +use TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent; use TYPO3\CMS\IndexedSearch\Indexer; /** - * Hooks for \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController (TSFE). - * @internal this is a TYPO3-internal hook implementation and not part of TYPO3's Core API. + * PSR-14 Event Listener for \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController (TSFE), + * which is called just before the content should be stored in the TYPO3 Cache. + * + * @internal this is a TYPO3-internal Event listener implementation and not part of TYPO3's Core API. */ -class TypoScriptFrontendHook +class FrontendGenerationPageIndexingTrigger { + public function __construct( + protected ExtensionConfiguration $extensionConfiguration, + protected TimeTracker $timeTracker, + protected PageTitleProviderManager $pageTitleProviderManager, + protected Indexer $indexer + ) { + } + /** * Trigger indexing of content, after evaluating if this page could / should be indexed. - * - * @param array $parameters - * @param TypoScriptFrontendController $tsfe + * This is triggered for all page content that can be cached. */ - public function indexPageContent(array $parameters, TypoScriptFrontendController $tsfe) + public function indexPageContent(AfterCacheableContentIsGeneratedEvent $event): void { + if (!$event->isCachingEnabled()) { + return; + } + $tsfe = $event->getController(); // Determine if page should be indexed, and if so, configure and initialize indexer if (!($tsfe->config['config']['index_enable'] ?? false)) { return; } // Indexer configuration from Extension Manager interface: - $disableFrontendIndexing = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('indexed_search', 'disableFrontendIndexing'); + $disableFrontendIndexing = (bool)$this->extensionConfiguration->get('indexed_search', 'disableFrontendIndexing'); $forceIndexing = $tsfe->applicationData['forceIndexing'] ?? false; - $timeTracker = GeneralUtility::makeInstance(TimeTracker::class); - $timeTracker->push('Index page'); + $this->timeTracker->push('Index page'); if ($disableFrontendIndexing && !$forceIndexing) { - $timeTracker->setTSlogMessage('Index page? No, Ordinary Frontend indexing during rendering is disabled.'); + $this->timeTracker->setTSlogMessage('Index page? No, Ordinary Frontend indexing during rendering is disabled.'); return; } - if ($tsfe->page['no_search']) { - $timeTracker->setTSlogMessage('Index page? No, The "No Search" flag has been set in the page properties!'); + if ($tsfe->page['no_search'] ?? false) { + $this->timeTracker->setTSlogMessage('Index page? No, The "No Search" flag has been set in the page properties!'); return; } /** @var LanguageAspect $languageAspect */ - $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language'); + $languageAspect = $tsfe->getContext()->getAspect('language'); if ($languageAspect->getId() !== $languageAspect->getContentId()) { - $timeTracker->setTSlogMessage('Index page? No, languageId was different from contentId which indicates that the page contains fall-back content and that would be falsely indexed as localized content.'); + $this->timeTracker->setTSlogMessage('Index page? No, languageId was different from contentId which indicates that the page contains fall-back content and that would be falsely indexed as localized content.'); return; } // Init and start indexing - $indexer = GeneralUtility::makeInstance(Indexer::class); - $indexer->forceIndexing = $forceIndexing; - $indexer->init($this->initializeIndexerConfiguration($tsfe, $languageAspect)); - $indexer->indexTypo3PageContent(); - $timeTracker->pull(); + $this->indexer->forceIndexing = $forceIndexing; + $this->indexer->init($this->initializeIndexerConfiguration($tsfe, $languageAspect)); + $this->indexer->indexTypo3PageContent(); + $this->timeTracker->pull(); } /** * Setting up internal configuration from config array based on TypoScriptFrontendController * Information about page for which the indexing takes place - * - * @param TypoScriptFrontendController $tsfe - * @param LanguageAspect $languageAspect - * @return array */ protected function initializeIndexerConfiguration(TypoScriptFrontendController $tsfe, LanguageAspect $languageAspect): array { @@ -93,7 +100,7 @@ class TypoScriptFrontendHook // MP variable, if any (Mount Points) 'MP' => $tsfe->MP, // Group list - 'gr_list' => implode(',', GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1])), + 'gr_list' => implode(',', $tsfe->getContext()->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1])), // page arguments array 'staticPageArguments' => $pageArguments->getStaticArguments(), // The creation date of the TYPO3 page @@ -106,13 +113,12 @@ class TypoScriptFrontendHook $configuration['rootline_uids'][$rlkey] = $rldat['uid']; } // Content of page - $configuration['content'] = $tsfe->content; // Content string (HTML of TYPO3 page) + $configuration['content'] = $tsfe->content; // Alternative title for indexing // @see https://forge.typo3.org/issues/88041 - $titleProvider = GeneralUtility::makeInstance(PageTitleProviderManager::class); - $configuration['indexedDocTitle'] = $titleProvider->getTitle(); + $configuration['indexedDocTitle'] = $this->pageTitleProviderManager->getTitle(); // Most recent modification time (seconds) of the content on the page. Used to evaluate whether it should be re-indexed. $configuration['mtime'] = $tsfe->register['SYS_LASTCHANGED'] ?? $tsfe->page['SYS_LASTCHANGED']; diff --git a/typo3/sysext/indexed_search/Configuration/Services.yaml b/typo3/sysext/indexed_search/Configuration/Services.yaml index 86c1d9c62a30..6ee1770bc538 100644 --- a/typo3/sysext/indexed_search/Configuration/Services.yaml +++ b/typo3/sysext/indexed_search/Configuration/Services.yaml @@ -12,3 +12,9 @@ services: - name: event.listener identifier: 'indexed-search' method: 'addMysqlFulltextIndex' + + TYPO3\CMS\IndexedSearch\EventListener\FrontendGenerationPageIndexingTrigger: + tags: + - name: event.listener + identifier: 'indexed-search' + method: 'indexPageContent' diff --git a/typo3/sysext/indexed_search/ext_localconf.php b/typo3/sysext/indexed_search/ext_localconf.php index 39a868f4032d..7c7736f9365d 100644 --- a/typo3/sysext/indexed_search/ext_localconf.php +++ b/typo3/sysext/indexed_search/ext_localconf.php @@ -8,7 +8,6 @@ use TYPO3\CMS\Extbase\Utility\ExtensionUtility; use TYPO3\CMS\IndexedSearch\Controller\SearchController; use TYPO3\CMS\IndexedSearch\FileContentParser; use TYPO3\CMS\IndexedSearch\Hook\DeleteIndexedData; -use TYPO3\CMS\IndexedSearch\Hook\TypoScriptFrontendHook; use TYPO3\CMS\IndexedSearch\Utility\DoubleMetaPhoneUtility; defined('TYPO3') or die(); @@ -21,7 +20,6 @@ ExtensionUtility::configurePlugin( [SearchController::class => 'form,search'] ); -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached']['indexed_search'] = TypoScriptFrontendHook::class . '->indexPageContent'; $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval']['indexed_search'] = DeleteIndexedData::class . '->delete'; // Configure default document parsers: diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php index beb003c42317..7598197321de 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php @@ -760,4 +760,28 @@ return [ 'Breaking-97797-GFXSettingProcessor_path_lzwRemoved.rst', ], ], + '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'tslib/class.tslib_fe.php\'][\'contentPostProc-cached\']' => [ + 'restFiles' => [ + 'Breaking-97862-HooksRelatedToGeneratingPageContentRemoved.rst', + 'Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour.rst', + ], + ], + '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'tslib/class.tslib_fe.php\'][\'contentPostProc-all\']' => [ + 'restFiles' => [ + 'Breaking-97862-HooksRelatedToGeneratingPageContentRemoved.rst', + 'Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour.rst', + ], + ], + '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'tslib/class.tslib_fe.php\'][\'usePageCache\']' => [ + 'restFiles' => [ + 'Breaking-97862-HooksRelatedToGeneratingPageContentRemoved.rst', + 'Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour.rst', + ], + ], + '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'tslib/class.tslib_fe.php\'][\'insertPageIncache\']' => [ + 'restFiles' => [ + 'Breaking-97862-HooksRelatedToGeneratingPageContentRemoved.rst', + 'Feature-97862-NewPSR-14EventsForManipulatingFrontendPageGenerationAndCacheBehaviour.rst', + ], + ], ]; diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php index 7c6c3833dd4a..e9a51fb6703c 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php @@ -59,4 +59,11 @@ return [ 'Breaking-96044-HardenMethodSignatureOfLogicalAndAndLogicalOr.rst', ], ], + 'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->generatePage_postProcessing' => [ + 'numberOfMandatoryArguments' => 1, + 'maximumNumberOfArguments' => 1, + 'restFiles' => [ + 'Breaking-97862-HooksRelatedToGeneratingPageContentRemoved.rst', + ], + ], ]; -- GitLab