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