Commit aa538279 authored by Benni Mack's avatar Benni Mack Committed by Christian Kuhn
Browse files

[TASK] Unify PageTsConfig loading

This change adds a new centralized place
in TYPO3\CMS\Core\Configuration\PageTsConfig to
load and parse (and match) PageTsConfig for Frontend
and Backend.

This way, the class can now be injected into
various places and reduce the usages to
"BackendUtility::getPagesTSconfig()"

Once all usages have been reduced, the main
BackendUtility method can be deprecated.

Resolves: #96546
Releases: main
Change-Id: I7311ca7cf535bb885fadc6b0b59f6ecd85f9444d
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73011

Reviewed-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent b8dd0cf5
......@@ -26,8 +26,7 @@ use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader;
use TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser;
use TYPO3\CMS\Core\Configuration\PageTsConfig;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\DateTimeAspect;
use TYPO3\CMS\Core\Core\Environment;
......@@ -55,7 +54,6 @@ use TYPO3\CMS\Core\Routing\RouterInterface;
use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -696,13 +694,6 @@ class BackendUtility
public static function getPagesTSconfig($id)
{
$id = (int)$id;
$cache = self::getRuntimeCache();
$pagesTsConfigIdToHash = $cache->get('pagesTsConfigIdToHash' . $id);
if ($pagesTsConfigIdToHash !== false) {
return $cache->get('pagesTsConfigHashToContent' . $pagesTsConfigIdToHash);
}
$rootLine = self::BEgetRootLine($id, '', true);
// Order correctly
ksort($rootLine);
......@@ -712,40 +703,9 @@ class BackendUtility
} catch (SiteNotFoundException $exception) {
$site = null;
}
// Load PageTS from all pages of the rootLine
$pageTs = GeneralUtility::makeInstance(PageTsConfigLoader::class)->load($rootLine);
// Parse the PageTS into an array, also applying conditions
$parser = GeneralUtility::makeInstance(
PageTsConfigParser::class,
GeneralUtility::makeInstance(TypoScriptParser::class),
GeneralUtility::makeInstance(CacheManager::class)->getCache('hash')
);
$matcher = GeneralUtility::makeInstance(ConditionMatcher::class, null, $id, $rootLine);
$tsConfig = $parser->parse($pageTs, $matcher, $site);
$cacheHash = md5((string)json_encode($tsConfig));
// Get User TSconfig overlay, if no backend user is logged-in, this needs to be checked as well
if (static::getBackendUserAuthentication()) {
$userTSconfig = static::getBackendUserAuthentication()->getTSConfig() ?? [];
} else {
$userTSconfig = [];
}
if (is_array($userTSconfig['page.'] ?? null)) {
// Override page TSconfig with user TSconfig
ArrayUtility::mergeRecursiveWithOverrule($tsConfig, $userTSconfig['page.']);
$cacheHash .= '_user' . static::getBackendUserAuthentication()->user['uid'];
}
// Many pages end up with the same ts config. To reduce memory usage, the cache
// entries are a linked list: One or more pids point to content hashes which then
// contain the cached content.
$cache->set('pagesTsConfigHashToContent' . $cacheHash, $tsConfig, ['pagesTsConfig']);
$cache->set('pagesTsConfigIdToHash' . $id, $cacheHash, ['pagesTsConfig']);
return $tsConfig;
$matcher = GeneralUtility::makeInstance(ConditionMatcher::class, GeneralUtility::makeInstance(Context::class), $id, $rootLine);
$tsConfig = GeneralUtility::makeInstance(PageTsConfig::class);
return $tsConfig->getWithUserOverride($id, $rootLine, $site, $matcher, static::getBackendUserAuthentication());
}
/*******************************************
......
......@@ -27,8 +27,9 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent;
use TYPO3\CMS\Core\Cache\Frontend\NullFrontend;
use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader;
use TYPO3\CMS\Core\Configuration\PageTsConfig;
use TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser;
use TYPO3\CMS\Core\Database\RelationHandler;
use TYPO3\CMS\Core\Localization\LanguageService;
......@@ -1020,12 +1021,12 @@ class BackendUtilityTest extends UnitTestCase
$expected = ['called.' => ['config']];
$pageId = 13;
$eventDispatcherProphecy = $this->prophesize(EventDispatcherInterface::class);
$eventDispatcherProphecy->dispatch(Argument::any())->willReturn(new ModifyLoadedPageTsConfigEvent([], []));
$eventDispatcherProphecy->dispatch(Argument::any())->willReturnArgument();
$loader = new PageTsConfigLoader($eventDispatcherProphecy->reveal());
GeneralUtility::addInstance(PageTsConfigLoader::class, $loader);
$parserProphecy = $this->prophesize(PageTsConfigParser::class);
$parserProphecy->parse(Argument::cetera())->willReturn($expected);
GeneralUtility::addInstance(PageTsConfigParser::class, $parserProphecy->reveal());
$configuration = new PageTsConfig(new NullFrontend('runtimeCache'), $loader, $parserProphecy->reveal());
GeneralUtility::addInstance(PageTsConfig::class, $configuration);
$matcherProphecy = $this->prophesize(ConditionMatcher::class);
GeneralUtility::addInstance(ConditionMatcher::class, $matcherProphecy->reveal());
......@@ -1039,8 +1040,6 @@ class BackendUtilityTest extends UnitTestCase
$cacheManagerProphecy = $this->prophesize(CacheManager::class);
$cacheProphecy = $this->prophesize(FrontendInterface::class);
$cacheManagerProphecy->getCache('runtime')->willReturn($cacheProphecy->reveal());
$cacheHashProphecy = $this->prophesize(FrontendInterface::class);
$cacheManagerProphecy->getCache('hash')->willReturn($cacheHashProphecy->reveal());
$cacheProphecy->has(Argument::cetera())->willReturn(false);
$cacheProphecy->get(Argument::cetera())->willReturn(false);
$cacheProphecy->set(Argument::cetera())->willReturn(false);
......
......@@ -37,10 +37,7 @@ use TYPO3\CMS\Core\Utility\PathUtility;
*/
class PageTsConfigLoader
{
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
protected EventDispatcherInterface $eventDispatcher;
public function __construct(EventDispatcherInterface $eventDispatcher)
{
......
<?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\Core\Configuration;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader;
use TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser;
use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\ConditionMatcherInterface;
use TYPO3\CMS\Core\Site\Entity\Site;
/**
* Main entry point for fetching PageTsConfig for frontend and backend.
*/
class PageTsConfig
{
protected FrontendInterface $cache;
protected PageTsConfigLoader $loader;
protected PageTsConfigParser $parser;
public function __construct(FrontendInterface $cache, PageTsConfigLoader $loader, PageTsConfigParser $parser)
{
$this->cache = $cache;
$this->loader = $loader;
$this->parser = $parser;
}
/**
* Load, parse and match all PageTsConfig for a given page from a root line.
*/
public function getForRootLine(array $rootLine, ?Site $site, ConditionMatcherInterface $conditionMatcher): array
{
return $this->parser->parse(
$this->loader->load($rootLine),
$conditionMatcher,
$site
);
}
/**
* Fetch and compile all PageTsConfig for a given page from a root line,
* but also overloads user-specific "page." properties which is possible too.
*
* This then caches a specific version away during runtime to avoid multiple overloads.
*/
public function getWithUserOverride(int $pageId, array $rootLine, ?Site $site, ConditionMatcherInterface $conditionMatcher, BackendUserAuthentication $user = null): array
{
$pagesTsConfigIdToHash = $this->cache->get('pagesTsConfigIdToHash' . $pageId);
if ($pagesTsConfigIdToHash !== false) {
return $this->cache->get('pagesTsConfigHashToContent' . $pagesTsConfigIdToHash);
}
$tsConfig = $this->getForRootLine($rootLine, $site, $conditionMatcher);
$cacheHash = md5((string)json_encode($tsConfig));
// Get UserTsConfig overlay, if no backend user is logged-in, this needs to be checked as well
if ($user) {
$userTsConfig = $user->getTSConfig()['page.'] ?? [];
if (!empty($userTsConfig)) {
// Override PageTsConfig with UserTsConfig
$tsConfig = array_replace_recursive($tsConfig, $userTsConfig);
$cacheHash .= '_user' . $user->user['uid'];
}
}
// Many pages end up with the same TsConfig. To reduce memory usage, the cache
// entries are a linked list: One or more pids point to content hashes which then
// contain the cached content.
$this->cache->set('pagesTsConfigHashToContent' . $cacheHash, $tsConfig, ['pagesTsConfig']);
$this->cache->set('pagesTsConfigIdToHash' . $pageId, $cacheHash, ['pagesTsConfig']);
return $tsConfig;
}
}
......@@ -31,15 +31,8 @@ use TYPO3\CMS\Core\Utility\ArrayUtility;
*/
class PageTsConfigParser
{
/**
* @var TypoScriptParser
*/
protected $typoScriptParser;
/**
* @var FrontendInterface
*/
protected $cache;
protected TypoScriptParser $typoScriptParser;
protected FrontendInterface $cache;
public function __construct(TypoScriptParser $typoScriptParser, FrontendInterface $cache)
{
......
......@@ -60,6 +60,16 @@ services:
TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader:
public: true
TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser:
public: true
arguments:
$cache: '@cache.hash'
TYPO3\CMS\Core\Configuration\PageTsConfig:
public: true
arguments:
$cache: '@cache.runtime'
TYPO3\CMS\Core\Database\Schema\SqlReader:
public: true
......
......@@ -19,9 +19,12 @@ namespace TYPO3\CMS\Core\Tests\Unit\Localization;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Configuration\PageTsConfig;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Localization\Locales;
use TYPO3\CMS\Core\Localization\TcaSystemLanguageCollector;
......@@ -127,6 +130,13 @@ class TcaSystemLanguageCollectorTest extends UnitTestCase
$siteFinder = $this->prophesize(SiteFinder::class);
$siteFinder->getAllSites()->willReturn([]);
GeneralUtility::addInstance(SiteFinder::class, $siteFinder->reveal());
$siteFinder->getSiteByPageId(0)->willThrow(SiteNotFoundException::class);
GeneralUtility::addInstance(SiteFinder::class, $siteFinder->reveal());
$conditionMatcher = $this->prophesize(ConditionMatcher::class);
GeneralUtility::addInstance(ConditionMatcher::class, $conditionMatcher->reveal());
$tsConfig = $this->prophesize(PageTsConfig::class);
$tsConfig->getWithUserOverride(Argument::cetera())->willReturn([]);
GeneralUtility::addInstance(PageTsConfig::class, $tsConfig->reveal());
$expectedItems = [
0 => [
......
......@@ -24,8 +24,7 @@ use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Charset\CharsetConverter;
use TYPO3\CMS\Core\Charset\UnknownCharsetException;
use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader;
use TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser;
use TYPO3\CMS\Core\Configuration\PageTsConfig;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\DateTimeAspect;
use TYPO3\CMS\Core\Context\LanguageAspect;
......@@ -63,7 +62,6 @@ use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
use TYPO3\CMS\Core\TypoScript\TemplateService;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -2966,22 +2964,16 @@ class TypoScriptFrontendController implements LoggerAwareInterface
*
* @return array
*/
public function getPagesTSconfig()
public function getPagesTSconfig(): array
{
if (!is_array($this->pagesTSconfig)) {
$contentHashCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
$loader = GeneralUtility::makeInstance(PageTsConfigLoader::class);
$tsConfigString = $loader->load(array_reverse($this->rootLine));
$parser = GeneralUtility::makeInstance(
PageTsConfigParser::class,
GeneralUtility::makeInstance(TypoScriptParser::class),
$contentHashCache
);
$this->pagesTSconfig = $parser->parse(
$tsConfigString,
GeneralUtility::makeInstance(ConditionMatcher::class, $this->context, $this->id, $this->rootLine),
$this->site
);
$matcher = GeneralUtility::makeInstance(ConditionMatcher::class, $this->context, $this->id, $this->rootLine);
$this->pagesTSconfig = GeneralUtility::makeInstance(PageTsConfig::class)
->getForRootLine(
array_reverse($this->rootLine),
$this->site,
$matcher
);
}
return $this->pagesTSconfig;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment