‪TYPO3CMS  ‪main
PageTsConfigActiveController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Container\ContainerInterface;
21 use Psr\Http\Message\ResponseInterface;
22 use Psr\Http\Message\ServerRequestInterface;
28 use TYPO3\CMS\Backend\Utility\BackendUtility;
47 
53 #[AsController]
55 {
56  public function ‪__construct(
57  private readonly ContainerInterface $container,
58  private readonly ‪UriBuilder $uriBuilder,
59  private readonly ‪ModuleTemplateFactory $moduleTemplateFactory,
60  private readonly ‪TsConfigTreeBuilder $tsConfigTreeBuilder,
61  ) {}
62 
63  public function ‪handleRequest(ServerRequestInterface $request): ResponseInterface
64  {
65  $backendUser = $this->‪getBackendUser();
66  $languageService = $this->‪getLanguageService();
67 
68  $queryParams = $request->getQueryParams();
69  $parsedBody = $request->getParsedBody();
70 
71  $currentModule = $request->getAttribute('module');
72  $currentModuleIdentifier = $currentModule->getIdentifier();
73  $moduleData = $request->getAttribute('moduleData');
74 
75  $pageUid = (int)($parsedBody['id'] ?? $queryParams['id'] ?? 0);
76  $pageRecord = BackendUtility::readPageAccess($pageUid, '1=1') ?: [];
77  if (empty($pageRecord)) {
78  // Redirect to overview if page could not be determined.
79  // Edge case if page has been removed meanwhile.
80  BackendUtility::setUpdateSignal('updatePageTree');
81  return new ‪RedirectResponse($this->uriBuilder->buildUriFromRoute('pagetsconfig_pages'));
82  }
83 
84  // Force boolean toggles to bool and init further get/post vars
85  if ($moduleData->clean('displayConstantSubstitutions', [true, false])) {
86  $backendUser->pushModuleData($currentModuleIdentifier, $moduleData->toArray());
87  }
88  $displayConstantSubstitutions = $moduleData->get('displayConstantSubstitutions');
89  if ($moduleData->clean('displayComments', [true, false])) {
90  $backendUser->pushModuleData($currentModuleIdentifier, $moduleData->toArray());
91  }
92  $displayComments = $moduleData->get('displayComments');
93  if ($moduleData->clean('sortAlphabetically', [true, false])) {
94  $backendUser->pushModuleData($currentModuleIdentifier, $moduleData->toArray());
95  }
96  $sortAlphabetically = $moduleData->get('sortAlphabetically');
97 
98  // Prepare site constants if any
99  $site = $request->getAttribute('site');
100  $siteSettingsAst = null;
101  $siteSettingsFlat = [];
102  if ($site instanceof ‪Site && !$site->‪getSettings()->isEmpty()) {
103  $siteSettings = $site->getSettings()->getAllFlat();
104  $siteConstants = '';
105  foreach ($siteSettings as $nodeIdentifier => $value) {
106  $siteConstants .= $nodeIdentifier . ' = ' . $value . LF;
107  }
108  $siteSettingsNode = new ‪SiteInclude();
109  $siteSettingsNode->setName('Site constants settings of site "' . $site->getIdentifier() . '"');
110  $siteSettingsNode->setLineStream((new ‪LosslessTokenizer())->tokenize($siteConstants));
111  $siteSettingsTreeRoot = new ‪RootInclude();
112  $siteSettingsTreeRoot->addChild($siteSettingsNode);
113  $astBuilderVisitor = $this->container->get(IncludeTreeAstBuilderVisitor::class);
114  $includeTreeTraverser = new ‪IncludeTreeTraverser();
115  $includeTreeTraverser->traverse($siteSettingsTreeRoot, [$astBuilderVisitor]);
116  $siteSettingsAst = $astBuilderVisitor->getAst();
117  // Trigger unique identifier creation for entire tree
118  $siteSettingsAst->setIdentifier('pageTsConfig-siteSettingsAst');
119  $siteSettingsFlat = $siteSettingsAst->flatten();
120  if ($sortAlphabetically) {
121  // Traverse AST to sort if needed
122  $astTraverser = new ‪AstTraverser();
123  $astTraverser->traverse($siteSettingsAst, [new ‪AstSortChildrenVisitor()]);
124  }
125  }
126 
127  // Base page TSconfig tree
128  $rootLine = BackendUtility::BEgetRootLine($pageUid, '', true);
129  ksort($rootLine);
130  $pagesTsConfigTree = $this->tsConfigTreeBuilder->getPagesTsConfigTree($rootLine, new ‪LosslessTokenizer());
131 
132  // Overload tree with user TSconfig if any
133  $userTsConfig = $backendUser->getUserTsConfig();
134  if ($userTsConfig === null) {
135  throw new \RuntimeException('User TSconfig not initialized', 1674609098);
136  }
137  $userTsConfigAst = $userTsConfig->getUserTsConfigTree();
138  $userTsConfigPageOverrides = '';
139  // @todo: Ugly, similar in PageTsConfigFactory.
140  $userTsConfigFlat = $userTsConfigAst->flatten();
141  foreach ($userTsConfigFlat as $userTsConfigIdentifier => $userTsConfigValue) {
142  if (str_starts_with($userTsConfigIdentifier, 'page.')) {
143  $userTsConfigPageOverrides .= substr($userTsConfigIdentifier, 5) . ' = ' . $userTsConfigValue . chr(10);
144  }
145  }
146  if (!empty($userTsConfigPageOverrides)) {
147  $includeNode = new ‪TsConfigInclude();
148  $includeNode->setName('pageTsConfig-overrides-by-userTsConfig');
149  $includeNode->setLineStream((new ‪LosslessTokenizer())->tokenize($userTsConfigPageOverrides));
150  $pagesTsConfigTree->addChild($includeNode);
151  }
152 
153  // Set enabled conditions in page TSconfig include tree and let it handle constant substitutions in page TSconfig conditions.
154  $pageTsConfigConditions = $this->‪handleToggledPageTsConfigConditions($pagesTsConfigTree, $moduleData, $parsedBody, $siteSettingsFlat);
155  $conditionEnforcerVisitor = new ‪IncludeTreeConditionEnforcerVisitor();
156  $conditionEnforcerVisitor->setEnabledConditions(array_column(array_filter($pageTsConfigConditions, static fn(array $condition): bool => (bool)$condition['active']), 'value'));
157  $treeTraverser = new ‪IncludeTreeTraverser();
158  $treeTraverser->traverse($pagesTsConfigTree, [$conditionEnforcerVisitor]);
159 
160  // Create AST with constants from site and conditions
161  $includeTreeTraverser = new ‪ConditionVerdictAwareIncludeTreeTraverser();
162  $astBuilderVisitor = $this->container->get(IncludeTreeCommentAwareAstBuilderVisitor::class);
163  $astBuilderVisitor->setFlatConstants($siteSettingsFlat);
164  $includeTreeTraverser->traverse($pagesTsConfigTree, [$astBuilderVisitor]);
165  $pageTsConfigAst = $astBuilderVisitor->getAst();
166  // Trigger unique identifier creation for entire tree
167  $pageTsConfigAst->setIdentifier('pageTsConfig');
168  if ($sortAlphabetically) {
169  // Traverse AST to sort if needed
170  $astTraverser = new ‪AstTraverser();
171  $astTraverser->traverse($pageTsConfigAst, [new ‪AstSortChildrenVisitor()]);
172  }
173 
174  $view = $this->moduleTemplateFactory->create($request);
175  $view->setTitle($languageService->sL($currentModule->getTitle()), $pageRecord['title'] ?? ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? '');
176  $view->getDocHeaderComponent()->setMetaInformation($pageRecord);
177  $this->‪addShortcutButtonToDocHeader($view, $currentModuleIdentifier, $pageRecord, $pageUid);
178  $view->makeDocHeaderModuleMenu(['id' => $pageUid]);
179  $view->assignMultiple([
180  'pageUid' => $pageUid,
181  'pageTitle' => $pageRecord['title'] ?? ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? '',
182  'displayConstantSubstitutions' => $displayConstantSubstitutions,
183  'displayComments' => $displayComments,
184  'sortAlphabetically' => $sortAlphabetically,
185  'siteSettingsAst' => $siteSettingsAst,
186  'pageTsConfigAst' => $pageTsConfigAst,
187  'pageTsConfigConditions' => $pageTsConfigConditions,
188  'pageTsConfigConditionsActiveCount' => count(array_filter($pageTsConfigConditions, static fn(array $condition): bool => (bool)$condition['active'])),
189  ]);
190  return $view->renderResponse('PageTsConfig/Active');
191  }
192 
198  private function ‪handleToggledPageTsConfigConditions(‪RootInclude $pageTsConfigTree, ‪ModuleData $moduleData, ?array $parsedBody, array $flattenedConstants): array
199  {
200  $treeTraverser = new ‪IncludeTreeTraverser();
201  $treeTraverserVisitors = [];
202  $setupConditionConstantSubstitutionVisitor = new ‪IncludeTreeSetupConditionConstantSubstitutionVisitor();
203  $setupConditionConstantSubstitutionVisitor->setFlattenedConstants($flattenedConstants);
204  $treeTraverserVisitors[] = $setupConditionConstantSubstitutionVisitor;
205  $conditionAggregatorVisitor = new ‪IncludeTreeConditionAggregatorVisitor();
206  $treeTraverserVisitors[] = $conditionAggregatorVisitor;
207  $treeTraverser->traverse($pageTsConfigTree, $treeTraverserVisitors);
208  $pageTsConfigConditions = $conditionAggregatorVisitor->getConditions();
209  $conditionsFromPost = $parsedBody['pageTsConfigConditions'] ?? [];
210  $conditionsFromModuleData = array_flip((array)$moduleData->‪get('pageTsConfigConditions'));
211  $conditions = [];
212  foreach ($pageTsConfigConditions as $condition) {
213  $conditionHash = hash('xxh3', $condition['value']);
214  $conditionActive = array_key_exists($conditionHash, $conditionsFromModuleData);
215  // Note we're not feeding the post values directly to module data, but filter
216  // them through available conditions to prevent polluting module data with
217  // manipulated post values.
218  if (($conditionsFromPost[$conditionHash] ?? null) === '0') {
219  unset($conditionsFromModuleData[$conditionHash]);
220  $conditionActive = false;
221  } elseif (($conditionsFromPost[$conditionHash] ?? null) === '1') {
222  $conditionsFromModuleData[$conditionHash] = true;
223  $conditionActive = true;
224  }
225  $conditions[] = [
226  'value' => $condition['value'],
227  'originalValue' => $condition['originalValue'],
228  'hash' => $conditionHash,
229  'active' => $conditionActive,
230  ];
231  }
232  if ($conditionsFromPost) {
233  $moduleData->‪set('pageTsConfigConditions', array_keys($conditionsFromModuleData));
234  $this->‪getBackendUser()->pushModuleData($moduleData->‪getModuleIdentifier(), $moduleData->‪toArray());
235  }
236  return $conditions;
237  }
238 
239  private function ‪addShortcutButtonToDocHeader(‪ModuleTemplate $view, string $moduleIdentifier, array $pageInfo, int $pageUid): void
240  {
241  $languageService = $this->‪getLanguageService();
242  $buttonBar = $view->‪getDocHeaderComponent()->getButtonBar();
243  $shortcutTitle = sprintf(
244  '%s: %s [%d]',
245  $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_pagetsconfig.xlf:module.pagetsconfig_active'),
246  BackendUtility::getRecordTitle('pages', $pageInfo),
247  $pageUid
248  );
249  $shortcutButton = $buttonBar->makeShortcutButton()
250  ->setRouteIdentifier($moduleIdentifier)
251  ->setDisplayName($shortcutTitle)
252  ->setArguments(['id' => $pageUid]);
253  $buttonBar->addButton($shortcutButton);
254  }
255 
257  {
258  return ‪$GLOBALS['LANG'];
259  }
260 
262  {
263  return ‪$GLOBALS['BE_USER'];
264  }
265 }
‪TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeSetupConditionConstantSubstitutionVisitor
Definition: IncludeTreeSetupConditionConstantSubstitutionVisitor.php:36
‪TYPO3\CMS\Core\TypoScript\AST\Visitor\AstSortChildrenVisitor
Definition: AstSortChildrenVisitor.php:30
‪TYPO3\CMS\Core\TypoScript\Tokenizer\LosslessTokenizer
Definition: LosslessTokenizer.php:61
‪TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeCommentAwareAstBuilderVisitor
Definition: IncludeTreeCommentAwareAstBuilderVisitor.php:41
‪TYPO3\CMS\Backend\Controller\PageTsConfig
Definition: PageTsConfigActiveController.php:18
‪TYPO3\CMS\Backend\Template\ModuleTemplateFactory
Definition: ModuleTemplateFactory.php:33
‪TYPO3\CMS\Backend\Controller\PageTsConfig\PageTsConfigActiveController\getBackendUser
‪getBackendUser()
Definition: PageTsConfigActiveController.php:261
‪TYPO3\CMS\Core\TypoScript\IncludeTree\Traverser\IncludeTreeTraverser
Definition: IncludeTreeTraverser.php:30
‪TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeConditionEnforcerVisitor
Definition: IncludeTreeConditionEnforcerVisitor.php:32
‪TYPO3\CMS\Backend\Module\ModuleData\toArray
‪toArray()
Definition: ModuleData.php:122
‪TYPO3\CMS\Backend\Module\ModuleData
Definition: ModuleData.php:30
‪TYPO3\CMS\Backend\Module\ModuleData\get
‪get(string $propertyName, mixed $default=null)
Definition: ModuleData.php:56
‪TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeAstBuilderVisitor
Definition: IncludeTreeAstBuilderVisitor.php:39
‪TYPO3\CMS\Backend\Template\ModuleTemplate
Definition: ModuleTemplate.php:46
‪TYPO3\CMS\Core\Site\Entity\Site
Definition: Site.php:42
‪TYPO3\CMS\Backend\Controller\PageTsConfig\PageTsConfigActiveController\__construct
‪__construct(private readonly ContainerInterface $container, private readonly UriBuilder $uriBuilder, private readonly ModuleTemplateFactory $moduleTemplateFactory, private readonly TsConfigTreeBuilder $tsConfigTreeBuilder,)
Definition: PageTsConfigActiveController.php:56
‪TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\SiteInclude
Definition: SiteInclude.php:25
‪TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\TsConfigInclude
Definition: TsConfigInclude.php:25
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Backend\Module\ModuleData\set
‪set(string $propertyName, mixed $value)
Definition: ModuleData.php:66
‪TYPO3\CMS\Core\Http\RedirectResponse
Definition: RedirectResponse.php:30
‪TYPO3\CMS\Backend\Template\ModuleTemplate\getDocHeaderComponent
‪getDocHeaderComponent()
Definition: ModuleTemplate.php:181
‪TYPO3\CMS\Backend\Controller\PageTsConfig\PageTsConfigActiveController\handleRequest
‪handleRequest(ServerRequestInterface $request)
Definition: PageTsConfigActiveController.php:63
‪TYPO3\CMS\Backend\Controller\PageTsConfig\PageTsConfigActiveController
Definition: PageTsConfigActiveController.php:55
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Controller\PageTsConfig\PageTsConfigActiveController\handleToggledPageTsConfigConditions
‪handleToggledPageTsConfigConditions(RootInclude $pageTsConfigTree, ModuleData $moduleData, ?array $parsedBody, array $flattenedConstants)
Definition: PageTsConfigActiveController.php:198
‪TYPO3\CMS\Core\TypoScript\IncludeTree\Traverser\ConditionVerdictAwareIncludeTreeTraverser
Definition: ConditionVerdictAwareIncludeTreeTraverser.php:38
‪TYPO3\CMS\Backend\Attribute\AsController
Definition: AsController.php:25
‪TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\RootInclude
Definition: RootInclude.php:27
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Backend\Controller\PageTsConfig\PageTsConfigActiveController\addShortcutButtonToDocHeader
‪addShortcutButtonToDocHeader(ModuleTemplate $view, string $moduleIdentifier, array $pageInfo, int $pageUid)
Definition: PageTsConfigActiveController.php:239
‪TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeConditionAggregatorVisitor
Definition: IncludeTreeConditionAggregatorVisitor.php:32
‪TYPO3\CMS\Core\TypoScript\AST\Traverser\AstTraverser
Definition: AstTraverser.php:31
‪TYPO3\CMS\Core\Site\Entity\Site\getSettings
‪getSettings()
Definition: Site.php:299
‪TYPO3\CMS\Backend\Module\ModuleData\getModuleIdentifier
‪getModuleIdentifier()
Definition: ModuleData.php:51
‪TYPO3\CMS\Core\TypoScript\IncludeTree\TsConfigTreeBuilder
Definition: TsConfigTreeBuilder.php:41
‪TYPO3\CMS\Backend\Controller\PageTsConfig\PageTsConfigActiveController\getLanguageService
‪getLanguageService()
Definition: PageTsConfigActiveController.php:256