TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,659 @@
|
||||
<?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\IncludeTree;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
|
||||
use TYPO3\CMS\Core\Site\Set\SetRegistry;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\DefaultTypoScriptInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\DefaultTypoScriptMagicKeyInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\ExtensionStaticInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\FileInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\IncludeInterface;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\IncludeStaticFileDatabaseInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\IncludeStaticFileFileInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\RootInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\SiteInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\SiteTemplateInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\SysTemplateInclude;
|
||||
use TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
|
||||
/**
|
||||
* Create a tree representing all TypoScript includes.
|
||||
*
|
||||
* This is the 'middle' part of the TypoScript parsing process: The tokenizers as "lowest"
|
||||
* structure create line streams from TypoScript, the AST builder as "highest" structure create
|
||||
* the TypoScript object tree.
|
||||
*
|
||||
* This structure gathers all TypoScript snippets that have to be tokenized, and creates a
|
||||
* tree with include nodes and sub include nodes.
|
||||
*
|
||||
* It is called in frontend (and backend "Template" module) with the page rootline, gets all
|
||||
* attached sys_template records, gets their content and various sub includes and takes care
|
||||
* of correct include order.
|
||||
*
|
||||
* This class together with TreeFromLineStreamBuilder also takes care of conditions and
|
||||
* imports ("@import"): Those create child nodes in the tree. To evaluate conditions, the
|
||||
* tree is later traversed, condition verdicts (true / false) are determined, to see if
|
||||
* condition's child nodes should be considered in AST.
|
||||
*
|
||||
* The IncludeTree is "runtime stateless": Constants values and conditions are *not* evaluated
|
||||
* here, so the tree is always the same for a given rootline. This makes this structure cache-able:
|
||||
* In frontend, the tree (or sub parts of it) is cached and fetched from cache for next
|
||||
* call. This means the entire tree-building and tokenizing is suppressed. After that runtime
|
||||
* information is added: Conditions are evaluated, and the AST is built from given IncludeTree.
|
||||
*
|
||||
* @internal: Internal tree structure.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
final class SysTemplateTreeBuilder
|
||||
{
|
||||
/**
|
||||
* Used in 'basedOn' includes to prevent endless loop: Each sys_template row can
|
||||
* be included only once in 'basedOn'.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
private array $includedSysTemplateUids = [];
|
||||
|
||||
/** @var 'constants'|'setup' */
|
||||
private string $type;
|
||||
|
||||
private TokenizerInterface $tokenizer;
|
||||
private ?PhpFrontend $cache = null;
|
||||
|
||||
private bool $enableStaticMagicIncludes = false;
|
||||
|
||||
public function __construct(
|
||||
private readonly ConnectionPool $connectionPool,
|
||||
private readonly PackageManager $packageManager,
|
||||
private readonly Context $context,
|
||||
private readonly TreeFromLineStreamBuilder $treeFromTokenStreamBuilder,
|
||||
private readonly SetRegistry $setRegistry,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param 'constants'|'setup' $type
|
||||
*/
|
||||
public function getTreeBySysTemplateRowsAndSite(
|
||||
string $type,
|
||||
array $sysTemplateRows,
|
||||
TokenizerInterface $tokenizer,
|
||||
?SiteInterface $site = null,
|
||||
?PhpFrontend $cache = null
|
||||
): RootInclude {
|
||||
if (!in_array($type, ['constants', 'setup'], true)) {
|
||||
throw new \RuntimeException('type must be either constants or setup', 1653737656);
|
||||
}
|
||||
$this->tokenizer = $tokenizer;
|
||||
$this->cache = $cache;
|
||||
$this->type = $type;
|
||||
$this->includedSysTemplateUids = [];
|
||||
|
||||
$rootNode = new RootInclude();
|
||||
|
||||
$siteIsTypoScriptRoot = $site instanceof Site ? $site->isTypoScriptRoot() : false;
|
||||
if ($siteIsTypoScriptRoot) {
|
||||
$this->enableStaticMagicIncludes = false;
|
||||
$cacheIdentifier = 'site-template-' . $this->type . '-' . $site->getIdentifier();
|
||||
$includeNode = $this->cache?->require($cacheIdentifier) ?: null;
|
||||
$includeNode ??= $this->createSiteTemplateInclude($site, $cacheIdentifier);
|
||||
$rootNode->addChild($includeNode);
|
||||
}
|
||||
|
||||
if (empty($sysTemplateRows)) {
|
||||
return $rootNode;
|
||||
}
|
||||
|
||||
$this->enableStaticMagicIncludes = true;
|
||||
// Convenience code: Usually, at least one sys_template records needs to have 'clear' set. This resets
|
||||
// the AST and triggers inclusion of "globals" TypoScript. When integrators missed to set the clear flags,
|
||||
// important globals TypoScript is not loaded, leading to pretty hard to find issues in Frontend
|
||||
// rendering. Since the details of the 'clear' flags are rather complex anyway, this code scans the given
|
||||
// sys_template records if the flag is set somewhere and if not, actively sets it dynamically for the
|
||||
// first templates. As a result, integrators do not need to think about the 'clear' flags at all for
|
||||
// simple instances, it 'just works'.
|
||||
$atLeastOneSysTemplateRowHasClearFlag = $siteIsTypoScriptRoot;
|
||||
if (!$atLeastOneSysTemplateRowHasClearFlag) {
|
||||
foreach ($sysTemplateRows as $sysTemplateRow) {
|
||||
if (($this->type === 'constants' && $sysTemplateRow['clear'] & 1) || ($this->type === 'setup' && $sysTemplateRow['clear'] & 2)) {
|
||||
$atLeastOneSysTemplateRowHasClearFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$firstRow = reset($sysTemplateRows);
|
||||
$firstRow['clear'] = $this->type === 'constants' ? 1 : 2;
|
||||
$sysTemplateRows[array_key_first($sysTemplateRows)] = $firstRow;
|
||||
}
|
||||
|
||||
foreach ($sysTemplateRows as $sysTemplateRow) {
|
||||
$cacheIdentifier = 'sys-template-' . $this->type . '-' . $this->getSysTemplateRowIdentifier($sysTemplateRow, $site);
|
||||
if ($this->cache) {
|
||||
// Get from cache if possible
|
||||
$includeNode = $this->cache->require($cacheIdentifier);
|
||||
if ($includeNode) {
|
||||
$rootNode->addChild($includeNode);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$includeNode = new SysTemplateInclude();
|
||||
$name = '[sys_template:' . $sysTemplateRow['uid'] . '] ' . $sysTemplateRow['title'];
|
||||
$includeNode->setName($name);
|
||||
$includeNode->setPid((int)$sysTemplateRow['pid']);
|
||||
if ($this->type === 'constants') {
|
||||
$includeNode->setLineStream($this->tokenizer->tokenize($sysTemplateRow['constants'] ?? ''));
|
||||
} else {
|
||||
$includeNode->setLineStream($this->tokenizer->tokenize($sysTemplateRow['config'] ?? ''));
|
||||
}
|
||||
if ($sysTemplateRow['root']) {
|
||||
$includeNode->setRoot(true);
|
||||
}
|
||||
$clear = $sysTemplateRow['clear'];
|
||||
if (($this->type === 'constants' && $clear & 1) || ($this->type === 'setup' && $clear & 2)) {
|
||||
$includeNode->setClear(true);
|
||||
}
|
||||
$this->handleSysTemplateRecordInclude($includeNode, $sysTemplateRow, $site);
|
||||
$this->treeFromTokenStreamBuilder->buildTree($includeNode, $this->type, $this->tokenizer);
|
||||
$this->cache?->set($cacheIdentifier, $this->prepareNodeForCache($includeNode));
|
||||
$rootNode->addChild($includeNode);
|
||||
}
|
||||
|
||||
return $rootNode;
|
||||
}
|
||||
|
||||
private function createSiteTemplateInclude(
|
||||
Site $site,
|
||||
string $cacheIdentifier
|
||||
): SiteTemplateInclude {
|
||||
$includeNode = new SiteTemplateInclude();
|
||||
$includeNode->setRoot(true);
|
||||
$includeNode->setClear(true);
|
||||
|
||||
$this->addScopedStaticsFromGlobals($includeNode, 'siteSets');
|
||||
$this->addContentRenderingFromGlobals($includeNode, 'TYPO3_CONF_VARS defaultContentRendering');
|
||||
|
||||
$sets = $this->setRegistry->getSets(...$site->getSets());
|
||||
if (count($sets) > 0) {
|
||||
$includeSetInclude = new IncludeStaticFileFileInclude();
|
||||
$includeSetInclude->setName('site:' . $site->getIdentifier() . ':sets');
|
||||
$includeSetInclude->setPath('site:' . $site->getIdentifier() . '/');
|
||||
foreach ($sets as $set) {
|
||||
if ($set->typoscript === null) {
|
||||
continue;
|
||||
}
|
||||
$this->handleSetInclude($includeSetInclude, rtrim($set->typoscript, '/') . '/', 'set:' . $set->name);
|
||||
}
|
||||
$includeNode->addChild($includeSetInclude);
|
||||
}
|
||||
|
||||
if ($this->type === 'constants') {
|
||||
$this->addDefaultTypoScriptConstantsFromSite($includeNode, $site);
|
||||
}
|
||||
|
||||
$siteTypoScript = $site->getTypoScript();
|
||||
$content = $this->type === 'constants' ? $siteTypoScript?->constants : $siteTypoScript?->setup;
|
||||
if ($content !== null) {
|
||||
$includeNode->setLineStream($this->tokenizer->tokenize($content));
|
||||
$this->treeFromTokenStreamBuilder->buildTree($includeNode, $this->type, $this->tokenizer, false);
|
||||
}
|
||||
|
||||
$includeNode->setName(sprintf(
|
||||
'[site:%s%s] %s',
|
||||
$site->getIdentifier(),
|
||||
$content === null ? '' : '/' . $this->type . '.typoscript',
|
||||
$site->getConfiguration()['websiteTitle'] ?? ''
|
||||
));
|
||||
|
||||
$this->cache?->set($cacheIdentifier, $this->prepareNodeForCache($includeNode));
|
||||
|
||||
return $includeNode;
|
||||
}
|
||||
|
||||
private function handleSetInclude(IncludeInterface $parentNode, string $path, string $label): void
|
||||
{
|
||||
$path = GeneralUtility::getFileAbsFileName($path);
|
||||
|
||||
// '/.../my_extension/Configuration/TypoScript/MyStaticInclude/include_static_file.txt'
|
||||
$includeStaticFileFileIncludePath = $path . 'include_static_file.txt';
|
||||
if (file_exists($path . 'include_static_file.txt')) {
|
||||
$includeStaticFileFileInclude = new IncludeStaticFileFileInclude();
|
||||
$includeStaticFileFileInclude->setName($label . ':include_static_file.txt');
|
||||
$includeStaticFileFileInclude->setPath($path . 'include_static_file.txt');
|
||||
$parentNode->addChild($includeStaticFileFileInclude);
|
||||
$includeStaticFileFileIncludeContent = (string)file_get_contents($includeStaticFileFileIncludePath);
|
||||
// @todo: There is no array_unique() for DB based include_static_file content?!
|
||||
$includeStaticFileFileIncludeArray = array_unique(GeneralUtility::trimExplode(',', $includeStaticFileFileIncludeContent, true));
|
||||
foreach ($includeStaticFileFileIncludeArray as $includeStaticFileFileIncludeString) {
|
||||
$this->handleSingleIncludeStaticFile($includeStaticFileFileInclude, $includeStaticFileFileIncludeString);
|
||||
}
|
||||
}
|
||||
|
||||
$fileName = $path . $this->type . '.typoscript';
|
||||
if (file_exists($fileName)) {
|
||||
$fileContent = file_get_contents($fileName);
|
||||
$fileNode = new FileInclude();
|
||||
$fileNode->setName($label . ':' . $this->type . '.typoscript');
|
||||
$fileNode->setPath($fileName);
|
||||
$fileNode->setLineStream($this->tokenizer->tokenize($fileContent));
|
||||
$this->treeFromTokenStreamBuilder->buildTree($fileNode, $this->type, $this->tokenizer, false);
|
||||
$parentNode->addChild($fileNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add includes defined in a sys_template record.
|
||||
*/
|
||||
private function handleSysTemplateRecordInclude(IncludeInterface $parentNode, array $row, ?SiteInterface $site): void
|
||||
{
|
||||
$this->includedSysTemplateUids[] = (int)$row['uid'];
|
||||
|
||||
$isRoot = (bool)$row['root'];
|
||||
$clearConstants = (int)$row['clear'] & 1;
|
||||
$clearSetup = (int)$row['clear'] & 2;
|
||||
$staticFileMode = (int)($row['static_file_mode']);
|
||||
$includeStaticAfterBasedOn = (bool)$row['includeStaticAfterBasedOn'];
|
||||
|
||||
if ($this->type === 'constants' && $clearConstants) {
|
||||
$this->addDefaultTypoScriptFromGlobals($parentNode);
|
||||
$this->addDefaultTypoScriptConstantsFromSite($parentNode, $site);
|
||||
}
|
||||
if ($this->type === 'setup' && $clearSetup) {
|
||||
$this->addDefaultTypoScriptFromGlobals($parentNode);
|
||||
}
|
||||
if ($staticFileMode === 3 && $isRoot) {
|
||||
$this->addExtensionStatics($parentNode);
|
||||
}
|
||||
if (!$includeStaticAfterBasedOn) {
|
||||
$this->handleIncludeStaticFileArray($parentNode, (string)$row['include_static_file']);
|
||||
}
|
||||
if (!empty($row['basedOn'])) {
|
||||
$this->handleIncludeBasedOnTemplates($parentNode, (string)$row['basedOn'], $site);
|
||||
}
|
||||
if ($includeStaticAfterBasedOn) {
|
||||
$this->handleIncludeStaticFileArray($parentNode, (string)$row['include_static_file']);
|
||||
}
|
||||
if ($staticFileMode === 1 || ($staticFileMode === 0 && $isRoot)) {
|
||||
$this->addExtensionStatics($parentNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle includes defined in a sys_template['include_static_file'] row. Extracted as
|
||||
* methods since it depends on 'includeStaticAfterBasedOn' field if this is included
|
||||
* *before* or *after* other 'basedOn' includes.
|
||||
*
|
||||
* The cache implemented here *does not* take the *content* of files into account.
|
||||
* This means changing a file *does not* automatically void the cache since that would
|
||||
* lead to lots of file_exists() and file_get_contents() calls in production.
|
||||
* Instances in development context should thus set the typoscript-cache to NullFrontend.
|
||||
* Note this cache-usage is the main-cache that kicks in whenever different sys_template
|
||||
* records include the same file. For instance, when multiple sites include ext:seo XmlSitemap,
|
||||
* the cache implementation here takes care the ext:seo subtree is calculated only once.
|
||||
*/
|
||||
private function handleIncludeStaticFileArray(IncludeInterface $parentNode, string $includeStaticFileString): void
|
||||
{
|
||||
$includeStaticFileIncludeArray = GeneralUtility::trimExplode(',', $includeStaticFileString, true);
|
||||
foreach ($includeStaticFileIncludeArray as $includeStaticFile) {
|
||||
$cacheIdentifier = preg_replace('/[^[:alnum:]]/u', '-', mb_strtolower($includeStaticFile)) . '-' . $this->type;
|
||||
if ($this->cache) {
|
||||
$node = $this->cache->require($cacheIdentifier);
|
||||
if ($node) {
|
||||
$parentNode->addChild($node);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$node = new IncludeStaticFileDatabaseInclude();
|
||||
$node->setName($includeStaticFile);
|
||||
$this->handleSingleIncludeStaticFile($node, $includeStaticFile);
|
||||
$this->cache?->set($cacheIdentifier, $this->prepareNodeForCache($node));
|
||||
$parentNode->addChild($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle includes defined in a sys_template['basedOn'] row.
|
||||
* Warning: Calls handleSysTemplateRecordInclude() recursive when another basedOn templates
|
||||
* record includes things again!
|
||||
*/
|
||||
private function handleIncludeBasedOnTemplates(IncludeInterface $parentNode, string $basedOnList, ?SiteInterface $site): void
|
||||
{
|
||||
$basedOnTemplateUids = GeneralUtility::intExplode(',', $basedOnList, true);
|
||||
// Filter uids that have been handled already.
|
||||
$basedOnTemplateUids = array_diff($basedOnTemplateUids, $this->includedSysTemplateUids);
|
||||
if (empty($basedOnTemplateUids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$basedOnTemplateRows = $this->getBasedOnSysTemplateRowsFromDatabase($basedOnTemplateUids);
|
||||
|
||||
foreach ($basedOnTemplateUids as $basedOnTemplateUid) {
|
||||
if (is_array($basedOnTemplateRows[$basedOnTemplateUid] ?? false)) {
|
||||
$sysTemplateRow = $basedOnTemplateRows[$basedOnTemplateUid];
|
||||
$this->includedSysTemplateUids[] = (int)$sysTemplateRow['uid'];
|
||||
$includeNode = new SysTemplateInclude();
|
||||
$name = '[sys_template:' . $sysTemplateRow['uid'] . '] ' . $sysTemplateRow['title'];
|
||||
$includeNode->setName($name);
|
||||
$includeNode->setPid((int)$sysTemplateRow['pid']);
|
||||
if ($this->type === 'constants') {
|
||||
$includeNode->setLineStream($this->tokenizer->tokenize($sysTemplateRow['constants'] ?? ''));
|
||||
} else {
|
||||
$includeNode->setLineStream($this->tokenizer->tokenize($sysTemplateRow['config'] ?? ''));
|
||||
}
|
||||
$this->treeFromTokenStreamBuilder->buildTree($includeNode, $this->type, $this->tokenizer);
|
||||
if ($sysTemplateRow['root']) {
|
||||
$includeNode->setRoot(true);
|
||||
}
|
||||
$clear = $sysTemplateRow['clear'];
|
||||
if (($this->type === 'constants' && $clear & 1)
|
||||
|| ($this->type === 'setup' && $clear & 2)
|
||||
) {
|
||||
$includeNode->setClear(true);
|
||||
}
|
||||
$parentNode->addChild($includeNode);
|
||||
$this->handleSysTemplateRecordInclude($includeNode, $sysTemplateRow, $site);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a single sys_template ['include_static_file'] include.
|
||||
* Looks up file "EXT:/My/Path/include_static_file.txt' in an extension and includes this.
|
||||
* Also loads "EXT:/My/Path/[constants|setup].[typoscript|ts|txt].
|
||||
* Warning: Recursive since an include_static_file.txt file can include other extension's include_static_file.txt again.
|
||||
* This method has no cache-layer usage on its own: handleSingleIncludeStaticFile() which calls this
|
||||
* method is the cache layer here.
|
||||
*/
|
||||
private function handleSingleIncludeStaticFile(IncludeInterface $parentNode, $includeStaticFileString): void
|
||||
{
|
||||
if (!PathUtility::isExtensionPath($includeStaticFileString)) {
|
||||
// Must start with 'EXT:'
|
||||
throw new \RuntimeException(
|
||||
'Single include_static_file does not start with "EXT:": ' . $includeStaticFileString,
|
||||
1651137904
|
||||
);
|
||||
}
|
||||
|
||||
// Cut off 'EXT:'
|
||||
$includeStaticFileWithoutExt = substr($includeStaticFileString, 4);
|
||||
$includeStaticFileExtKeyAndPath = GeneralUtility::trimExplode('/', $includeStaticFileWithoutExt, true, 2);
|
||||
if (empty($includeStaticFileExtKeyAndPath[0]) || empty($includeStaticFileExtKeyAndPath[1])) {
|
||||
throw new \RuntimeException(
|
||||
'Syntax of static includes is "EXT:extension_key/Path". Usually enforced as such by ExtensionManagementUtility::addStaticFile',
|
||||
1651138603
|
||||
);
|
||||
}
|
||||
$extensionKey = $includeStaticFileExtKeyAndPath[0];
|
||||
if (!ExtensionManagementUtility::isLoaded($extensionKey)) {
|
||||
return;
|
||||
}
|
||||
// example: '/.../my_extension/Configuration/TypoScript/MyStaticInclude/'
|
||||
$pathSegmentWithAppendedSlash = rtrim($includeStaticFileExtKeyAndPath[1]) . '/';
|
||||
$path = ExtensionManagementUtility::extPath($extensionKey, $pathSegmentWithAppendedSlash);
|
||||
|
||||
// '/.../my_extension/Configuration/TypoScript/MyStaticInclude/include_static_file.txt'
|
||||
$includeStaticFileFileIncludePath = $path . 'include_static_file.txt';
|
||||
if (file_exists($path . 'include_static_file.txt')) {
|
||||
$includeStaticFileFileInclude = new IncludeStaticFileFileInclude();
|
||||
$name = 'EXT:' . $extensionKey . '/' . $pathSegmentWithAppendedSlash . 'include_static_file.txt';
|
||||
$includeStaticFileFileInclude->setName($name);
|
||||
$includeStaticFileFileInclude->setPath($includeStaticFileString);
|
||||
$parentNode->addChild($includeStaticFileFileInclude);
|
||||
$includeStaticFileFileIncludeContent = (string)file_get_contents($includeStaticFileFileIncludePath);
|
||||
// @todo: There is no array_unique() for DB based include_static_file content?!
|
||||
$includeStaticFileFileIncludeArray = array_unique(GeneralUtility::trimExplode(',', $includeStaticFileFileIncludeContent, true));
|
||||
foreach ($includeStaticFileFileIncludeArray as $includeStaticFileFileIncludeString) {
|
||||
$this->handleSingleIncludeStaticFile($includeStaticFileFileInclude, $includeStaticFileFileIncludeString);
|
||||
}
|
||||
}
|
||||
|
||||
$extensions = ['.typoscript', '.ts', '.txt'];
|
||||
foreach ($extensions as $extension) {
|
||||
// '/.../my_extension/Configuration/TypoScript/MyStaticInclude/[constants|setup]' plus one of the allowed extensions like '.typoscript'
|
||||
$fileName = $path . $this->type . $extension;
|
||||
if (file_exists($fileName)) {
|
||||
$fileContent = file_get_contents($fileName);
|
||||
$fileNode = new FileInclude();
|
||||
$name = 'EXT:' . $extensionKey . '/' . $pathSegmentWithAppendedSlash . $this->type . $extension;
|
||||
$fileNode->setName($name);
|
||||
$fileNode->setPath($name);
|
||||
$fileNode->setLineStream($this->tokenizer->tokenize($fileContent));
|
||||
$this->treeFromTokenStreamBuilder->buildTree($fileNode, $this->type, $this->tokenizer);
|
||||
$parentNode->addChild($fileNode);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->enableStaticMagicIncludes) {
|
||||
$extensionKeyWithoutUnderscores = str_replace('_', '', $extensionKey);
|
||||
$this->addStaticMagicFromGlobals($parentNode, $extensionKeyWithoutUnderscores . '/' . $pathSegmentWithAppendedSlash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load 'EXT:my_extension/ext_typoscript_[constants|setup].typoscript'
|
||||
* of *all* loaded extensions if they exist.
|
||||
*/
|
||||
private function addExtensionStatics(IncludeInterface $parentNode): void
|
||||
{
|
||||
foreach ($this->packageManager->getActivePackages() as $package) {
|
||||
$extensionKey = $package->getPackageKey();
|
||||
$extensionKeyWithoutUnderscores = str_replace('_', '', $extensionKey);
|
||||
$file = $package->getPackagePath() . 'ext_typoscript_' . $this->type . '.typoscript';
|
||||
if (file_exists($file)) {
|
||||
$identifier = preg_replace('/[^[:alnum:]]/u', '-', 'ext-' . $extensionKey . '-ext-typoscript-' . $this->type . '-typoscript');
|
||||
if ($this->cache) {
|
||||
$node = $this->cache->require($identifier);
|
||||
if ($node) {
|
||||
$parentNode->addChild($node);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$fileContent = file_get_contents($file);
|
||||
$this->addStaticMagicFromGlobals($parentNode, $extensionKeyWithoutUnderscores);
|
||||
$node = new ExtensionStaticInclude();
|
||||
$node->setName('EXT:' . $extensionKey . '/ext_typoscript_' . $this->type . '.typoscript');
|
||||
$node->setPath('EXT:' . $extensionKey . '/ext_typoscript_' . $this->type . '.typoscript');
|
||||
$node->setLineStream($this->tokenizer->tokenize($fileContent));
|
||||
$this->treeFromTokenStreamBuilder->buildTree($node, $this->type, $this->tokenizer);
|
||||
$this->cache?->set($identifier, $this->prepareNodeForCache($node));
|
||||
$parentNode->addChild($node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load default constants TS from $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_[constants|setup]']
|
||||
* whenever 'root=1' is set for a sys_template.
|
||||
*/
|
||||
private function addDefaultTypoScriptFromGlobals(IncludeInterface $parentConstantNode): void
|
||||
{
|
||||
$defaultTypoScriptConstants = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $this->type] ?? '';
|
||||
if (!empty($defaultTypoScriptConstants)) {
|
||||
$cacheIdentifier = 'globals-defaulttyposcript-' . $this->type . '-' . hash('xxh3', $defaultTypoScriptConstants);
|
||||
if ($this->cache) {
|
||||
$node = $this->cache->require($cacheIdentifier);
|
||||
if ($node) {
|
||||
$parentConstantNode->addChild($node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$node = new DefaultTypoScriptInclude();
|
||||
$node->setName('TYPO3_CONF_VARS[\'FE\'][\'defaultTypoScript_' . $this->type . '\']');
|
||||
$node->setLineStream($this->tokenizer->tokenize($defaultTypoScriptConstants));
|
||||
$this->treeFromTokenStreamBuilder->buildTree($node, $this->type, $this->tokenizer);
|
||||
$this->cache?->set($cacheIdentifier, $this->prepareNodeForCache($node));
|
||||
$parentConstantNode->addChild($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load default TS constants from site configuration if that page has a site in rootline.
|
||||
*/
|
||||
private function addDefaultTypoScriptConstantsFromSite(IncludeInterface $parentConstantNode, ?SiteInterface $site): void
|
||||
{
|
||||
if (!$site instanceof Site) {
|
||||
return;
|
||||
}
|
||||
$siteConstants = '';
|
||||
$siteSettings = $site->getSettings();
|
||||
if ($siteSettings->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
$cacheIdentifier = 'site-constants-' . hash('xxh3', json_encode($siteSettings, JSON_THROW_ON_ERROR));
|
||||
if ($this->cache) {
|
||||
$node = $this->cache->require($cacheIdentifier);
|
||||
if ($node) {
|
||||
$parentConstantNode->addChild($node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$siteSettings = $siteSettings->getAllFlat();
|
||||
foreach ($siteSettings as $nodeIdentifier => $value) {
|
||||
$siteConstants .= $nodeIdentifier . ' = ' . $value . LF;
|
||||
}
|
||||
$node = new SiteInclude();
|
||||
$node->setName('Site constants settings of site "' . $site->getIdentifier() . '"');
|
||||
$node->setLineStream($this->tokenizer->tokenize($siteConstants));
|
||||
$this->cache?->set($cacheIdentifier, $this->prepareNodeForCache($node));
|
||||
$parentConstantNode->addChild($node);
|
||||
}
|
||||
|
||||
private function addScopedStaticsFromGlobals(IncludeInterface $parentNode, string $identifier): void
|
||||
{
|
||||
// defaultTypoScript_constants.' or defaultTypoScript_setup.'
|
||||
$source = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $this->type . '.'][$identifier] ?? null;
|
||||
if (!empty($source)) {
|
||||
$node = new DefaultTypoScriptMagicKeyInclude();
|
||||
$node->setName('TYPO3_CONF_VARS globals_defaultTypoScript_' . $this->type . '.' . $identifier);
|
||||
$node->setLineStream($this->tokenizer->tokenize($source));
|
||||
$this->treeFromTokenStreamBuilder->buildTree($node, $this->type, $this->tokenizer);
|
||||
$parentNode->addChild($node);
|
||||
}
|
||||
}
|
||||
|
||||
private function addContentRenderingFromGlobals(IncludeInterface $parentNode, string $name): void
|
||||
{
|
||||
$source = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $this->type . '.']['defaultContentRendering'] ?? null;
|
||||
if (!empty($source)) {
|
||||
$node = new DefaultTypoScriptMagicKeyInclude();
|
||||
$node->setName($name);
|
||||
$node->setLineStream($this->tokenizer->tokenize($source));
|
||||
$this->treeFromTokenStreamBuilder->buildTree($node, $this->type, $this->tokenizer);
|
||||
$parentNode->addChild($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A rather weird lookup in $GLOBALS['TYPO3_CONF_VARS']['FE'] for magic includes.
|
||||
* See ExtensionManagementUtility::addTypoScript() for more details on this.
|
||||
*/
|
||||
private function addStaticMagicFromGlobals(IncludeInterface $parentNode, string $identifier): void
|
||||
{
|
||||
$this->addScopedStaticsFromGlobals($parentNode, $identifier);
|
||||
// If this is a template of type "default content rendering", see if other extensions have added their TypoScript that should be included.
|
||||
if (in_array($identifier, $GLOBALS['TYPO3_CONF_VARS']['FE']['contentRenderingTemplates'], true)) {
|
||||
$this->addContentRenderingFromGlobals($parentNode, 'TYPO3_CONF_VARS defaultContentRendering ' . $this->type . ' for ' . $identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 'basedOn' sys_template sub-rows of sys_templates that use this.
|
||||
* Note the 'IN()' query implementation below delivers rows in *any* order. To preserve
|
||||
* basedOn list order, we re-index result rows by uid and then iterate on the original
|
||||
* order of $basedOnTemplateUids in handleIncludeBasedOnTemplates().
|
||||
*/
|
||||
private function getBasedOnSysTemplateRowsFromDatabase(array $basedOnTemplateUids): array
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_template');
|
||||
$queryBuilder->setRestrictions($this->getSysTemplateQueryRestrictionContainer());
|
||||
$basedOnTemplateRows = $queryBuilder
|
||||
->select('*')
|
||||
->from('sys_template')
|
||||
->where(
|
||||
$queryBuilder->expr()->in(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($basedOnTemplateUids, Connection::PARAM_INT_ARRAY)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
return array_combine(array_column($basedOnTemplateRows, 'uid'), $basedOnTemplateRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a cache identifier for a sys_template row.
|
||||
* This is a bit nifty: There are instances in the wild that add the same TypoScript
|
||||
* sys_template over and over again in a page tree to for instance toggle a single value.
|
||||
* Those content-identical template rows create only one cache entry: We create a hash
|
||||
* from the relevant row fields like 'constants' and 'config', but we do NOT include
|
||||
* the sys_template row 'uid' and 'pid'. So different sys_template rows with the same content
|
||||
* lead to the same identifier, and we cache that just once.
|
||||
*
|
||||
* One additional dependency influences the identifier as well: If the 'clear constants'
|
||||
* flag is set, this row will later trigger loading of constants from given site settings.
|
||||
* When two "first" template rows have the exact same field content in different sites, the
|
||||
* site identifier needs to be added to the hash to still create two different cache entries.
|
||||
*/
|
||||
private function getSysTemplateRowIdentifier(array $sysTemplateRow, ?SiteInterface $site): string
|
||||
{
|
||||
$siteIdentifier = 'dummy';
|
||||
if ($this->type === 'constants' && ((int)$sysTemplateRow['clear'] & 1) && $site !== null) {
|
||||
$siteIdentifier = $site->getIdentifier();
|
||||
}
|
||||
$cacheRelevantSysTemplateRowValues = [
|
||||
'root' => (int)$sysTemplateRow['root'],
|
||||
'clear' => (int)$sysTemplateRow['clear'],
|
||||
'include_static_file' => (string)$sysTemplateRow['include_static_file'],
|
||||
'constants' => (string)$sysTemplateRow['constants'],
|
||||
'config' => (string)$sysTemplateRow['config'],
|
||||
'basedOn' => (string)$sysTemplateRow['basedOn'],
|
||||
'includeStaticAfterBasedOn' => (int)$sysTemplateRow['includeStaticAfterBasedOn'],
|
||||
'static_file_mode' => (int)$sysTemplateRow['static_file_mode'],
|
||||
'siteIdentifier' => $siteIdentifier,
|
||||
];
|
||||
return hash('xxh3', json_encode($cacheRelevantSysTemplateRowValues, JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
private function prepareNodeForCache(IncludeInterface $node): string
|
||||
{
|
||||
return 'return unserialize(\'' . addcslashes(serialize($node), '\'\\') . '\');';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sys_template record query builder restrictions.
|
||||
* Allows hidden records if enabled in context.
|
||||
*/
|
||||
private function getSysTemplateQueryRestrictionContainer(): DefaultRestrictionContainer
|
||||
{
|
||||
$restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
|
||||
if ($this->context->getPropertyFromAspect('visibility', 'includeHiddenContent', false)) {
|
||||
$restrictionContainer->removeByType(HiddenRestriction::class);
|
||||
}
|
||||
return $restrictionContainer;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user