‪TYPO3CMS  ‪main
RequestHandler.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\EventDispatcher\EventDispatcherInterface;
21 use Psr\Http\Message\ResponseInterface;
22 use Psr\Http\Message\ServerRequestInterface;
23 use Psr\Http\Server\RequestHandlerInterface;
48 
70 class ‪RequestHandler implements RequestHandlerInterface
71 {
72  public function ‪__construct(
73  private readonly EventDispatcherInterface $eventDispatcher,
74  private readonly ‪ListenerProvider $listenerProvider,
75  private readonly ‪TimeTracker $timeTracker,
76  private readonly ‪FilePathSanitizer $filePathSanitizer,
77  private readonly ‪TypoScriptService $typoScriptService,
78  ) {
79  }
80 
90  protected function ‪resetGlobalsToCurrentRequest(ServerRequestInterface $request)
91  {
92  if ($request->getQueryParams() !== $_GET) {
93  $queryParams = $request->getQueryParams();
94  $_GET = $queryParams;
95  ‪$GLOBALS['HTTP_GET_VARS'] = $_GET;
96  }
97  if ($request->getMethod() === 'POST') {
98  $parsedBody = $request->getParsedBody();
99  if (is_array($parsedBody) && $parsedBody !== $_POST) {
100  $_POST = $parsedBody;
101  ‪$GLOBALS['HTTP_POST_VARS'] = $_POST;
102  }
103  }
104  ‪$GLOBALS['TYPO3_REQUEST'] = $request;
105  }
106 
110  public function ‪handle(ServerRequestInterface $request): ResponseInterface
111  {
112  $controller = $request->getAttribute('frontend.controller');
113 
114  $this->‪resetGlobalsToCurrentRequest($request);
115 
116  // Generate page
117  if ($controller->isGeneratePage()) {
118  $this->timeTracker->push('Page generation');
119 
120  // forward `ConsumableString` containing a nonce to `PageRenderer`
121  $nonce = $request->getAttribute('nonce');
122  $this->‪getPageRenderer()->setNonce($nonce);
123 
124  $controller->generatePage_preProcessing();
125  $controller->preparePageContentGeneration($request);
126 
127  // Make sure all FAL resources are prefixed with absPrefPrefix
128  $this->listenerProvider->addListener(
129  GeneratePublicUrlForResourceEvent::class,
130  PublicUrlPrefixer::class,
131  'prefixWithAbsRefPrefix'
132  );
133 
134  // Content generation
135  $this->timeTracker->incStackPointer();
136  $this->timeTracker->push('Page generation PAGE object');
137 
138  $controller->content = $this->‪generatePageContent($controller, $request);
139 
140  $this->timeTracker->pull($this->timeTracker->LR ? $controller->content : '');
141  $this->timeTracker->decStackPointer();
142 
143  // In case the nonce value was actually consumed during the rendering process, add a
144  // permanent substitution of the current value (that will be cached), with a future
145  // value (that will be generated and issued in the HTTP CSP header).
146  // Besides that, the same handling is triggered in case there are other uncached items
147  // already - this is due to the fact that the `PageRenderer` state has been serialized
148  // before and note executed via `$pageRenderer->render()` and did not consume any nonce values
149  // (see serialization in `generatePageContent()`).
150  if ($nonce instanceof ‪ConsumableString && (count($nonce) > 0 || $controller->isINTincScript())) {
151  // nonce was consumed
152  $controller->config['INTincScript'][] = [
153  'target' => NonceValueSubstitution::class . '->substituteNonce',
154  'parameters' => ['nonce' => $nonce->value],
155  'permanent' => true,
156  ];
157  }
158 
159  $controller->generatePage_postProcessing($request);
160  $this->timeTracker->pull();
161  }
162  $controller->releaseLocks();
163 
164  // Render non-cached page parts by replacing placeholders which are taken from cache or added during page generation
165  if ($controller->isINTincScript()) {
166  if (!$controller->isGeneratePage()) {
167  // When the page was generated, this was already called. Avoid calling this twice.
168  $controller->preparePageContentGeneration($request);
169 
170  // Make sure all FAL resources are prefixed with absPrefPrefix
171  $this->listenerProvider->addListener(
172  GeneratePublicUrlForResourceEvent::class,
173  PublicUrlPrefixer::class,
174  'prefixWithAbsRefPrefix'
175  );
176  }
177  $this->timeTracker->push('Non-cached objects');
178  $controller->INTincScript($request);
179  $this->timeTracker->pull();
180  }
181 
182  // Create a default Response object and add headers and body to it
183  $response = new ‪Response();
184  $response = $controller->applyHttpHeadersToResponse($response);
185  $this->‪displayPreviewInfoMessage($controller);
186  $response->getBody()->write($controller->content);
187  return $response;
188  }
189 
194  protected function ‪generatePageContent(‪TypoScriptFrontendController $controller, ServerRequestInterface $request): string
195  {
196  // Generate the main content between the <body> tags
197  // This has to be done first, as some additional TSFE-related code could have been written
198  $pageContent = $this->‪generatePageBodyContent($controller);
199  // If 'disableAllHeaderCode' is set, all the pageRenderer settings are not evaluated
200  if ($controller->config['config']['disableAllHeaderCode'] ?? false) {
201  return $pageContent;
202  }
203  // Now, populate pageRenderer with all additional data
204  $this->‪processHtmlBasedRenderingSettings($controller, $controller->‪getLanguage(), $request);
205  $pageRenderer = $this->‪getPageRenderer();
206  // Add previously generated page content within the <body> tag afterwards
207  $pageRenderer->addBodyContent(LF . $pageContent);
208  if ($controller->‪isINTincScript()) {
209  // Store the serialized pageRenderer state in configuration
210  $controller->config['INTincScript_ext']['pageRendererState'] = serialize($pageRenderer->getState());
211  // Store the serialized AssetCollector state in configuration
212  $controller->config['INTincScript_ext']['assetCollectorState'] = serialize(GeneralUtility::makeInstance(AssetCollector::class)->getState());
213  // Render complete page, keep placeholders for JavaScript and CSS
214  return $pageRenderer->renderPageWithUncachedObjects($controller->config['INTincScript_ext']['divKey'] ?? '');
215  }
216  // Render complete page
217  return $pageRenderer->render();
218  }
219 
225  protected function ‪generatePageBodyContent(‪TypoScriptFrontendController $controller): string
226  {
227  $pageContent = $controller->cObj->cObjGet($controller->pSetup) ?: '';
228  if ($controller->pSetup['wrap'] ?? false) {
229  $pageContent = $controller->cObj->wrap($pageContent, $controller->pSetup['wrap']);
230  }
231  if ($controller->pSetup['stdWrap.'] ?? false) {
232  $pageContent = $controller->cObj->stdWrap($pageContent, $controller->pSetup['stdWrap.']);
233  }
234  return $pageContent;
235  }
236 
244  protected function ‪processHtmlBasedRenderingSettings(‪TypoScriptFrontendController $controller, ‪SiteLanguage $siteLanguage, ServerRequestInterface $request): void
245  {
246  $pageRenderer = $this->‪getPageRenderer();
247  if ($controller->config['config']['moveJsFromHeaderToFooter'] ?? false) {
248  $pageRenderer->enableMoveJsFromHeaderToFooter();
249  }
250  if ($controller->config['config']['pageRendererTemplateFile'] ?? false) {
251  try {
252  $file = $this->filePathSanitizer->sanitize($controller->config['config']['pageRendererTemplateFile'], true);
253  $pageRenderer->setTemplateFile($file);
254  } catch (‪Exception) {
255  // do nothing
256  }
257  }
258  ‪$headerComment = trim($controller->config['config']['headerComment'] ?? '');
259  if (‪$headerComment) {
260  $pageRenderer->addInlineComment("\t" . str_replace(LF, LF . "\t", ‪$headerComment) . LF);
261  }
262  $htmlTagAttributes = [];
263 
264  if ($siteLanguage->‪getLocale()->isRightToLeftLanguageDirection()) {
265  $htmlTagAttributes['dir'] = 'rtl';
266  }
267  $docType = $pageRenderer->getDocType();
268  // Setting document type:
269  $docTypeParts = [];
270  $xmlDocument = true;
271  // Part 1: XML prologue
272  $xmlPrologue = (string)($controller->config['config']['xmlprologue'] ?? '');
273  switch ($xmlPrologue) {
274  case 'none':
275  $xmlDocument = false;
276  break;
277  case 'xml_10':
278  case 'xml_11':
279  case '':
280  if ($docType->isXmlCompliant()) {
281  $docTypeParts[] = $docType->getXmlPrologue();
282  } else {
283  $xmlDocument = false;
284  }
285  break;
286  default:
287  $docTypeParts[] = $xmlPrologue;
288  }
289  // Part 2: DTD
290  if ($docType->getDoctypeDeclaration() !== '') {
291  $docTypeParts[] = $docType->getDoctypeDeclaration();
292  }
293  if (!empty($docTypeParts)) {
294  $pageRenderer->setXmlPrologAndDocType(implode(LF, $docTypeParts));
295  }
296  // See https://www.w3.org/International/questions/qa-html-language-declarations.en.html#attributes
297  $htmlTagAttributes[$docType->isXmlCompliant() ? 'xml:lang' : 'lang'] = $siteLanguage->‪getLocale()->getLanguageCode();
298 
299  if ($docType->isXmlCompliant() || $docType === DocType::html5 && $xmlDocument) {
300  // We add this to HTML5 to achieve a slightly better backwards compatibility
301  $htmlTagAttributes['xmlns'] = 'http://www.w3.org/1999/xhtml';
302  if (is_array($controller->config['config']['namespaces.'] ?? null)) {
303  foreach ($controller->config['config']['namespaces.'] as $prefix => $uri) {
304  // $uri gets htmlspecialchared later
305  $htmlTagAttributes['xmlns:' . htmlspecialchars($prefix)] = $uri;
306  }
307  }
308  }
309  // Begin header section:
310  $htmlTag = $this->‪generateHtmlTag($htmlTagAttributes, $controller->config['config'] ?? [], $controller->cObj);
311  $pageRenderer->setHtmlTag($htmlTag);
312  // Head tag:
313  $headTag = $controller->pSetup['headTag'] ?? '<head>';
314  if (isset($controller->pSetup['headTag.'])) {
315  $headTag = $controller->cObj->stdWrap($headTag, $controller->pSetup['headTag.']);
316  }
317  $pageRenderer->setHeadTag($headTag);
318  $pageRenderer->addInlineComment(GeneralUtility::makeInstance(Typo3Information::class)->getInlineHeaderComment());
319  if ($controller->pSetup['shortcutIcon'] ?? false) {
320  try {
321  $favIcon = $this->filePathSanitizer->sanitize($controller->pSetup['shortcutIcon']);
322  $iconFileInfo = GeneralUtility::makeInstance(ImageInfo::class, ‪Environment::getPublicPath() . '/' . $favIcon);
323  if ($iconFileInfo->isFile()) {
324  $iconMimeType = $iconFileInfo->getMimeType();
325  if ($iconMimeType) {
326  $iconMimeType = ' type="' . $iconMimeType . '"';
327  $pageRenderer->setIconMimeType($iconMimeType);
328  }
329  $pageRenderer->setFavIcon(‪PathUtility::getAbsoluteWebPath($controller->absRefPrefix . $favIcon));
330  }
331  } catch (‪Exception) {
332  // do nothing
333  }
334  }
335  // Including CSS files
336  $typoScriptSetupArray = $request->getAttribute('frontend.typoscript')->getSetupArray();
337  if (is_array($typoScriptSetupArray['plugin.'] ?? null)) {
338  $stylesFromPlugins = '';
339  foreach ($typoScriptSetupArray['plugin.'] as $key => $iCSScode) {
340  if (is_array($iCSScode)) {
341  if (($iCSScode['_CSS_DEFAULT_STYLE'] ?? false) && empty($controller->config['config']['removeDefaultCss'])) {
342  $cssDefaultStyle = $controller->cObj->stdWrapValue('_CSS_DEFAULT_STYLE', $iCSScode ?? []);
343  $stylesFromPlugins .= '/* default styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssDefaultStyle . LF;
344  }
345  }
346  }
347  if (!empty($stylesFromPlugins)) {
348  $this->‪addCssToPageRenderer($controller, $stylesFromPlugins, false, 'InlineDefaultCss');
349  }
350  }
351  /**********************************************************************/
352  /* config.includeCSS / config.includeCSSLibs
353  /**********************************************************************/
354  if (is_array($controller->pSetup['includeCSS.'] ?? null)) {
355  foreach ($controller->pSetup['includeCSS.'] as $key => $CSSfile) {
356  if (!is_array($CSSfile)) {
357  $cssFileConfig = &$controller->pSetup['includeCSS.'][$key . '.'];
358  if (isset($cssFileConfig['if.']) && !$controller->cObj->checkIf($cssFileConfig['if.'])) {
359  continue;
360  }
361  if ($cssFileConfig['external'] ?? false) {
362  $ss = $CSSfile;
363  } else {
364  try {
365  $ss = $this->filePathSanitizer->sanitize($CSSfile, true);
366  } catch (‪Exception) {
367  $ss = null;
368  }
369  }
370  if ($ss) {
371  $additionalAttributes = $cssFileConfig ?? [];
372  unset(
373  $additionalAttributes['if.'],
374  $additionalAttributes['alternate'],
375  $additionalAttributes['media'],
376  $additionalAttributes['title'],
377  $additionalAttributes['external'],
378  $additionalAttributes['inline'],
379  $additionalAttributes['disableCompression'],
380  $additionalAttributes['excludeFromConcatenation'],
381  $additionalAttributes['allWrap'],
382  $additionalAttributes['allWrap.'],
383  $additionalAttributes['forceOnTop'],
384  );
385  $pageRenderer->addCssFile(
386  $ss,
387  ($cssFileConfig['alternate'] ?? false) ? 'alternate stylesheet' : 'stylesheet',
388  ($cssFileConfig['media'] ?? false) ?: 'all',
389  ($cssFileConfig['title'] ?? false) ?: '',
390  empty($cssFileConfig['external']) && empty($cssFileConfig['inline']) && empty($cssFileConfig['disableCompression']),
391  (bool)($cssFileConfig['forceOnTop'] ?? false),
392  $cssFileConfig['allWrap'] ?? '',
393  ($cssFileConfig['excludeFromConcatenation'] ?? false) || ($cssFileConfig['inline'] ?? false),
394  $cssFileConfig['allWrap.']['splitChar'] ?? '|',
395  (bool)($cssFileConfig['inline'] ?? false),
396  $additionalAttributes
397  );
398  }
399  unset($cssFileConfig);
400  }
401  }
402  }
403  if (is_array($controller->pSetup['includeCSSLibs.'] ?? null)) {
404  foreach ($controller->pSetup['includeCSSLibs.'] as $key => $CSSfile) {
405  if (!is_array($CSSfile)) {
406  $cssFileConfig = &$controller->pSetup['includeCSSLibs.'][$key . '.'];
407  if (isset($cssFileConfig['if.']) && !$controller->cObj->checkIf($cssFileConfig['if.'])) {
408  continue;
409  }
410  if ($cssFileConfig['external'] ?? false) {
411  $ss = $CSSfile;
412  } else {
413  try {
414  $ss = $this->filePathSanitizer->sanitize($CSSfile, true);
415  } catch (‪Exception) {
416  $ss = null;
417  }
418  }
419  if ($ss) {
420  $additionalAttributes = $cssFileConfig ?? [];
421  unset(
422  $additionalAttributes['if.'],
423  $additionalAttributes['alternate'],
424  $additionalAttributes['media'],
425  $additionalAttributes['title'],
426  $additionalAttributes['external'],
427  $additionalAttributes['internal'],
428  $additionalAttributes['disableCompression'],
429  $additionalAttributes['excludeFromConcatenation'],
430  $additionalAttributes['allWrap'],
431  $additionalAttributes['allWrap.'],
432  $additionalAttributes['forceOnTop'],
433  );
434  $pageRenderer->addCssLibrary(
435  $ss,
436  ($cssFileConfig['alternate'] ?? false) ? 'alternate stylesheet' : 'stylesheet',
437  ($cssFileConfig['media'] ?? false) ?: 'all',
438  ($cssFileConfig['title'] ?? false) ?: '',
439  empty($cssFileConfig['external']) && empty($cssFileConfig['inline']) && empty($cssFileConfig['disableCompression']),
440  (bool)($cssFileConfig['forceOnTop'] ?? false),
441  $cssFileConfig['allWrap'] ?? '',
442  ($cssFileConfig['excludeFromConcatenation'] ?? false) || ($cssFileConfig['inline'] ?? false),
443  $cssFileConfig['allWrap.']['splitChar'] ?? '|',
444  (bool)($cssFileConfig['inline'] ?? false),
445  $additionalAttributes
446  );
447  }
448  unset($cssFileConfig);
449  }
450  }
451  }
452 
453  $style = $controller->cObj->cObjGet($controller->pSetup['cssInline.'] ?? null, 'cssInline.');
454  if (trim($style)) {
455  $this->‪addCssToPageRenderer($controller, $style, true, 'additionalTSFEInlineStyle');
456  }
457  // JavaScript library files
458  if (is_array($controller->pSetup['includeJSLibs.'] ?? null)) {
459  foreach ($controller->pSetup['includeJSLibs.'] as $key => $JSfile) {
460  if (!is_array($JSfile)) {
461  if (isset($controller->pSetup['includeJSLibs.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSLibs.'][$key . '.']['if.'])) {
462  continue;
463  }
464  if ($controller->pSetup['includeJSLibs.'][$key . '.']['external'] ?? false) {
465  $ss = $JSfile;
466  } else {
467  try {
468  $ss = $this->filePathSanitizer->sanitize($JSfile, true);
469  } catch (‪Exception) {
470  $ss = null;
471  }
472  }
473  if ($ss) {
474  $jsFileConfig = &$controller->pSetup['includeJSLibs.'][$key . '.'];
475  $additionalAttributes = $jsFileConfig ?? [];
476  $crossOrigin = (string)($jsFileConfig['crossorigin'] ?? '');
477  if ($crossOrigin === '' && ($jsFileConfig['integrity'] ?? false) && ($jsFileConfig['external'] ?? false)) {
478  $crossOrigin = 'anonymous';
479  }
480  unset(
481  $additionalAttributes['if.'],
482  $additionalAttributes['type'],
483  $additionalAttributes['crossorigin'],
484  $additionalAttributes['integrity'],
485  $additionalAttributes['external'],
486  $additionalAttributes['allWrap'],
487  $additionalAttributes['allWrap.'],
488  $additionalAttributes['disableCompression'],
489  $additionalAttributes['excludeFromConcatenation'],
490  $additionalAttributes['integrity'],
491  $additionalAttributes['defer'],
492  $additionalAttributes['nomodule'],
493  );
494  $pageRenderer->addJsLibrary(
495  $key,
496  $ss,
497  $jsFileConfig['type'] ?? null,
498  empty($jsFileConfig['external']) && empty($jsFileConfig['disableCompression']),
499  (bool)($jsFileConfig['forceOnTop'] ?? false),
500  $jsFileConfig['allWrap'] ?? '',
501  (bool)($jsFileConfig['excludeFromConcatenation'] ?? false),
502  $jsFileConfig['allWrap.']['splitChar'] ?? '|',
503  (bool)($jsFileConfig['async'] ?? false),
504  $jsFileConfig['integrity'] ?? '',
505  (bool)($jsFileConfig['defer'] ?? false),
506  $crossOrigin,
507  (bool)($jsFileConfig['nomodule'] ?? false),
508  $additionalAttributes
509  );
510  unset($jsFileConfig);
511  }
512  }
513  }
514  }
515  if (is_array($controller->pSetup['includeJSFooterlibs.'] ?? null)) {
516  foreach ($controller->pSetup['includeJSFooterlibs.'] as $key => $JSfile) {
517  if (!is_array($JSfile)) {
518  if (isset($controller->pSetup['includeJSFooterlibs.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSFooterlibs.'][$key . '.']['if.'])) {
519  continue;
520  }
521  if ($controller->pSetup['includeJSFooterlibs.'][$key . '.']['external'] ?? false) {
522  $ss = $JSfile;
523  } else {
524  try {
525  $ss = $this->filePathSanitizer->sanitize($JSfile, true);
526  } catch (‪Exception) {
527  $ss = null;
528  }
529  }
530  if ($ss) {
531  $jsFileConfig = &$controller->pSetup['includeJSFooterlibs.'][$key . '.'];
532  $additionalAttributes = $jsFileConfig ?? [];
533  $crossOrigin = (string)($jsFileConfig['crossorigin'] ?? '');
534  if ($crossOrigin === '' && ($jsFileConfig['integrity'] ?? false) && ($jsFileConfig['external'] ?? false)) {
535  $crossOrigin = 'anonymous';
536  }
537  unset(
538  $additionalAttributes['if.'],
539  $additionalAttributes['type'],
540  $additionalAttributes['crossorigin'],
541  $additionalAttributes['integrity'],
542  $additionalAttributes['external'],
543  $additionalAttributes['allWrap'],
544  $additionalAttributes['allWrap.'],
545  $additionalAttributes['disableCompression'],
546  $additionalAttributes['excludeFromConcatenation'],
547  $additionalAttributes['integrity'],
548  $additionalAttributes['defer'],
549  $additionalAttributes['nomodule'],
550  );
551  $pageRenderer->addJsFooterLibrary(
552  $key,
553  $ss,
554  $jsFileConfig['type'] ?? null,
555  empty($jsFileConfig['external']) && empty($jsFileConfig['disableCompression']),
556  (bool)($jsFileConfig['forceOnTop'] ?? false),
557  $jsFileConfig['allWrap'] ?? '',
558  (bool)($jsFileConfig['excludeFromConcatenation'] ?? false),
559  $jsFileConfig['allWrap.']['splitChar'] ?? '|',
560  (bool)($jsFileConfig['async'] ?? false),
561  $jsFileConfig['integrity'] ?? '',
562  (bool)($jsFileConfig['defer'] ?? false),
563  $crossOrigin,
564  (bool)($jsFileConfig['nomodule'] ?? false),
565  $additionalAttributes
566  );
567  unset($jsFileConfig);
568  }
569  }
570  }
571  }
572  // JavaScript files
573  if (is_array($controller->pSetup['includeJS.'] ?? null)) {
574  foreach ($controller->pSetup['includeJS.'] as $key => $JSfile) {
575  if (!is_array($JSfile)) {
576  if (isset($controller->pSetup['includeJS.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJS.'][$key . '.']['if.'])) {
577  continue;
578  }
579  if ($controller->pSetup['includeJS.'][$key . '.']['external'] ?? false) {
580  $ss = $JSfile;
581  } else {
582  try {
583  $ss = $this->filePathSanitizer->sanitize($JSfile, true);
584  } catch (‪Exception) {
585  $ss = null;
586  }
587  }
588  if ($ss) {
589  $jsConfig = &$controller->pSetup['includeJS.'][$key . '.'];
590  $crossOrigin = (string)($jsConfig['crossorigin'] ?? '');
591  if ($crossOrigin === '' && ($jsConfig['integrity'] ?? false) && ($jsConfig['external'] ?? false)) {
592  $crossOrigin = 'anonymous';
593  }
594  $pageRenderer->addJsFile(
595  $ss,
596  $jsConfig['type'] ?? null,
597  empty($jsConfig['external']) && empty($jsConfig['disableCompression']),
598  (bool)($jsConfig['forceOnTop'] ?? false),
599  $jsConfig['allWrap'] ?? '',
600  (bool)($jsConfig['excludeFromConcatenation'] ?? false),
601  $jsConfig['allWrap.']['splitChar'] ?? '|',
602  (bool)($jsConfig['async'] ?? false),
603  $jsConfig['integrity'] ?? '',
604  (bool)($jsConfig['defer'] ?? false),
605  $crossOrigin,
606  (bool)($jsConfig['nomodule'] ?? false),
607  $jsConfig['data.'] ?? []
608  );
609  unset($jsConfig);
610  }
611  }
612  }
613  }
614  if (is_array($controller->pSetup['includeJSFooter.'] ?? null)) {
615  foreach ($controller->pSetup['includeJSFooter.'] as $key => $JSfile) {
616  if (!is_array($JSfile)) {
617  if (isset($controller->pSetup['includeJSFooter.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSFooter.'][$key . '.']['if.'])) {
618  continue;
619  }
620  if ($controller->pSetup['includeJSFooter.'][$key . '.']['external'] ?? false) {
621  $ss = $JSfile;
622  } else {
623  try {
624  $ss = $this->filePathSanitizer->sanitize($JSfile, true);
625  } catch (‪Exception) {
626  $ss = null;
627  }
628  }
629  if ($ss) {
630  $jsConfig = &$controller->pSetup['includeJSFooter.'][$key . '.'];
631  $crossOrigin = (string)($jsConfig['crossorigin'] ?? '');
632  if ($crossOrigin === '' && ($jsConfig['integrity'] ?? false) && ($jsConfig['external'] ?? false)) {
633  $crossOrigin = 'anonymous';
634  }
635  $pageRenderer->addJsFooterFile(
636  $ss,
637  $jsConfig['type'] ?? null,
638  empty($jsConfig['external']) && empty($jsConfig['disableCompression']),
639  (bool)($jsConfig['forceOnTop'] ?? false),
640  $jsConfig['allWrap'] ?? '',
641  (bool)($jsConfig['excludeFromConcatenation'] ?? false),
642  $jsConfig['allWrap.']['splitChar'] ?? '|',
643  (bool)($jsConfig['async'] ?? false),
644  $jsConfig['integrity'] ?? '',
645  (bool)($jsConfig['defer'] ?? false),
646  $crossOrigin,
647  (bool)($jsConfig['nomodule'] ?? false),
648  $jsConfig['data.'] ?? []
649  );
650  unset($jsConfig);
651  }
652  }
653  }
654  }
655  // Headerdata
656  if (is_array($controller->pSetup['headerData.'] ?? null)) {
657  $pageRenderer->addHeaderData($controller->cObj->cObjGet($controller->pSetup['headerData.'], 'headerData.'));
658  }
659  // Footerdata
660  if (is_array($controller->pSetup['footerData.'] ?? null)) {
661  $pageRenderer->addFooterData($controller->cObj->cObjGet($controller->pSetup['footerData.'], 'footerData.'));
662  }
663  $controller->‪generatePageTitle();
664 
665  // @internal hook for EXT:seo, will be gone soon, do not use it in your own extensions
666  $_params = ['page' => $controller->page];
667  $_ref = null;
668  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags'] ?? [] as $_funcRef) {
669  GeneralUtility::callUserFunction($_funcRef, $_params, $_ref);
670  }
671 
672  $this->‪generateHrefLangTags($controller, $request);
673  $this->‪generateMetaTagHtml(
674  $controller->pSetup['meta.'] ?? [],
675  $controller->cObj
676  );
677 
678  // Javascript inline code
679  $inlineJS = (string)$controller->cObj->cObjGet($controller->pSetup['jsInline.'] ?? null, 'jsInline.');
680  // Javascript inline code for Footer
681  $inlineFooterJs = (string)$controller->cObj->cObjGet($controller->pSetup['jsFooterInline.'] ?? null, 'jsFooterInline.');
682  $compressJs = (bool)($controller->config['config']['compressJs'] ?? false);
683 
684  // Needs to be called after call cObjGet() calls in order to get all headerData and footerData and replacements
685  // see #100216
686  $controller->‪INTincScript_loadJSCode();
687 
688  // this option is set to "external" as default
689  if (($controller->config['config']['removeDefaultJS'] ?? 'external') === 'external') {
690  /*
691  * This keeps inlineJS from *_INT Objects from being moved to external files.
692  * At this point in frontend rendering *_INT Objects only have placeholders instead
693  * of actual content so moving these placeholders to external files would
694  * a) break the JS file (syntax errors due to the placeholders)
695  * b) the needed JS would never get included to the page
696  * Therefore inlineJS from *_INT Objects must not be moved to external files but
697  * kept internal.
698  */
699  $inlineJSint = '';
700  $this->‪stripIntObjectPlaceholder($inlineJS, $inlineJSint);
701  if ($inlineJSint) {
702  $pageRenderer->addJsInlineCode('TS_inlineJSint', $inlineJSint, $compressJs);
703  }
704  if (trim($inlineJS)) {
705  $pageRenderer->addJsFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($inlineJS), null, $compressJs);
706  }
707  if ($inlineFooterJs) {
708  $inlineFooterJSint = '';
709  $this->‪stripIntObjectPlaceholder($inlineFooterJs, $inlineFooterJSint);
710  if ($inlineFooterJSint) {
711  $pageRenderer->addJsFooterInlineCode('TS_inlineFooterJSint', $inlineFooterJSint, $compressJs);
712  }
713  $pageRenderer->addJsFooterFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($inlineFooterJs), null, $compressJs);
714  }
715  } else {
716  // Include only inlineJS
717  if ($inlineJS) {
718  $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $compressJs);
719  }
720  if ($inlineFooterJs) {
721  $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $compressJs);
722  }
723  }
724  if (isset($controller->pSetup['inlineLanguageLabelFiles.']) && is_array($controller->pSetup['inlineLanguageLabelFiles.'])) {
725  foreach ($controller->pSetup['inlineLanguageLabelFiles.'] as $key => $languageFile) {
726  if (is_array($languageFile)) {
727  continue;
728  }
729  $languageFileConfig = &$controller->pSetup['inlineLanguageLabelFiles.'][$key . '.'];
730  if (isset($languageFileConfig['if.']) && !$controller->cObj->checkIf($languageFileConfig['if.'])) {
731  continue;
732  }
733  $pageRenderer->addInlineLanguageLabelFile(
734  $languageFile,
735  ($languageFileConfig['selectionPrefix'] ?? false) ? $languageFileConfig['selectionPrefix'] : '',
736  ($languageFileConfig['stripFromSelectionName'] ?? false) ? $languageFileConfig['stripFromSelectionName'] : ''
737  );
738  }
739  }
740  if (isset($controller->pSetup['inlineSettings.']) && is_array($controller->pSetup['inlineSettings.'])) {
741  $pageRenderer->addInlineSettingArray('TS', $controller->pSetup['inlineSettings.']);
742  }
743  // Compression and concatenate settings
744  if ($controller->config['config']['compressCss'] ?? false) {
745  $pageRenderer->enableCompressCss();
746  }
747  if ($compressJs ?? false) {
748  $pageRenderer->enableCompressJavascript();
749  }
750  if ($controller->config['config']['concatenateCss'] ?? false) {
751  $pageRenderer->enableConcatenateCss();
752  }
753  if ($controller->config['config']['concatenateJs'] ?? false) {
754  $pageRenderer->enableConcatenateJavascript();
755  }
756  // Add header data block
757  if ($controller->additionalHeaderData) {
758  $pageRenderer->addHeaderData(implode(LF, $controller->additionalHeaderData));
759  }
760  // Add footer data block
761  if ($controller->additionalFooterData) {
762  $pageRenderer->addFooterData(implode(LF, $controller->additionalFooterData));
763  }
764  // Header complete, now the body tag is added so the regular content can be applied later-on
765  if ($controller->config['config']['disableBodyTag'] ?? false) {
766  $bodyTag = '';
767  } else {
768  $defBT = (isset($controller->pSetup['bodyTagCObject']) && $controller->pSetup['bodyTagCObject'])
769  ? $controller->cObj->cObjGetSingle($controller->pSetup['bodyTagCObject'], $controller->pSetup['bodyTagCObject.'] ?? [], 'bodyTagCObject')
770  : '<body>';
771  $bodyTag = (isset($controller->pSetup['bodyTag']) && $controller->pSetup['bodyTag'])
772  ? $controller->pSetup['bodyTag']
773  : $defBT;
774  ‪if (trim($controller->pSetup['bodyTagAdd'] ?? '')) {
775  $bodyTag = preg_replace('/>$/', '', trim($bodyTag)) . ' ' . trim($controller->pSetup['bodyTagAdd']) . '>';
776  }
777  }
778  $pageRenderer->addBodyContent(LF . $bodyTag);
779  }
780 
781  /*************************
782  *
783  * Helper functions
784  *
785  *************************/
786 
794  protected function ‪stripIntObjectPlaceholder(&$searchString, &$intObjects)
795  {
796  $tempArray = [];
797  preg_match_all('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', $searchString, $tempArray);
798  $searchString = preg_replace('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', '', $searchString);
799  $intObjects = implode('', $tempArray[0]);
800  }
801 
807  protected function ‪generateMetaTagHtml(array $metaTagTypoScript, ‪ContentObjectRenderer $cObj)
808  {
809  $pageRenderer = $this->‪getPageRenderer();
810  $conf = $this->typoScriptService->convertTypoScriptArrayToPlainArray($metaTagTypoScript);
811  foreach ($conf as $key => $properties) {
812  $replace = false;
813  if (is_array($properties)) {
814  $nodeValue = $properties['_typoScriptNodeValue'] ?? '';
815  $value = trim((string)$cObj->‪stdWrap($nodeValue, $metaTagTypoScript[$key . '.']));
816  if ($value === '' && !empty($properties['value'])) {
817  $value = $properties['value'];
818  $replace = false;
819  }
820  } else {
821  $value = $properties;
822  }
823 
824  $attribute = 'name';
825  if ((is_array($properties) && !empty($properties['httpEquivalent'])) || strtolower($key) === 'refresh') {
826  $attribute = 'http-equiv';
827  }
828  if (is_array($properties) && !empty($properties['attribute'])) {
829  $attribute = $properties['attribute'];
830  }
831  if (is_array($properties) && !empty($properties['replace'])) {
832  $replace = true;
833  }
834 
835  if (!is_array($value)) {
836  $value = (array)$value;
837  }
838  foreach ($value as $subValue) {
839  if (trim($subValue ?? '') !== '') {
840  $pageRenderer->setMetaTag($attribute, $key, $subValue, [], $replace);
841  }
842  }
843  }
844  }
845 
846  protected function ‪getPageRenderer(): ‪PageRenderer
847  {
848  return GeneralUtility::makeInstance(PageRenderer::class);
849  }
850 
858  protected function ‪addCssToPageRenderer(‪TypoScriptFrontendController $controller, string $cssStyles, bool $excludeFromConcatenation, string $inlineBlockName)
859  {
860  // This option is enabled by default on purpose
861  if (empty($controller->config['config']['inlineStyle2TempFile'] ?? true)) {
862  $this->‪getPageRenderer()->addCssInlineBlock($inlineBlockName, $cssStyles, !empty($controller->config['config']['compressCss'] ?? false));
863  } else {
864  $this->‪getPageRenderer()->addCssFile(
865  GeneralUtility::writeStyleSheetContentToTemporaryFile($cssStyles),
866  'stylesheet',
867  'all',
868  '',
869  (bool)($controller->config['config']['compressCss'] ?? false),
870  false,
871  '',
872  $excludeFromConcatenation
873  );
874  }
875  }
876 
896  protected function ‪generateHtmlTag(array $htmlTagAttributes, array $configuration, ‪ContentObjectRenderer $cObj): string
897  {
898  if (is_array($configuration['htmlTag.']['attributes.'] ?? null)) {
899  $attributeString = '';
900  foreach ($configuration['htmlTag.']['attributes.'] as $attributeName => $value) {
901  $attributeString .= ' ' . htmlspecialchars($attributeName) . ($value !== '' ? '="' . htmlspecialchars((string)$value) . '"' : '');
902  // If e.g. "htmlTag.attributes.dir" is set, make sure it is not added again with "implodeAttributes()"
903  if (isset($htmlTagAttributes[$attributeName])) {
904  unset($htmlTagAttributes[$attributeName]);
905  }
906  }
907  $attributeString = ltrim(GeneralUtility::implodeAttributes($htmlTagAttributes) . $attributeString);
908  } elseif (($configuration['htmlTag_setParams'] ?? '') === 'none') {
909  $attributeString = '';
910  } elseif (isset($configuration['htmlTag_setParams'])) {
911  $attributeString = $configuration['htmlTag_setParams'];
912  } else {
913  $attributeString = GeneralUtility::implodeAttributes($htmlTagAttributes);
914  }
915  $htmlTag = '<html' . ($attributeString ? ' ' . $attributeString : '') . '>';
916  if (isset($configuration['htmlTag_stdWrap.'])) {
917  $htmlTag = $cObj->‪stdWrap($htmlTag, $configuration['htmlTag_stdWrap.']);
918  }
919  return $htmlTag;
920  }
921 
922  protected function ‪generateHrefLangTags(‪TypoScriptFrontendController $controller, ServerRequestInterface $request): void
923  {
924  if ($controller->config['config']['disableHrefLang'] ?? false) {
925  return;
926  }
927 
928  $hrefLangs = $this->eventDispatcher->dispatch(
929  new ‪ModifyHrefLangTagsEvent($request)
930  )->getHrefLangs();
931  if (count($hrefLangs) > 1) {
932  $data = [];
933  foreach ($hrefLangs as $hrefLang => $href) {
934  $data[] = sprintf('<link %s/>', GeneralUtility::implodeAttributes([
935  'rel' => 'alternate',
936  'hreflang' => $hrefLang,
937  'href' => $href,
938  ], true));
939  }
940  $controller->additionalHeaderData[] = implode(LF, $data);
941  }
942  }
943 
950  {
951  $context = $controller->‪getContext();
952  $isInWorkspace = $context->getPropertyFromAspect('workspace', 'isOffline', false);
953  $isInPreviewMode = $context->hasAspect('frontend.preview')
954  && $context->getPropertyFromAspect('frontend.preview', 'isPreview');
955  if (!$isInPreviewMode || $isInWorkspace || ($controller->config['config']['disablePreviewNotification'] ?? false)) {
956  return;
957  }
958  if ($controller->config['config']['message_preview'] ?? '') {
959  $message = $controller->config['config']['message_preview'];
960  } else {
961  $label = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_tsfe.xlf:preview');
962  $styles = [];
963  $styles[] = 'position: fixed';
964  $styles[] = 'top: 15px';
965  $styles[] = 'right: 15px';
966  $styles[] = 'padding: 8px 18px';
967  $styles[] = 'background: #fff3cd';
968  $styles[] = 'border: 1px solid #ffeeba';
969  $styles[] = 'font-family: sans-serif';
970  $styles[] = 'font-size: 14px';
971  $styles[] = 'font-weight: bold';
972  $styles[] = 'color: #856404';
973  $styles[] = 'z-index: 20000';
974  $styles[] = 'user-select: none';
975  $styles[] = 'pointer-events: none';
976  $styles[] = 'text-align: center';
977  $styles[] = 'border-radius: 2px';
978  $message = '<div id="typo3-preview-info" style="' . implode(';', $styles) . '">' . htmlspecialchars($label) . '</div>';
979  }
980  if (!empty($message)) {
981  $controller->content = str_ireplace('</body>', $message . '</body>', $controller->content);
982  }
983  }
984 
986  {
987  return GeneralUtility::makeInstance(LanguageServiceFactory::class)->createFromUserPreferences(‪$GLOBALS['BE_USER'] ?? null);
988  }
989 }
‪TYPO3\CMS\Core\Localization\LanguageServiceFactory
Definition: LanguageServiceFactory.php:25
‪TYPO3\CMS\Core\Page\AssetCollector
Definition: AssetCollector.php:42
‪$headerComment
‪$headerComment
Definition: header-comment.php:41
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Information\Typo3Information
Definition: Typo3Information.php:28
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController\generatePageTitle
‪string generatePageTitle()
Definition: TypoScriptFrontendController.php:1939
‪TYPO3\CMS\Frontend\Http
Definition: Application.php:18
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Frontend\Http\RequestHandler\generatePageContent
‪generatePageContent(TypoScriptFrontendController $controller, ServerRequestInterface $request)
Definition: RequestHandler.php:194
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage\getLocale
‪getLocale()
Definition: SiteLanguage.php:174
‪TYPO3\CMS\Frontend\Resource\FilePathSanitizer
Definition: FilePathSanitizer.php:39
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController\getContext
‪getContext()
Definition: TypoScriptFrontendController.php:2535
‪TYPO3\CMS\Frontend\Http\RequestHandler\__construct
‪__construct(private readonly EventDispatcherInterface $eventDispatcher, private readonly ListenerProvider $listenerProvider, private readonly TimeTracker $timeTracker, private readonly FilePathSanitizer $filePathSanitizer, private readonly TypoScriptService $typoScriptService,)
Definition: RequestHandler.php:72
‪TYPO3\CMS\Frontend\Resource\PublicUrlPrefixer
Definition: PublicUrlPrefixer.php:26
‪TYPO3\CMS\Core\Resource\Event\GeneratePublicUrlForResourceEvent
Definition: GeneratePublicUrlForResourceEvent.php:31
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage
Definition: SiteLanguage.php:27
‪TYPO3\CMS\Frontend\Cache\NonceValueSubstitution
Definition: NonceValueSubstitution.php:24
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsoluteWebPath
‪static string getAbsoluteWebPath(string $targetPath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:52
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController\getLanguage
‪getLanguage()
Definition: TypoScriptFrontendController.php:2525
‪TYPO3\CMS\Core\Page\PageRenderer
Definition: PageRenderer.php:46
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController\isINTincScript
‪bool isINTincScript()
Definition: TypoScriptFrontendController.php:2159
‪TYPO3\CMS\Core\Http\Response
Definition: Response.php:32
‪TYPO3\CMS\Frontend\Http\RequestHandler\addCssToPageRenderer
‪addCssToPageRenderer(TypoScriptFrontendController $controller, string $cssStyles, bool $excludeFromConcatenation, string $inlineBlockName)
Definition: RequestHandler.php:858
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer\stdWrap
‪string null stdWrap($content='', $conf=[])
Definition: ContentObjectRenderer.php:1068
‪TYPO3\CMS\Frontend\Http\RequestHandler\stripIntObjectPlaceholder
‪stripIntObjectPlaceholder(&$searchString, &$intObjects)
Definition: RequestHandler.php:794
‪TYPO3\CMS\Frontend\Http\RequestHandler\generateHrefLangTags
‪generateHrefLangTags(TypoScriptFrontendController $controller, ServerRequestInterface $request)
Definition: RequestHandler.php:922
‪TYPO3\CMS\Frontend\Http\RequestHandler\generatePageBodyContent
‪generatePageBodyContent(TypoScriptFrontendController $controller)
Definition: RequestHandler.php:225
‪TYPO3\CMS\Frontend\Http\RequestHandler\getPageRenderer
‪getPageRenderer()
Definition: RequestHandler.php:846
‪TYPO3\CMS\Frontend\Http\RequestHandler\displayPreviewInfoMessage
‪displayPreviewInfoMessage(TypoScriptFrontendController $controller)
Definition: RequestHandler.php:949
‪TYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent
Definition: ModifyHrefLangTagsEvent.php:27
‪TYPO3\CMS\Core\Domain\ConsumableString
Definition: ConsumableString.php:26
‪TYPO3\CMS\Core\Type\File\ImageInfo
Definition: ImageInfo.php:28
‪TYPO3\CMS\Core\TypoScript\TypoScriptService
Definition: TypoScriptService.php:27
‪TYPO3\CMS\Frontend\Http\RequestHandler
Definition: RequestHandler.php:71
‪TYPO3\CMS\Frontend\Http\RequestHandler\getLanguageService
‪getLanguageService()
Definition: RequestHandler.php:985
‪TYPO3\CMS\Core\Resource\Exception
Definition: Exception.php:22
‪TYPO3\CMS\Frontend\Http\RequestHandler\processHtmlBasedRenderingSettings
‪processHtmlBasedRenderingSettings(TypoScriptFrontendController $controller, SiteLanguage $siteLanguage, ServerRequestInterface $request)
Definition: RequestHandler.php:244
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
Definition: TypoScriptFrontendController.php:102
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Frontend\Http\RequestHandler\generateHtmlTag
‪string generateHtmlTag(array $htmlTagAttributes, array $configuration, ContentObjectRenderer $cObj)
Definition: RequestHandler.php:896
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪if
‪if(PHP_SAPI !=='cli')
Definition: checkNamespaceIntegrity.php:25
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController\INTincScript_loadJSCode
‪INTincScript_loadJSCode()
Definition: TypoScriptFrontendController.php:2137
‪TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
Definition: ContentObjectRenderer.php:90
‪TYPO3\CMS\Frontend\Http\RequestHandler\handle
‪handle(ServerRequestInterface $request)
Definition: RequestHandler.php:110
‪TYPO3\CMS\Core\Type\DocType
‪DocType
Definition: DocType.php:27
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Frontend\Http\RequestHandler\generateMetaTagHtml
‪generateMetaTagHtml(array $metaTagTypoScript, ContentObjectRenderer $cObj)
Definition: RequestHandler.php:807
‪TYPO3\CMS\Core\EventDispatcher\ListenerProvider
Definition: ListenerProvider.php:30
‪TYPO3\CMS\Core\TimeTracker\TimeTracker
Definition: TimeTracker.php:32
‪TYPO3\CMS\Core\Resource\Exception
Definition: AbstractFileOperationException.php:16
‪TYPO3\CMS\Frontend\Http\RequestHandler\resetGlobalsToCurrentRequest
‪resetGlobalsToCurrentRequest(ServerRequestInterface $request)
Definition: RequestHandler.php:90