485 lines
28 KiB
PHP
485 lines
28 KiB
PHP
<?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;
|
|
}
|
|
}
|