TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,484 @@
|
||||
<?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\TypoScript;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\TypoScript\AST\Merger\SetupConfigMerger;
|
||||
use TYPO3\CMS\Core\TypoScript\AST\Node\ChildNode;
|
||||
use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\RootInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\SysTemplateTreeBuilder;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\Traverser\ConditionVerdictAwareIncludeTreeTraverser;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\Traverser\IncludeTreeTraverser;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeAstBuilderVisitor;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeConditionIncludeListAccumulatorVisitor;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeConditionMatcherVisitor;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeSetupConditionConstantSubstitutionVisitor;
|
||||
use TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer;
|
||||
use TYPO3\CMS\Frontend\Event\ModifyTypoScriptConfigEvent;
|
||||
use TYPO3\CMS\Frontend\Event\ModifyTypoScriptConstantsEvent;
|
||||
|
||||
/**
|
||||
* Create FrontendTypoScript with its details. This is typically used by a Frontend middleware
|
||||
* to calculate the TypoScript needed to satisfy rendering details of the specific Request.
|
||||
*
|
||||
* @internal Methods signatures and detail implementations are still subject to change.
|
||||
*/
|
||||
final readonly class FrontendTypoScriptFactory
|
||||
{
|
||||
public function __construct(
|
||||
private ContainerInterface $container,
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
private SysTemplateTreeBuilder $treeBuilder,
|
||||
private LossyTokenizer $tokenizer,
|
||||
private IncludeTreeTraverser $includeTreeTraverser,
|
||||
private ConditionVerdictAwareIncludeTreeTraverser $includeTreeTraverserConditionVerdictAware,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* First step of TypoScript calculations.
|
||||
* This is *always* called, even in FE fully cached pages context since the page
|
||||
* cache entry depends on setup condition verdicts, which depends on settings.
|
||||
*
|
||||
* Returns the FrontendTypoScript object with these parameters set:
|
||||
* * settingsTree: The full settings ("constants") AST
|
||||
* * flatSettings: Flattened list of settings, derived from settings tree
|
||||
* * settingsConditionList: Settings conditions with verdicts of this Request
|
||||
* * setupConditionList: Setup conditions with verdicts of this Request
|
||||
* * (sometimes) setupIncludeTree: The setup include tree *if* it had to be calculated
|
||||
*/
|
||||
public function createSettingsAndSetupConditions(
|
||||
SiteInterface $site,
|
||||
array $sysTemplateRows,
|
||||
array $expressionMatcherVariables,
|
||||
?PhpFrontend $typoScriptCache,
|
||||
): FrontendTypoScript {
|
||||
$settingsDetails = $this->createSettings(
|
||||
$site,
|
||||
$sysTemplateRows,
|
||||
$expressionMatcherVariables,
|
||||
$typoScriptCache
|
||||
);
|
||||
$setupDetails = $this->createSetupConditionList(
|
||||
$site,
|
||||
$sysTemplateRows,
|
||||
$expressionMatcherVariables,
|
||||
$typoScriptCache,
|
||||
$settingsDetails['flatSettings'],
|
||||
$settingsDetails['settingsConditionList'],
|
||||
);
|
||||
$frontendTypoScript = new FrontendTypoScript(
|
||||
$settingsDetails['settingsTree'],
|
||||
$settingsDetails['settingsConditionList'],
|
||||
$settingsDetails['flatSettings'],
|
||||
$setupDetails['setupConditionList'],
|
||||
);
|
||||
if ($setupDetails['setupIncludeTree']) {
|
||||
$frontendTypoScript->setSetupIncludeTree($setupDetails['setupIncludeTree']);
|
||||
}
|
||||
return $frontendTypoScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate settings (formerly "constants").
|
||||
*
|
||||
* The page cache entry identifier depends on setup TypoScript: A single page with two different
|
||||
* setup TypoScript AST will probably render different results, thus two page-cache entries.
|
||||
* Setup TypoScript can be different when setup conditions match differently.
|
||||
* Setup conditions can use settings "[{$foo} = 42]".
|
||||
*
|
||||
* All FE requests thus need the current list of settings, and settings can have conditions, too.
|
||||
* We thus *always* need the current list of settings, even in fully cached pages context.
|
||||
*
|
||||
* The method calculates settings and uses caches as much as possible:
|
||||
* * settingsTree: The full settings AST
|
||||
* * flatSettings: Flattened list of settings, derived from settings AST
|
||||
* * settingsConditionList: Settings conditions with verdicts of this Request
|
||||
*
|
||||
* @return array{settingsTree: RootNode, flatSettings: array, settingsConditionList: array}
|
||||
*/
|
||||
private function createSettings(
|
||||
SiteInterface $site,
|
||||
array $sysTemplateRows,
|
||||
array $expressionMatcherVariables,
|
||||
?PhpFrontend $typoScriptCache,
|
||||
): array {
|
||||
$cacheCriteria = [
|
||||
'sysTemplateRows' => $sysTemplateRows,
|
||||
];
|
||||
if ($site instanceof Site && $site->isTypoScriptRoot()) {
|
||||
$cacheCriteria['siteIdentifier'] = $site->getIdentifier();
|
||||
}
|
||||
$conditionTreeCacheIdentifier = 'settings-condition-tree-' . hash('xxh3', json_encode($cacheCriteria, JSON_THROW_ON_ERROR));
|
||||
|
||||
if ($conditionTree = $typoScriptCache?->require($conditionTreeCacheIdentifier)) {
|
||||
// Got the (flat) include tree of all settings conditions for this TypoScript combination from cache.
|
||||
// Good. Traverse this list to calculate "current" condition verdicts. Hash this list together with a
|
||||
// hash of the TypoScript sys_templates, and try to retrieve the full settings TypoScript AST from cache.
|
||||
// Note: Working with the derived condition tree that *only* contains conditions, but not the full
|
||||
// include tree is a trick: We only need the condition verdicts to know the AST cache identifier,
|
||||
// and traversing the flat condition tree is quicker than traversing the entire settings include tree,
|
||||
// since it only scales with the number of settings conditions and not with the full amount of TypoScript
|
||||
// settings. The same trick is used for the setup AST cache later.
|
||||
$conditionMatcherVisitor = $this->container->get(IncludeTreeConditionMatcherVisitor::class);
|
||||
$conditionMatcherVisitor->initializeExpressionMatcherWithVariables($expressionMatcherVariables);
|
||||
// It does not matter if we use IncludeTreeTraverser or ConditionVerdictAwareIncludeTreeTraverser here:
|
||||
// Conditions list is flat, not nested. IncludeTreeTraverser has an if() less, so we use that one.
|
||||
$this->includeTreeTraverser->traverse($conditionTree, [$conditionMatcherVisitor]);
|
||||
$conditionList = $conditionMatcherVisitor->getConditionListWithVerdicts();
|
||||
$settings = $typoScriptCache->require(
|
||||
'settings-' . hash('xxh3', $conditionTreeCacheIdentifier . json_encode($conditionList, JSON_THROW_ON_ERROR))
|
||||
);
|
||||
if (is_array($settings)) {
|
||||
return [
|
||||
'settingsTree' => $settings['ast'],
|
||||
'flatSettings' => $settings['flatSettings'],
|
||||
'settingsConditionList' => $conditionList,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// We did not get settings from cache, or are not allowed to use cache. Build settings from scratch.
|
||||
// We fetch the full settings include tree (from cache if possible), register the condition
|
||||
// matcher and register the AST builder and traverse include tree to retrieve settings AST and derive
|
||||
// 'flat settings' from it. Both are cached if allowed afterward for the above 'if' to kick in next time.
|
||||
$includeTree = $this->treeBuilder->getTreeBySysTemplateRowsAndSite('constants', $sysTemplateRows, $this->tokenizer, $site, $typoScriptCache);
|
||||
$conditionMatcherVisitor = $this->container->get(IncludeTreeConditionMatcherVisitor::class);
|
||||
$conditionMatcherVisitor->initializeExpressionMatcherWithVariables($expressionMatcherVariables);
|
||||
$visitors = [];
|
||||
$visitors[] = $conditionMatcherVisitor;
|
||||
$astBuilderVisitor = $this->container->get(IncludeTreeAstBuilderVisitor::class);
|
||||
$visitors[] = $astBuilderVisitor;
|
||||
// We must use ConditionVerdictAwareIncludeTreeTraverser here: This one does not walk into
|
||||
// children for not matching conditions, which is important to create the correct AST.
|
||||
$this->includeTreeTraverserConditionVerdictAware->traverse($includeTree, $visitors);
|
||||
$tree = $astBuilderVisitor->getAst();
|
||||
// @internal Dispatch an experimental event allowing listeners to still change the settings AST,
|
||||
// to for instance implement nested constants if really needed. Note this event may change
|
||||
// or vanish later without further notice.
|
||||
$tree = $this->eventDispatcher->dispatch(new ModifyTypoScriptConstantsEvent($tree))->getConstantsAst();
|
||||
$flatSettings = $tree->flatten();
|
||||
|
||||
// Prepare the full list of settings conditions in order to cache this list, avoiding the
|
||||
// settings AST building next time. We need all conditions of the entire include tree, but the
|
||||
// above ConditionVerdictAwareIncludeTreeTraverser did not find nested conditions if an upper
|
||||
// condition did not match. We thus have to traverse include tree a second time with the
|
||||
// IncludeTreeTraverser. This one does traverse into not matching conditions.
|
||||
$visitors = [];
|
||||
$conditionMatcherVisitor = $this->container->get(IncludeTreeConditionMatcherVisitor::class);
|
||||
$conditionMatcherVisitor->initializeExpressionMatcherWithVariables($expressionMatcherVariables);
|
||||
$visitors[] = $conditionMatcherVisitor;
|
||||
$conditionTreeAccumulatorVisitor = null;
|
||||
if (!$conditionTree && $typoScriptCache) {
|
||||
// If the settingsConditionTree did not come from cache above and if we are allowed to cache,
|
||||
// register the visitor that creates the settings condition include tree, to cache it.
|
||||
$conditionTreeAccumulatorVisitor = $this->container->get(IncludeTreeConditionIncludeListAccumulatorVisitor::class);
|
||||
$visitors[] = $conditionTreeAccumulatorVisitor;
|
||||
}
|
||||
$this->includeTreeTraverser->traverse($includeTree, $visitors);
|
||||
$conditionList = $conditionMatcherVisitor->getConditionListWithVerdicts();
|
||||
|
||||
if ($conditionTreeAccumulatorVisitor) {
|
||||
// Cache the flat condition include tree for next run.
|
||||
$conditionTree = $conditionTreeAccumulatorVisitor->getConditionIncludes();
|
||||
$typoScriptCache?->set(
|
||||
$conditionTreeCacheIdentifier,
|
||||
'return unserialize(\'' . addcslashes(serialize($conditionTree), '\'\\') . '\');'
|
||||
);
|
||||
}
|
||||
$typoScriptCache?->set(
|
||||
// Cache full AST and the derived 'flattened' variant for next run, which will kick in if
|
||||
// the sys_templates and condition verdicts are identical with another Request.
|
||||
'settings-' . hash('xxh3', $conditionTreeCacheIdentifier . json_encode($conditionList, JSON_THROW_ON_ERROR)),
|
||||
'return unserialize(\'' . addcslashes(serialize(['ast' => $tree, 'flatSettings' => $flatSettings]), '\'\\') . '\');'
|
||||
);
|
||||
|
||||
return [
|
||||
'settingsTree' => $tree,
|
||||
'flatSettings' => $flatSettings,
|
||||
'settingsConditionList' => $conditionList,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate setup condition verdicts.
|
||||
*
|
||||
* With settings being done, the list of matching setup condition verdicts is calculated,
|
||||
* which depend on settings. Setup conditions with their verdicts are part of the page
|
||||
* cache identifier, they are *always* needed in the FE rendering chain.
|
||||
*
|
||||
* The cached variant uses a similar trick as with the settings calculation above: We
|
||||
* calculate a flat tree of all conditions and cache this, so the traverser only needs
|
||||
* to iterate the conditions to calculate their verdicts, but not the entire include
|
||||
* tree next time.
|
||||
*
|
||||
* The method returns:
|
||||
* * 'setupConditionList': Setup conditions with verdicts of this Request
|
||||
* * (sometimes) setupIncludeTree: The setup include tree *if* it had to be calculated. Used internally
|
||||
* to suppress a second calculation in createSetupConfigOrFullSetup().
|
||||
*
|
||||
* @return array{setupConditionList: array, setupIncludeTree: RootInclude|null}
|
||||
*/
|
||||
private function createSetupConditionList(
|
||||
SiteInterface $site,
|
||||
array $sysTemplateRows,
|
||||
array $expressionMatcherVariables,
|
||||
?PhpFrontend $typoScriptCache,
|
||||
array $flatSettings,
|
||||
array $settingsConditionList,
|
||||
): array {
|
||||
$conditionTreeCacheIdentifier = 'setup-condition-tree-' . hash(
|
||||
'xxh3',
|
||||
json_encode($sysTemplateRows, JSON_THROW_ON_ERROR)
|
||||
. json_encode($site instanceof Site && $site->isTypoScriptRoot() ? $site->getSets() : '', JSON_THROW_ON_ERROR)
|
||||
. json_encode($settingsConditionList, JSON_THROW_ON_ERROR)
|
||||
);
|
||||
|
||||
if ($conditionTree = $typoScriptCache?->require($conditionTreeCacheIdentifier)) {
|
||||
// We got the flat list of all setup conditions for this TypoScript combination from cache. Good. We traverse
|
||||
// this list to calculate "current" condition verdicts, which we need as hash to be part of page cache identifier.
|
||||
// We're done and return. Note 'setupIncludeTree' is *not* returned in this case since it is not needed and
|
||||
// may or may not be needed later, depending on if we can get a page cache entry later and if it has _INT objects.
|
||||
$visitors = [];
|
||||
$conditionConstantSubstitutionVisitor = $this->container->get(IncludeTreeSetupConditionConstantSubstitutionVisitor::class);
|
||||
$conditionConstantSubstitutionVisitor->setFlattenedConstants($flatSettings);
|
||||
$visitors[] = $conditionConstantSubstitutionVisitor;
|
||||
$conditionMatcherVisitor = $this->container->get(IncludeTreeConditionMatcherVisitor::class);
|
||||
$conditionMatcherVisitor->initializeExpressionMatcherWithVariables($expressionMatcherVariables);
|
||||
$visitors[] = $conditionMatcherVisitor;
|
||||
// It does not matter if we use IncludeTreeTraverser or ConditionVerdictAwareIncludeTreeTraverser here:
|
||||
// Condition list is flat, not nested. IncludeTreeTraverser has an if() less, so we use that one.
|
||||
$this->includeTreeTraverser->traverse($conditionTree, $visitors);
|
||||
return [
|
||||
'setupConditionList' => $conditionMatcherVisitor->getConditionListWithVerdicts(),
|
||||
'setupIncludeTree' => null,
|
||||
];
|
||||
}
|
||||
|
||||
// We did not get setup condition list from cache, or are not allowed to use cache. We have to build setup
|
||||
// condition list from scratch. This means we'll fetch the full setup include tree (from cache if possible),
|
||||
// register the constant substitution visitor, the condition matcher and the condition accumulator visitor.
|
||||
$includeTree = $this->treeBuilder->getTreeBySysTemplateRowsAndSite('setup', $sysTemplateRows, $this->tokenizer, $site, $typoScriptCache);
|
||||
$visitors = [];
|
||||
$conditionConstantSubstitutionVisitor = $this->container->get(IncludeTreeSetupConditionConstantSubstitutionVisitor::class);
|
||||
$conditionConstantSubstitutionVisitor->setFlattenedConstants($flatSettings);
|
||||
$visitors[] = $conditionConstantSubstitutionVisitor;
|
||||
$conditionMatcherVisitor = $this->container->get(IncludeTreeConditionMatcherVisitor::class);
|
||||
$conditionMatcherVisitor->initializeExpressionMatcherWithVariables($expressionMatcherVariables);
|
||||
$visitors[] = $conditionMatcherVisitor;
|
||||
$conditionTreeAccumulatorVisitor = $this->container->get(IncludeTreeConditionIncludeListAccumulatorVisitor::class);
|
||||
$visitors[] = $conditionTreeAccumulatorVisitor;
|
||||
// It is important to use IncludeTreeTraverser here: We need the condition verdicts of *all* conditions, and
|
||||
// we want to accumulate all of them. The ConditionVerdictAwareIncludeTreeTraverser wouldn't walk into nested
|
||||
// conditions if an upper one does not match, which defeats cache identifier calculations.
|
||||
$this->includeTreeTraverser->traverse($includeTree, $visitors);
|
||||
|
||||
$typoScriptCache?->set(
|
||||
$conditionTreeCacheIdentifier,
|
||||
'return unserialize(\'' . addcslashes(serialize($conditionTreeAccumulatorVisitor->getConditionIncludes()), '\'\\') . '\');'
|
||||
);
|
||||
|
||||
return [
|
||||
'setupConditionList' => $conditionMatcherVisitor->getConditionListWithVerdicts(),
|
||||
'setupIncludeTree' => $includeTree,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich the given FrontendTypoScript object with TypoScript 'setup' relevant data.
|
||||
*
|
||||
* The method is called in FE after an attempt to retrieve page content from cache has
|
||||
* been done. There are three possible outcomes:
|
||||
* * The page has been retrieved from cache and the content *does not* contain uncached "_INT" objects
|
||||
* * The page has been retrieved from cache and the content *does* contain uncached "_INT" objects
|
||||
* * The page could not be retrieved from cache
|
||||
*
|
||||
* If the page could not be retrieved from cache, or if the cached page content contains "_INT" objects,
|
||||
* flag $needsFullSetup is given true, and the full TypoScript is calculated since at least parts of
|
||||
* the page content has to be rendered, which then needs full TypoScript.
|
||||
* If the page could be retrieved from cache, and contains no "_INT" objects, $needsFullSetup in false, the
|
||||
* rendering chain only needs the "config." part of TypoScript to satisfy the remaining middlewares.
|
||||
*
|
||||
* The method implements these variants and tries to add as little overhead as possible.
|
||||
*
|
||||
* Returns the FrontendTypoScript object:
|
||||
* * configTree: Always set. Global TypoScript 'config.' merged with overrides from given type/typeNum "page.config.".
|
||||
* * configArray: Always set. Array representation of configTree.
|
||||
* * setupTree: Not set if $needsFullSetup=false and configTree could be retrieved from cache. Full TypoScript setup.
|
||||
* * setupArray: Not set if $needsFullSetup=false and configTree could be retrieved from cache.
|
||||
* Array representation of setupTree.
|
||||
* * pageTree: Not set if $needsFullSetup=false and configTree could be retrieved from cache, or if no PAGE object
|
||||
* could be determined. The 'PAGE' object tree for given type/typeNum.
|
||||
* * pageArray: Not set if $needsFullSetup=false and configTree could be retrieved from cache, or if no PAGE object
|
||||
* could be determined. Array representation of PageTree.
|
||||
*/
|
||||
public function createSetupConfigOrFullSetup(
|
||||
bool $needsFullSetup,
|
||||
FrontendTypoScript $frontendTypoScript,
|
||||
SiteInterface $site,
|
||||
array $sysTemplateRows,
|
||||
array $expressionMatcherVariables,
|
||||
string $type,
|
||||
?PhpFrontend $typoScriptCache,
|
||||
?ServerRequestInterface $request,
|
||||
): FrontendTypoScript {
|
||||
$setupTypoScriptCacheIdentifier = 'setup-' . hash(
|
||||
'xxh3',
|
||||
json_encode($sysTemplateRows, JSON_THROW_ON_ERROR)
|
||||
. ($site instanceof Site && $site->isTypoScriptRoot() ? $site->getIdentifier() : '')
|
||||
. json_encode($frontendTypoScript->getSettingsConditionList(), JSON_THROW_ON_ERROR)
|
||||
. json_encode($frontendTypoScript->getSetupConditionList(), JSON_THROW_ON_ERROR)
|
||||
);
|
||||
$setupConfigTypoScriptCacheIdentifier = 'setup-config-' . hash('xxh3', $setupTypoScriptCacheIdentifier . $type);
|
||||
|
||||
$gotSetupConfigFromCache = false;
|
||||
if ($setupConfigTypoScriptCache = $typoScriptCache?->require($setupConfigTypoScriptCacheIdentifier)) {
|
||||
$frontendTypoScript->setConfigTree($setupConfigTypoScriptCache['ast']);
|
||||
$frontendTypoScript->setConfigArray($setupConfigTypoScriptCache['array']);
|
||||
if (!$needsFullSetup) {
|
||||
// Fully cached page context without _INT - only 'config' is needed. Return early.
|
||||
return $frontendTypoScript;
|
||||
}
|
||||
$gotSetupConfigFromCache = true;
|
||||
}
|
||||
|
||||
$setupRawConfigAst = null;
|
||||
if (!$typoScriptCache || $needsFullSetup || !$gotSetupConfigFromCache) {
|
||||
// If caching is not allowed, if no page cache entry could be loaded or if the page cache entry has _INT
|
||||
// object, we need the full setup AST. Try to use a cache entry for setup AST, which especially up _INT
|
||||
// parsing. In unavailable, calculate full setup AST and cache it if allowed.
|
||||
$gotSetupFromCache = false;
|
||||
if ($setupTypoScriptCache = $typoScriptCache?->require($setupTypoScriptCacheIdentifier)) {
|
||||
// We need AST, and we got it from cache.
|
||||
$frontendTypoScript->setSetupTree($setupTypoScriptCache['ast']);
|
||||
$frontendTypoScript->setSetupArray($setupTypoScriptCache['array']);
|
||||
$setupRawConfigAst = $setupTypoScriptCache['ast']->getChildByName('config');
|
||||
$gotSetupFromCache = true;
|
||||
}
|
||||
if (!$typoScriptCache || !$gotSetupFromCache) {
|
||||
// We need AST and couldn't get it from cache or are now allowed to. We thus need the full setup
|
||||
// IncludeTree, which we can get from cache again if allowed, or is calculated a-new if not.
|
||||
$setupIncludeTree = $frontendTypoScript->getSetupIncludeTree();
|
||||
if (!$typoScriptCache || $setupIncludeTree === null) {
|
||||
// A previous method *may* have calculated setup include tree already. Calculate now if not.
|
||||
$setupIncludeTree = $this->treeBuilder->getTreeBySysTemplateRowsAndSite('setup', $sysTemplateRows, $this->tokenizer, $site, $typoScriptCache);
|
||||
}
|
||||
$visitors = [];
|
||||
$conditionConstantSubstitutionVisitor = $this->container->get(IncludeTreeSetupConditionConstantSubstitutionVisitor::class);
|
||||
$conditionConstantSubstitutionVisitor->setFlattenedConstants($frontendTypoScript->getFlatSettings());
|
||||
$visitors[] = $conditionConstantSubstitutionVisitor;
|
||||
$conditionMatcherVisitor = $this->container->get(IncludeTreeConditionMatcherVisitor::class);
|
||||
$conditionMatcherVisitor->initializeExpressionMatcherWithVariables($expressionMatcherVariables);
|
||||
$visitors[] = $conditionMatcherVisitor;
|
||||
$astBuilderVisitor = $this->container->get(IncludeTreeAstBuilderVisitor::class);
|
||||
$astBuilderVisitor->setFlatConstants($frontendTypoScript->getFlatSettings());
|
||||
$visitors[] = $astBuilderVisitor;
|
||||
$this->includeTreeTraverserConditionVerdictAware->traverse($setupIncludeTree, $visitors);
|
||||
$setupAst = $astBuilderVisitor->getAst();
|
||||
// @todo: It would be good to actively remove 'config' from AST and array here
|
||||
// to prevent people from using the unmerged variant. The same
|
||||
// is already done for the determined PAGE 'config' below. This works, but
|
||||
// is currently blocked by functional tests that assert details?
|
||||
// Also, we need to still cache with full 'config' to handle multiple types.
|
||||
$setupRawConfigAst = $setupAst->getChildByName('config');
|
||||
$frontendTypoScript->setSetupTree($setupAst);
|
||||
$frontendTypoScript->setSetupArray($setupAst->toArray());
|
||||
|
||||
// Write cache entry for AST and its array representation.
|
||||
$typoScriptCache?->set(
|
||||
$setupTypoScriptCacheIdentifier,
|
||||
'return unserialize(\'' . addcslashes(serialize(['ast' => $setupAst, 'array' => $setupAst->toArray()]), '\'\\') . '\');'
|
||||
);
|
||||
}
|
||||
|
||||
$setupAst = $frontendTypoScript->getSetupTree();
|
||||
$rawSetupPageNodeFromType = null;
|
||||
$pageNodeFoundByType = false;
|
||||
foreach ($setupAst->getNextChild() as $potentialPageNode) {
|
||||
// Find the PAGE object that matches given type/typeNum
|
||||
if ($potentialPageNode->getValue() === 'PAGE') {
|
||||
// @todo: We could potentially remove *all* PAGE objects from setup here. This prevents people
|
||||
// from accessing other ones than the determined one in $frontendTypoScript->getSetupArray().
|
||||
$typeNumChild = $potentialPageNode->getChildByName('typeNum');
|
||||
if ($typeNumChild && $type === $typeNumChild->getValue()) {
|
||||
$rawSetupPageNodeFromType = $potentialPageNode;
|
||||
$pageNodeFoundByType = true;
|
||||
break;
|
||||
}
|
||||
if (!$typeNumChild && $type === '0') {
|
||||
// The first PAGE node that has no typeNum is considered '0' automatically.
|
||||
$rawSetupPageNodeFromType = $potentialPageNode;
|
||||
$pageNodeFoundByType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$pageNodeFoundByType) {
|
||||
$rawSetupPageNodeFromType = new RootNode();
|
||||
}
|
||||
$setupPageAst = new RootNode();
|
||||
foreach ($rawSetupPageNodeFromType->getNextChild() as $child) {
|
||||
$setupPageAst->addChild($child);
|
||||
}
|
||||
|
||||
if (!$gotSetupConfigFromCache) {
|
||||
// If we did not get merged 'config.' from cache above, create it now and cache it.
|
||||
$mergedSetupConfigAst = (new SetupConfigMerger())->merge($setupRawConfigAst, $setupPageAst->getChildByName('config'));
|
||||
if ($mergedSetupConfigAst->getChildByName('absRefPrefix') === null) {
|
||||
// Make sure config.absRefPrefix is set, fallback to 'auto'.
|
||||
$absRefPrefixNode = new ChildNode('absRefPrefix');
|
||||
$absRefPrefixNode->setValue('auto');
|
||||
$mergedSetupConfigAst->addChild($absRefPrefixNode);
|
||||
}
|
||||
if ($mergedSetupConfigAst->getChildByName('doctype') === null) {
|
||||
// Make sure config.doctype is set, fallback to 'html5'.
|
||||
$doctypeNode = new ChildNode('doctype');
|
||||
$doctypeNode->setValue('html5');
|
||||
$mergedSetupConfigAst->addChild($doctypeNode);
|
||||
}
|
||||
if ($request) {
|
||||
// Dispatch ModifyTypoScriptConfigEvent before config is cached and if Request is given.
|
||||
$mergedSetupConfigAst = $this->eventDispatcher
|
||||
->dispatch(new ModifyTypoScriptConfigEvent($request, $setupAst, $mergedSetupConfigAst))->getConfigTree();
|
||||
}
|
||||
$frontendTypoScript->setConfigTree($mergedSetupConfigAst);
|
||||
$setupConfigArray = $mergedSetupConfigAst->toArray();
|
||||
$frontendTypoScript->setConfigArray($setupConfigArray);
|
||||
$typoScriptCache?->set(
|
||||
$setupConfigTypoScriptCacheIdentifier,
|
||||
'return unserialize(\'' . addcslashes(serialize(['ast' => $mergedSetupConfigAst, 'array' => $setupConfigArray]), '\'\\') . '\');'
|
||||
);
|
||||
}
|
||||
|
||||
if ($pageNodeFoundByType) {
|
||||
// Remove "page.config" to prevent people from working with the not merged variant.
|
||||
// We do *not* set page if it could not be determined (important for hasPage() later
|
||||
// to return an early "no PAGE for type found" Response.
|
||||
$setupPageAst->removeChildByName('config');
|
||||
$frontendTypoScript->setPageTree($setupPageAst);
|
||||
$frontendTypoScript->setPageArray($setupPageAst->toArray());
|
||||
}
|
||||
}
|
||||
return $frontendTypoScript;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user