From: Helmut Hummel Date: Fri, 19 Apr 2019 16:03:31 +0000 (+0200) Subject: [BUGFIX] Remove caches for page title and meta tag X-Git-Tag: v10.0.0~157 X-Git-Url: http://git.typo3.org/Packages/TYPO3.CMS.git/commitdiff_plain/d05865f7b9f4c50432f93009510737f39709fd45 [BUGFIX] Remove caches for page title and meta tag By concept for frontend rendering the page title and meta tags are not meant to be stored in page cache in order to allow non cachable plugins to modify those. Currently both page title and meta tags are stored in separate cache entries, which violates the concept above and unnecessarily tightly couples those code parts to the TypoScriptFrontendController and internal logic of it. This patch removes these caches. In order to properly handle the uncached rendering state, we make sure the meta tag registry is properly serialized with all managers. Resolves: #88179 Releases: master, 9.5 Change-Id: If5200398bf9ab9db09ab97403c976d82cb33d01d Signed-off-by: Frank Naegler Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60520 Tested-by: TYPO3com Tested-by: Helmut Hummel Tested-by: Richard Haeser Reviewed-by: Helmut Hummel Reviewed-by: Richard Haeser --- diff --git a/typo3/sysext/core/Classes/MetaTag/AbstractMetaTagManager.php b/typo3/sysext/core/Classes/MetaTag/AbstractMetaTagManager.php index 9a09b5c4cb9e..422c9712a644 100644 --- a/typo3/sysext/core/Classes/MetaTag/AbstractMetaTagManager.php +++ b/typo3/sysext/core/Classes/MetaTag/AbstractMetaTagManager.php @@ -16,9 +16,7 @@ namespace TYPO3\CMS\Core\MetaTag; * The TYPO3 project - inspiring people to share! */ -use TYPO3\CMS\Core\SingletonInterface; - -abstract class AbstractMetaTagManager implements MetaTagManagerInterface, SingletonInterface +abstract class AbstractMetaTagManager implements MetaTagManagerInterface { /** * The default attribute that defines the name of the property diff --git a/typo3/sysext/core/Classes/MetaTag/GenericMetaTagManager.php b/typo3/sysext/core/Classes/MetaTag/GenericMetaTagManager.php index b7a03a51dfc3..b724befcdb89 100644 --- a/typo3/sysext/core/Classes/MetaTag/GenericMetaTagManager.php +++ b/typo3/sysext/core/Classes/MetaTag/GenericMetaTagManager.php @@ -16,13 +16,11 @@ namespace TYPO3\CMS\Core\MetaTag; * The TYPO3 project - inspiring people to share! */ -use TYPO3\CMS\Core\SingletonInterface; - /** * Handles typical meta tags (non-grouped). Use AbstractMetaTagManager * to create you own MetaTags, this class is final by design */ -final class GenericMetaTagManager implements MetaTagManagerInterface, SingletonInterface +final class GenericMetaTagManager implements MetaTagManagerInterface { /** * The separator to define subproperties like og:image:width diff --git a/typo3/sysext/core/Classes/MetaTag/MetaTagManagerRegistry.php b/typo3/sysext/core/Classes/MetaTag/MetaTagManagerRegistry.php index b542c6090530..0ddf9b6fafef 100644 --- a/typo3/sysext/core/Classes/MetaTag/MetaTagManagerRegistry.php +++ b/typo3/sysext/core/Classes/MetaTag/MetaTagManagerRegistry.php @@ -26,6 +26,8 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; class MetaTagManagerRegistry implements SingletonInterface { protected $registry = []; + private $instances = []; + private $managers; public function __construct() { @@ -53,6 +55,7 @@ class MetaTagManagerRegistry implements SingletonInterface 'before' => $before, 'after' => $after ]; + $this->managers = null; } /** @@ -81,18 +84,24 @@ class MetaTagManagerRegistry implements SingletonInterface */ public function getAllManagers(): array { + if ($this->managers !== null) { + return $this->managers; + } + $orderedManagers = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies( $this->registry ); - $managers = []; + $this->managers = []; foreach ($orderedManagers as $manager => $managerConfiguration) { - if (class_exists($managerConfiguration['module'])) { - $managers[$manager] = GeneralUtility::makeInstance($managerConfiguration['module']); + $module = $managerConfiguration['module']; + if (class_exists($module)) { + $this->instances[$module] = $this->instances[$module] ?? GeneralUtility::makeInstance($module); + $this->managers[$manager] = $this->instances[$module]; } } - return $managers; + return $this->managers; } /** @@ -100,6 +109,7 @@ class MetaTagManagerRegistry implements SingletonInterface */ public function removeAllManagers() { - unset($this->registry); + $this->registry = []; + $this->managers = null; } } diff --git a/typo3/sysext/core/Classes/Page/PageRenderer.php b/typo3/sysext/core/Classes/Page/PageRenderer.php index 75dca760df31..f161375b360e 100644 --- a/typo3/sysext/core/Classes/Page/PageRenderer.php +++ b/typo3/sysext/core/Classes/Page/PageRenderer.php @@ -18,7 +18,6 @@ use TYPO3\CMS\Backend\Routing\Router; use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Template\DocumentTemplate; use TYPO3\CMS\Core\Cache\CacheManager; -use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Localization\LocalizationFactory; @@ -170,13 +169,6 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface */ protected $metaTags = []; - /** - * META Tags added via the API - * - * @var array - */ - protected $metaTagsByAPI = []; - /** * @var array */ @@ -358,6 +350,15 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface $this->setMetaTag('name', 'generator', 'TYPO3 CMS'); } + /** + * Set restored meta tag managers as singletons + * so that uncached plugins can use them to add or remove meta tags + */ + public function __wakeup() + { + GeneralUtility::setSingletonInstance(get_class($this->metaTagRegistry), $this->metaTagRegistry); + } + /** * @param FrontendInterface $cache */ @@ -918,7 +919,6 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface 1496402460 ); } - $manager = $this->metaTagRegistry->getManagerForProperty($name); $manager->addProperty($name, $content, $subProperties, $replace, $type); } @@ -1674,33 +1674,11 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface { $metaTags = []; $metaTagManagers = $this->metaTagRegistry->getAllManagers(); - try { - $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('pages'); - } catch (NoSuchCacheException $e) { - $cache = null; - } foreach ($metaTagManagers as $manager => $managerObject) { - $cacheIdentifier = $this->getTypoScriptFrontendController()->newHash . '-metatag-' . $manager; - - $existingCacheEntry = false; - if ($cache instanceof FrontendInterface && $properties = $cache->get($cacheIdentifier)) { - $existingCacheEntry = true; - } else { - $properties = $managerObject->renderAllProperties(); - } - + $properties = $managerObject->renderAllProperties(); if (!empty($properties)) { $metaTags[] = $properties; - - if ($cache instanceof FrontendInterface && !$existingCacheEntry && ($this->getTypoScriptFrontendController()->page['uid'] ?? false)) { - $cache->set( - $cacheIdentifier, - $properties, - ['pageId_' . $this->getTypoScriptFrontendController()->page['uid']], - $this->getTypoScriptFrontendController()->get_cache_timeout() - ); - } } } return $metaTags; diff --git a/typo3/sysext/core/Classes/PageTitle/PageTitleProviderManager.php b/typo3/sysext/core/Classes/PageTitle/PageTitleProviderManager.php index 7e182f594c7d..e20a587e41d7 100644 --- a/typo3/sysext/core/Classes/PageTitle/PageTitleProviderManager.php +++ b/typo3/sysext/core/Classes/PageTitle/PageTitleProviderManager.php @@ -16,30 +16,16 @@ namespace TYPO3\CMS\Core\PageTitle; * The TYPO3 project - inspiring people to share! */ -use TYPO3\CMS\Core\Cache\CacheManager; -use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException; -use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Service\DependencyOrderingService; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\TypoScript\TypoScriptService; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; /** * This class will take care of the different providers and returns the title with the highest priority */ class PageTitleProviderManager implements SingletonInterface { - /** - * @var FrontendInterface - */ - protected $pageCache; - - public function __construct() - { - $this->initCaches(); - } - /** * @return string * @throws \TYPO3\CMS\Core\Cache\Exception @@ -56,22 +42,10 @@ class PageTitleProviderManager implements SingletonInterface ->orderByDependencies($titleProviders); foreach ($orderedTitleProviders as $provider => $configuration) { - $cacheIdentifier = $this->getTypoScriptFrontendController()->newHash . '-titleTag-' . $provider; - if ($this->pageCache instanceof FrontendInterface && - $pageTitle = $this->pageCache->get($cacheIdentifier) - ) { - break; - } if (class_exists($configuration['provider']) && is_subclass_of($configuration['provider'], PageTitleProviderInterface::class)) { /** @var PageTitleProviderInterface $titleProviderObject */ $titleProviderObject = GeneralUtility::makeInstance($configuration['provider']); if ($pageTitle = $titleProviderObject->getTitle()) { - $this->pageCache->set( - $cacheIdentifier, - $pageTitle, - ['pageId_' . $this->getTypoScriptFrontendController()->page['uid']], - $this->getTypoScriptFrontendController()->get_cache_timeout() - ); break; } } @@ -80,14 +54,6 @@ class PageTitleProviderManager implements SingletonInterface return $pageTitle; } - /** - * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController - */ - private function getTypoScriptFrontendController(): TypoScriptFrontendController - { - return $GLOBALS['TSFE']; - } - /** * Get the TypoScript configuration for pageTitleProviders * @return array @@ -96,24 +62,12 @@ class PageTitleProviderManager implements SingletonInterface { $typoscriptService = GeneralUtility::makeInstance(TypoScriptService::class); $config = $typoscriptService->convertTypoScriptArrayToPlainArray( - $this->getTypoScriptFrontendController()->config['config'] ?? [] + $GLOBALS['TSFE']->config['config'] ?? [] ); return $config['pageTitleProviders'] ?? []; } - /** - * Initializes the caching system. - */ - protected function initCaches(): void - { - try { - $this->pageCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('pages'); - } catch (NoSuchCacheException $e) { - // Intended fall-through - } - } - /** * @param array $orderInformation * @return string[] diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Classes/Controller/MetaPluginController.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Classes/Controller/MetaPluginController.php new file mode 100644 index 000000000000..331576335c83 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Classes/Controller/MetaPluginController.php @@ -0,0 +1,43 @@ +getQueryParams()['id']; + GeneralUtility::makeInstance(CustomPageTitleProvider::class) + ->setTitle('static title with pageId: ' . $pageId . ' and pluginNumber: ' . $configuration['pluginNumber']); + $metaTagManager = GeneralUtility::makeInstance(MetaTagManagerRegistry::class)->getManagerForProperty('og:title'); + $metaTagManager->addProperty( + 'og:title', + 'OG title from a controller with pageId: ' . $pageId . ' and pluginNumber: ' . $configuration['pluginNumber'], + [], + true + ); + return 'TYPO3\CMS\TestMeta\Controller::setMetaData'; + } +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Classes/PageTitle/CustomPageTitleProvider.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Classes/PageTitle/CustomPageTitleProvider.php new file mode 100644 index 000000000000..e8b0d2369e52 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Classes/PageTitle/CustomPageTitleProvider.php @@ -0,0 +1,26 @@ +title = $title; + } +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page1.typoscript b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page1.typoscript new file mode 100644 index 000000000000..d15abaea105c --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page1.typoscript @@ -0,0 +1,12 @@ +config.pageTitleProviders { + testMetaProvider { + provider = TYPO3\CMS\TestMeta\PageTitle\CustomPageTitleProvider + before = record + after = altPageTitle + } +} + +page = PAGE +page.5 = TEXT +page.5.value = MetaData-Test +page.5.stdWrap.wrap =

|

diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page2.typoscript b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page2.typoscript new file mode 100644 index 000000000000..ea0c01ca3474 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page2.typoscript @@ -0,0 +1,23 @@ +config.pageTitleProviders { + testMetaProvider { + provider = TYPO3\CMS\TestMeta\PageTitle\CustomPageTitleProvider + before = record + after = altPageTitle + } +} + +page = PAGE +page.5 = TEXT +page.5.value = MetaData-Test +page.5.stdWrap.wrap =

|

+ +page.10 = USER +page.10 { + userFunc = TYPO3\CMS\TestMeta\Controller\MetaPluginController->setMetaData + pluginNumber = 10 +} +page.20 = USER +page.20 { + userFunc = TYPO3\CMS\TestMeta\Controller\MetaPluginController->setMetaData + pluginNumber = 20 +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page3.typoscript b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page3.typoscript new file mode 100644 index 000000000000..486409c3d550 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page3.typoscript @@ -0,0 +1,23 @@ +config.pageTitleProviders { + testMetaProvider { + provider = TYPO3\CMS\TestMeta\PageTitle\CustomPageTitleProvider + before = record + after = altPageTitle + } +} + +page = PAGE +page.5 = TEXT +page.5.value = MetaData-Test +page.5.stdWrap.wrap =

|

+ +page.10 = USER_INT +page.10 { + userFunc = TYPO3\CMS\TestMeta\Controller\MetaPluginController->setMetaData + pluginNumber = 10 +} +page.20 = USER_INT +page.20 { + userFunc = TYPO3\CMS\TestMeta\Controller\MetaPluginController->setMetaData + pluginNumber = 20 +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page4.typoscript b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page4.typoscript new file mode 100644 index 000000000000..d3424a70dc6d --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page4.typoscript @@ -0,0 +1,23 @@ +config.pageTitleProviders { + testMetaProvider { + provider = TYPO3\CMS\TestMeta\PageTitle\CustomPageTitleProvider + before = record + after = altPageTitle + } +} + +page = PAGE +page.5 = TEXT +page.5.value = MetaData-Test +page.5.stdWrap.wrap =

|

+ +page.10 = USER +page.10 { + userFunc = TYPO3\CMS\TestMeta\Controller\MetaPluginController->setMetaData + pluginNumber = 10 +} +page.20 = USER_INT +page.20 { + userFunc = TYPO3\CMS\TestMeta\Controller\MetaPluginController->setMetaData + pluginNumber = 20 +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page5.typoscript b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page5.typoscript new file mode 100644 index 000000000000..eaa68e47ac43 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page5.typoscript @@ -0,0 +1,23 @@ +config.pageTitleProviders { + testMetaProvider { + provider = TYPO3\CMS\TestMeta\PageTitle\CustomPageTitleProvider + before = record + after = altPageTitle + } +} + +page = PAGE +page.5 = TEXT +page.5.value = MetaData-Test +page.5.stdWrap.wrap =

|

+ +page.10 = USER_INT +page.10 { + userFunc = TYPO3\CMS\TestMeta\Controller\MetaPluginController->setMetaData + pluginNumber = 10 +} +page.20 = USER +page.20 { + userFunc = TYPO3\CMS\TestMeta\Controller\MetaPluginController->setMetaData + pluginNumber = 20 +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/ext_emconf.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/ext_emconf.php new file mode 100644 index 000000000000..74274843c374 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/ext_emconf.php @@ -0,0 +1,20 @@ + 'MetaData Test', + 'description' => 'MetaData Test', + 'category' => 'example', + 'version' => '10.0.0', + 'state' => 'beta', + 'clearCacheOnLoad' => 0, + 'author' => 'Frank Nägler', + 'author_email' => 'frank.naegler@typo3.org', + 'author_company' => '', + 'constraints' => [ + 'depends' => [ + 'typo3' => '10.0.0', + 'seo' => '10.0.0', + ], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Scenarios/pages_with_plugins_seo_meta.xml b/typo3/sysext/core/Tests/Functional/Fixtures/Scenarios/pages_with_plugins_seo_meta.xml new file mode 100644 index 000000000000..cbd8730315f5 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Scenarios/pages_with_plugins_seo_meta.xml @@ -0,0 +1,33 @@ + + + + 1 + 0 + Rootpage for Tests + 0 + + + 2 + 1 + Page with USER Plugins + 0 + + + 3 + 1 + Page with USER_INT Plugins + 0 + + + 4 + 1 + Page with USER_INT and USER Plugins + 0 + + + 5 + 1 + Page with USER and USER_INT Plugins + 0 + + diff --git a/typo3/sysext/core/Tests/Functional/MetaDataHandling/PluginsTest.php b/typo3/sysext/core/Tests/Functional/MetaDataHandling/PluginsTest.php new file mode 100644 index 000000000000..7dc8e602c959 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/MetaDataHandling/PluginsTest.php @@ -0,0 +1,125 @@ +testExtensionsToLoad[] = 'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta'; + + parent::setUp(); + $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/Scenarios/pages_with_plugins_seo_meta.xml'); + + $this->writeSiteConfiguration( + 'website-local', + $this->buildSiteConfiguration(1, 'http://localhost/'), + [ + $this->buildDefaultLanguageConfiguration('EN', '/') + ] + ); + } + + public function ensurePageSetupIsOkDataProvider(): array + { + return [ + 'page:uid:1' => [1, false], + 'page:uid:2' => [2, true], + 'page:uid:3' => [3, true], + 'page:uid:4' => [4, true], + 'page:uid:5' => [5, true], + ]; + } + + /** + * @test + * @dataProvider ensurePageSetupIsOkDataProvider + * @param int $pageId + * @param bool $expectPluginOutput + */ + public function ensurePageSetupIsOk(int $pageId, bool $expectPluginOutput): void + { + $this->setUpFrontendRootPage(1, ['typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page' . $pageId . '.typoscript']); + $response = $this->executeFrontendRequest( + (new InternalRequest('http://localhost/'))->withQueryParameters([ + 'id' => $pageId, + ]) + ); + $body = (string)$response->getBody(); + $this->assertStringContainsString('

MetaData-Test

', $body); + if ($expectPluginOutput) { + $this->assertStringContainsString('TYPO3\CMS\TestMeta\Controller::setMetaData', $body); + } else { + $this->assertStringNotContainsString('TYPO3\CMS\TestMeta\Controller::setMetaData', $body); + } + } + + public function ensureMetaDataAreCorrectDataProvider(): array + { + return [ + 'page:uid:1' => [1, 'Rootpage for Tests', ''], + 'page:uid:2' => [2, 'static title with pageId: 2 and pluginNumber: 20', 'OG title from a controller with pageId: 2 and pluginNumber: 20'], + 'page:uid:3' => [3, 'static title with pageId: 3 and pluginNumber: 20', 'OG title from a controller with pageId: 3 and pluginNumber: 20'], + 'page:uid:4' => [4, 'static title with pageId: 4 and pluginNumber: 20', 'OG title from a controller with pageId: 4 and pluginNumber: 20'], + 'page:uid:5' => [5, 'static title with pageId: 5 and pluginNumber: 10', 'OG title from a controller with pageId: 5 and pluginNumber: 10'], + ]; + } + + /** + * This test ensures that the meta data and title of the page are the same + * even if the pages is delivered cached or uncached. + * + * @test + * @dataProvider ensureMetaDataAreCorrectDataProvider + * @param int $pageId + * @param string $expectedTitle + * @param string $expectedMetaOgTitle + */ + public function ensureMetaDataAreCorrect(int $pageId, string $expectedTitle, string $expectedMetaOgTitle): void + { + $this->setUpFrontendRootPage(1, ['typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Configuration/TypoScript/page' . $pageId . '.typoscript']); + + // First hit to create a cached version + $uncachedResponse = $this->executeFrontendRequest( + (new InternalRequest('http://localhost/'))->withQueryParameters([ + 'id' => $pageId, + ]) + ); + $body = (string)$uncachedResponse->getBody(); + $this->assertStringContainsString('' . $expectedTitle . '', $body); + if ($expectedMetaOgTitle !== '') { + $this->assertStringContainsString('', $body, 'first hit, not cached'); + } + + // Second hit to check the cached version + $cachedResponse = $this->executeFrontendRequest( + (new InternalRequest('http://localhost/'))->withQueryParameters([ + 'id' => $pageId, + ]) + ); + $body = (string)$cachedResponse->getBody(); + $this->assertStringContainsString('' . $expectedTitle . '', $body); + if ($expectedMetaOgTitle !== '') { + $this->assertStringContainsString('', $body, 'second hit, cached'); + } + } +}