*/ 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; } }