[!!!][TASK] Remove deprecated code related to TSFE
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Http / RequestHandler.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Frontend\Http;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use Psr\Http\Message\ResponseInterface;
20 use Psr\Http\Message\ServerRequestInterface;
21 use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
22 use TYPO3\CMS\Core\Core\Environment;
23 use TYPO3\CMS\Core\Http\NullResponse;
24 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
25 use TYPO3\CMS\Core\Http\Response;
26 use TYPO3\CMS\Core\Page\PageRenderer;
27 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
28 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
29 use TYPO3\CMS\Core\Type\File\ImageInfo;
30 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
31 use TYPO3\CMS\Core\Utility\ArrayUtility;
32 use TYPO3\CMS\Core\Utility\GeneralUtility;
33 use TYPO3\CMS\Core\Utility\PathUtility;
34 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
35 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
36 use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
37
38 /**
39 * This is the main entry point of the TypoScript driven standard front-end.
40 *
41 * "handle()" is called when all PSR-15 middlewares have been set up the PSR-7 ServerRequest object and the following
42 * things have been evaluated
43 * - correct page ID, page type (typeNum), rootline, MP etc.
44 * - info if is cached content already available
45 * - proper language
46 * - proper TypoScript which should be processed.
47 *
48 * Then, this class is able to render the actual HTTP body part built via TypoScript. Here this is split into two parts:
49 * - Everything included in <body>, done via page.10, page.20 etc.
50 * - Everything around.
51 *
52 * If the content has been built together within the cache (cache_pages), it is fetched directly, and
53 * any so-called "uncached" content is generated again.
54 *
55 * Some further hooks allow to post-processing the content.
56 *
57 * Then the right HTTP response headers are compiled together and sent as well.
58 */
59 class RequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterface
60 {
61 /**
62 * Instance of the timetracker
63 * @var TimeTracker
64 */
65 protected $timeTracker;
66
67 /**
68 * Handles a frontend request
69 *
70 * @param ServerRequestInterface $request
71 * @return ResponseInterface
72 */
73 public function handleRequest(ServerRequestInterface $request): ResponseInterface
74 {
75 return $this->handle($request);
76 }
77
78 /**
79 * Puts parameters that have been added or removed from the global _GET or _POST arrays
80 * into the given request (however, the PSR-7 request information takes precedence).
81 *
82 * @param ServerRequestInterface $request
83 * @return ServerRequestInterface
84 */
85 protected function addModifiedGlobalsToIncomingRequest(ServerRequestInterface $request): ServerRequestInterface
86 {
87 $originalGetParameters = $request->getAttribute('_originalGetParameters', null);
88 if ($originalGetParameters !== null && !empty($_GET) && $_GET !== $originalGetParameters) {
89 // Find out what has been changed.
90 $modifiedGetParameters = ArrayUtility::arrayDiffAssocRecursive($_GET ?? [], $originalGetParameters);
91 if (!empty($modifiedGetParameters)) {
92 $queryParams = array_replace_recursive($modifiedGetParameters, $request->getQueryParams());
93 $request = $request->withQueryParams($queryParams);
94 $GLOBALS['TYPO3_REQUEST'] = $request;
95 $this->timeTracker->setTSlogMessage('GET parameters have been modified during Request building in a hook.');
96 }
97 }
98 // do same for $_POST if the request is a POST request
99 $originalPostParameters = $request->getAttribute('_originalPostParameters', null);
100 if ($request->getMethod() === 'POST' && $originalPostParameters !== null && !empty($_POST) && $_POST !== $originalPostParameters) {
101 // Find out what has been changed
102 $modifiedPostParameters = ArrayUtility::arrayDiffAssocRecursive($_POST ?? [], $originalPostParameters);
103 if (!empty($modifiedPostParameters)) {
104 $parsedBody = array_replace_recursive($modifiedPostParameters, $request->getParsedBody());
105 $request = $request->withParsedBody($parsedBody);
106 $GLOBALS['TYPO3_REQUEST'] = $request;
107 $this->timeTracker->setTSlogMessage('POST parameters have been modified during Request building in a hook.');
108 }
109 }
110 return $request;
111 }
112
113 /**
114 * Sets the global GET and POST to the values, so if people access $_GET and $_POST
115 * Within hooks starting NOW (e.g. cObject), they get the "enriched" data from query params.
116 *
117 * This needs to be run after the request object has been enriched with modified GET/POST variables.
118 *
119 * @param ServerRequestInterface $request
120 * @internal this safety net will be removed in TYPO3 v10.0.
121 */
122 protected function resetGlobalsToCurrentRequest(ServerRequestInterface $request)
123 {
124 if ($request->getQueryParams() !== $_GET) {
125 $queryParams = $request->getQueryParams();
126 $_GET = $queryParams;
127 $GLOBALS['HTTP_GET_VARS'] = $_GET;
128 }
129 if ($request->getMethod() === 'POST') {
130 $parsedBody = $request->getParsedBody();
131 if (is_array($parsedBody) && $parsedBody !== $_POST) {
132 $_POST = $parsedBody;
133 $GLOBALS['HTTP_POST_VARS'] = $_POST;
134 }
135 }
136 }
137 /**
138 * Handles a frontend request, after finishing running middlewares
139 *
140 * @param ServerRequestInterface $request
141 * @return ResponseInterface|null
142 */
143 public function handle(ServerRequestInterface $request): ResponseInterface
144 {
145 // Fetch the initialized time tracker object
146 $this->timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
147 /** @var TypoScriptFrontendController $controller */
148 $controller = $GLOBALS['TSFE'];
149
150 // safety net, will be removed in TYPO3 v10.0. Aligns $_GET/$_POST to the incoming request.
151 $request = $this->addModifiedGlobalsToIncomingRequest($request);
152 $this->resetGlobalsToCurrentRequest($request);
153
154 // Generate page
155 if ($controller->isGeneratePage()) {
156 $this->timeTracker->push('Page generation');
157 $controller->generatePage_preProcessing();
158 $controller->preparePageContentGeneration($request);
159
160 // Content generation
161 $this->timeTracker->incStackPointer();
162 $this->timeTracker->push($controller->sPre, 'PAGE');
163
164 // If 'disableAllHeaderCode' is set, all the header-code is discarded
165 if ($controller->config['config']['disableAllHeaderCode'] ?? false) {
166 $controller->content = $this->generatePageContent($controller);
167 } else {
168 $controller->content = $this->generatePageContentWithHeader($controller, $request->getAttribute('language', null));
169 }
170
171 $this->timeTracker->pull($this->timeTracker->LR ? $controller->content : '');
172 $this->timeTracker->decStackPointer();
173
174 $controller->setAbsRefPrefix();
175 $controller->generatePage_postProcessing();
176 $this->timeTracker->pull();
177 }
178 $controller->releaseLocks();
179
180 // Render non-cached page parts by replacing placeholders which are taken from cache or added during page generation
181 if ($controller->isINTincScript()) {
182 if (!$controller->isGeneratePage()) {
183 // When page was generated, this was already called. Avoid calling this twice.
184 $controller->preparePageContentGeneration($request);
185 }
186 $this->timeTracker->push('Non-cached objects');
187 $controller->INTincScript();
188 $this->timeTracker->pull();
189 }
190
191 // Create a Response object when sending content
192 $response = new Response();
193
194 // Output content
195 $isOutputting = $controller->isOutputting();
196 if ($isOutputting) {
197 $this->timeTracker->push('Print Content');
198 $response = $controller->applyHttpHeadersToResponse($response);
199 $controller->processContentForOutput();
200 $this->timeTracker->pull();
201 }
202 // Store session data for fe_users
203 $controller->fe_user->storeSessionData();
204
205 // Statistics
206 $GLOBALS['TYPO3_MISC']['microtime_end'] = microtime(true);
207 if ($isOutputting && ($controller->config['config']['debug'] ?? !empty($GLOBALS['TYPO3_CONF_VARS']['FE']['debug']))) {
208 $response = $response->withHeader('X-TYPO3-Parsetime', $this->timeTracker->getParseTime() . 'ms');
209 }
210
211 // Hook for "end-of-frontend"
212 $_params = ['pObj' => &$controller];
213 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe'] ?? [] as $_funcRef) {
214 GeneralUtility::callUserFunction($_funcRef, $_params, $controller);
215 }
216
217 // Finish time tracking (started in TYPO3\CMS\Frontend\Middleware\TimeTrackerInitialization)
218 $this->timeTracker->pull();
219
220 if ($isOutputting) {
221 $response->getBody()->write($controller->content);
222 // if any code set a response code that is not 200 clear the cache's content
223 // if we fail to do so we would deliver cache content with a wrong header, which causes big mess.
224 if (http_response_code() !== 200 || $response->getStatusCode() !== 200) {
225 $controller->clearPageCacheContent();
226 }
227 }
228
229 return $isOutputting ? $response : new NullResponse();
230 }
231
232 /**
233 * Generates the main content part within <body> tags (except JS files/CSS files).
234 *
235 * @param TypoScriptFrontendController $controller
236 * @return string
237 */
238 protected function generatePageContent(TypoScriptFrontendController $controller): string
239 {
240 $pageContent = $controller->cObj->cObjGet($controller->pSetup) ?: '';
241 if ($controller->pSetup['wrap'] ?? false) {
242 $pageContent = $controller->cObj->wrap($pageContent, $controller->pSetup['wrap']);
243 }
244 if ($controller->pSetup['stdWrap.'] ?? false) {
245 $pageContent = $controller->cObj->stdWrap($pageContent, $controller->pSetup['stdWrap.']);
246 }
247 return $pageContent;
248 }
249
250 /**
251 * Rendering normal HTML-page with header by wrapping the generated content ($pageContent) in body-tags and setting the header accordingly.
252 * Render HTML page with header parts (<head> tag content and wrap around <body> tag) - this is done
253 * after the "main" page Content, since some JS may be inserted at that point.
254 *
255 * @param TypoScriptFrontendController $controller
256 * @param SiteLanguage|null $siteLanguage
257 * @return string
258 */
259 protected function generatePageContentWithHeader(TypoScriptFrontendController $controller, ?SiteLanguage $siteLanguage): string
260 {
261 // Generate the page content, this has to be first, as some additional TSFE-related code could have been written
262 $pageContent = $this->generatePageContent($controller);
263 $pageRenderer = $this->getPageRenderer();
264 if ($controller->config['config']['moveJsFromHeaderToFooter'] ?? false) {
265 $pageRenderer->enableMoveJsFromHeaderToFooter();
266 }
267 if ($controller->config['config']['pageRendererTemplateFile'] ?? false) {
268 try {
269 $file = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($controller->config['config']['pageRendererTemplateFile']);
270 $pageRenderer->setTemplateFile($file);
271 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
272 // do nothing
273 }
274 }
275 $headerComment = trim($controller->config['config']['headerComment'] ?? '');
276 if ($headerComment) {
277 $pageRenderer->addInlineComment("\t" . str_replace(LF, LF . "\t", $headerComment) . LF);
278 }
279 // Setting charset:
280 $theCharset = $controller->metaCharset;
281 // Reset the content variables:
282 $controller->content = '';
283 $htmlTagAttributes = [];
284 $htmlLang = $controller->config['config']['htmlTag_langKey'] ?? ($controller->sys_language_isocode ?: 'en');
285 $direction = $controller->config['config']['htmlTag_dir'] ?? null;
286 if ($siteLanguage !== null) {
287 $direction = $siteLanguage->getDirection();
288 $htmlLang = $siteLanguage->getTwoLetterIsoCode();
289 }
290
291 if ($direction) {
292 $htmlTagAttributes['dir'] = htmlspecialchars($direction);
293 }
294 // Setting document type:
295 $docTypeParts = [];
296 $xmlDocument = true;
297 // Part 1: XML prologue
298 switch ((string)($controller->config['config']['xmlprologue'] ?? '')) {
299 case 'none':
300 $xmlDocument = false;
301 break;
302 case 'xml_10':
303 $docTypeParts[] = '<?xml version="1.0" encoding="' . $theCharset . '"?>';
304 break;
305 case 'xml_11':
306 $docTypeParts[] = '<?xml version="1.1" encoding="' . $theCharset . '"?>';
307 break;
308 case '':
309 if ($controller->xhtmlVersion) {
310 $docTypeParts[] = '<?xml version="1.0" encoding="' . $theCharset . '"?>';
311 } else {
312 $xmlDocument = false;
313 }
314 break;
315 default:
316 $docTypeParts[] = $controller->config['config']['xmlprologue'];
317 }
318 // Part 2: DTD
319 $doctype = $controller->config['config']['doctype'] ?? null;
320 if ($doctype) {
321 switch ($doctype) {
322 case 'xhtml_trans':
323 $docTypeParts[] = '<!DOCTYPE html
324 PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
325 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
326 break;
327 case 'xhtml_strict':
328 $docTypeParts[] = '<!DOCTYPE html
329 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
330 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
331 break;
332 case 'xhtml_basic':
333 $docTypeParts[] = '<!DOCTYPE html
334 PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN"
335 "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">';
336 break;
337 case 'xhtml_11':
338 $docTypeParts[] = '<!DOCTYPE html
339 PUBLIC "-//W3C//DTD XHTML 1.1//EN"
340 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
341 break;
342 case 'xhtml+rdfa_10':
343 $docTypeParts[] = '<!DOCTYPE html
344 PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN"
345 "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">';
346 break;
347 case 'html5':
348 $docTypeParts[] = '<!DOCTYPE html>';
349 if ($xmlDocument) {
350 $pageRenderer->setMetaCharsetTag('<meta charset="|" />');
351 } else {
352 $pageRenderer->setMetaCharsetTag('<meta charset="|">');
353 }
354 break;
355 case 'none':
356 break;
357 default:
358 $docTypeParts[] = $doctype;
359 }
360 } else {
361 $docTypeParts[] = '<!DOCTYPE html>';
362 if ($xmlDocument) {
363 $pageRenderer->setMetaCharsetTag('<meta charset="|" />');
364 } else {
365 $pageRenderer->setMetaCharsetTag('<meta charset="|">');
366 }
367 }
368 if ($controller->xhtmlVersion) {
369 $htmlTagAttributes['xml:lang'] = $htmlLang;
370 }
371 if ($controller->xhtmlVersion < 110 || $doctype === 'html5') {
372 $htmlTagAttributes['lang'] = $htmlLang;
373 }
374 if ($controller->xhtmlVersion || $doctype === 'html5' && $xmlDocument) {
375 // We add this to HTML5 to achieve a slightly better backwards compatibility
376 $htmlTagAttributes['xmlns'] = 'http://www.w3.org/1999/xhtml';
377 if (is_array($controller->config['config']['namespaces.'])) {
378 foreach ($controller->config['config']['namespaces.'] as $prefix => $uri) {
379 // $uri gets htmlspecialchared later
380 $htmlTagAttributes['xmlns:' . htmlspecialchars($prefix)] = $uri;
381 }
382 }
383 }
384 // Swap XML and doctype order around (for MSIE / Opera standards compliance)
385 if ($controller->config['config']['doctypeSwitch'] ?? false) {
386 $docTypeParts = array_reverse($docTypeParts);
387 }
388 // Adding doctype parts:
389 if (!empty($docTypeParts)) {
390 $pageRenderer->setXmlPrologAndDocType(implode(LF, $docTypeParts));
391 }
392 // Begin header section:
393 $htmlTag = $this->generateHtmlTag($htmlTagAttributes, $controller->config['config'] ?? [], $controller->cObj);
394 $pageRenderer->setHtmlTag($htmlTag);
395 // Head tag:
396 $headTag = $controller->pSetup['headTag'] ?? '<head>';
397 if (isset($controller->pSetup['headTag.'])) {
398 $headTag = $controller->cObj->stdWrap($headTag, $controller->pSetup['headTag.']);
399 }
400 $pageRenderer->setHeadTag($headTag);
401 // Setting charset meta tag:
402 $pageRenderer->setCharSet($theCharset);
403 $pageRenderer->addInlineComment(' This website is powered by TYPO3 - inspiring people to share!
404 TYPO3 is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.
405 TYPO3 is copyright ' . TYPO3_copyright_year . ' of Kasper Skaarhoj. Extensions are copyright of their respective owners.
406 Information and contribution at ' . TYPO3_URL_GENERAL . '
407 ');
408 if ($controller->baseUrl) {
409 $pageRenderer->setBaseUrl($controller->baseUrl);
410 }
411 if ($controller->pSetup['shortcutIcon'] ?? false) {
412 try {
413 $favIcon = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($controller->pSetup['shortcutIcon']);
414 $iconFileInfo = GeneralUtility::makeInstance(ImageInfo::class, Environment::getPublicPath() . '/' . $favIcon);
415 if ($iconFileInfo->isFile()) {
416 $iconMimeType = $iconFileInfo->getMimeType();
417 if ($iconMimeType) {
418 $iconMimeType = ' type="' . $iconMimeType . '"';
419 $pageRenderer->setIconMimeType($iconMimeType);
420 }
421 $pageRenderer->setFavIcon(PathUtility::getAbsoluteWebPath($controller->absRefPrefix . $favIcon));
422 }
423 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
424 // do nothing
425 }
426 }
427 // Including CSS files
428 if (isset($controller->tmpl->setup['plugin.']) && is_array($controller->tmpl->setup['plugin.'])) {
429 $stylesFromPlugins = '';
430 foreach ($controller->tmpl->setup['plugin.'] as $key => $iCSScode) {
431 if (is_array($iCSScode)) {
432 if ($iCSScode['_CSS_DEFAULT_STYLE'] && empty($controller->config['config']['removeDefaultCss'])) {
433 if (isset($iCSScode['_CSS_DEFAULT_STYLE.'])) {
434 $cssDefaultStyle = $controller->cObj->stdWrap($iCSScode['_CSS_DEFAULT_STYLE'], $iCSScode['_CSS_DEFAULT_STYLE.']);
435 } else {
436 $cssDefaultStyle = $iCSScode['_CSS_DEFAULT_STYLE'];
437 }
438 $stylesFromPlugins .= '/* default styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssDefaultStyle . LF;
439 }
440 if ($iCSScode['_CSS_PAGE_STYLE'] && empty($controller->config['config']['removePageCss'])) {
441 $cssPageStyle = implode(LF, $iCSScode['_CSS_PAGE_STYLE']);
442 if (isset($iCSScode['_CSS_PAGE_STYLE.'])) {
443 $cssPageStyle = $controller->cObj->stdWrap($cssPageStyle, $iCSScode['_CSS_PAGE_STYLE.']);
444 }
445 $cssPageStyle = '/* specific page styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssPageStyle;
446 $this->addCssToPageRenderer($controller, $cssPageStyle, true, 'InlinePageCss');
447 }
448 }
449 }
450 if (!empty($stylesFromPlugins)) {
451 $this->addCssToPageRenderer($controller, $stylesFromPlugins, false, 'InlineDefaultCss');
452 }
453 }
454 /**********************************************************************/
455 /* config.includeCSS / config.includeCSSLibs
456 /**********************************************************************/
457 if (isset($controller->pSetup['includeCSS.']) && is_array($controller->pSetup['includeCSS.'])) {
458 foreach ($controller->pSetup['includeCSS.'] as $key => $CSSfile) {
459 if (!is_array($CSSfile)) {
460 $cssFileConfig = &$controller->pSetup['includeCSS.'][$key . '.'];
461 if (isset($cssFileConfig['if.']) && !$controller->cObj->checkIf($cssFileConfig['if.'])) {
462 continue;
463 }
464 if ($cssFileConfig['external']) {
465 $ss = $CSSfile;
466 } else {
467 try {
468 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($CSSfile);
469 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
470 $ss = null;
471 }
472 }
473 if ($ss) {
474 if ($cssFileConfig['import']) {
475 if (!$cssFileConfig['external'] && $ss[0] !== '/') {
476 // To fix MSIE 6 that cannot handle these as relative paths (according to Ben v Ende)
477 $ss = GeneralUtility::dirname(GeneralUtility::getIndpEnv('SCRIPT_NAME')) . '/' . $ss;
478 }
479 $pageRenderer->addCssInlineBlock('import_' . $key, '@import url("' . htmlspecialchars($ss) . '") ' . htmlspecialchars($cssFileConfig['media']) . ';', empty($cssFileConfig['disableCompression']), (bool)$cssFileConfig['forceOnTop']);
480 } else {
481 $pageRenderer->addCssFile(
482 $ss,
483 $cssFileConfig['alternate'] ? 'alternate stylesheet' : 'stylesheet',
484 $cssFileConfig['media'] ?: 'all',
485 $cssFileConfig['title'] ?: '',
486 $cssFileConfig['external'] ? false : empty($cssFileConfig['disableCompression']),
487 (bool)$cssFileConfig['forceOnTop'],
488 $cssFileConfig['allWrap'],
489 (bool)$cssFileConfig['excludeFromConcatenation'] || (bool)$cssFileConfig['inline'],
490 $cssFileConfig['allWrap.']['splitChar'],
491 $cssFileConfig['inline']
492 );
493 unset($cssFileConfig);
494 }
495 }
496 }
497 }
498 }
499 if (isset($controller->pSetup['includeCSSLibs.']) && is_array($controller->pSetup['includeCSSLibs.'])) {
500 foreach ($controller->pSetup['includeCSSLibs.'] as $key => $CSSfile) {
501 if (!is_array($CSSfile)) {
502 $cssFileConfig = &$controller->pSetup['includeCSSLibs.'][$key . '.'];
503 if (isset($cssFileConfig['if.']) && !$controller->cObj->checkIf($cssFileConfig['if.'])) {
504 continue;
505 }
506 if ($cssFileConfig['external']) {
507 $ss = $CSSfile;
508 } else {
509 try {
510 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($CSSfile);
511 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
512 $ss = null;
513 }
514 }
515 if ($ss) {
516 if ($cssFileConfig['import']) {
517 if (!$cssFileConfig['external'] && $ss[0] !== '/') {
518 // To fix MSIE 6 that cannot handle these as relative paths (according to Ben v Ende)
519 $ss = GeneralUtility::dirname(GeneralUtility::getIndpEnv('SCRIPT_NAME')) . '/' . $ss;
520 }
521 $pageRenderer->addCssInlineBlock('import_' . $key, '@import url("' . htmlspecialchars($ss) . '") ' . htmlspecialchars($cssFileConfig['media']) . ';', empty($cssFileConfig['disableCompression']), (bool)$cssFileConfig['forceOnTop']);
522 } else {
523 $pageRenderer->addCssLibrary(
524 $ss,
525 $cssFileConfig['alternate'] ? 'alternate stylesheet' : 'stylesheet',
526 $cssFileConfig['media'] ?: 'all',
527 $cssFileConfig['title'] ?: '',
528 $cssFileConfig['external'] ? false : empty($cssFileConfig['disableCompression']),
529 (bool)$cssFileConfig['forceOnTop'],
530 $cssFileConfig['allWrap'],
531 (bool)$cssFileConfig['excludeFromConcatenation'] || (bool)$cssFileConfig['inline'],
532 $cssFileConfig['allWrap.']['splitChar'],
533 $cssFileConfig['inline']
534 );
535 unset($cssFileConfig);
536 }
537 }
538 }
539 }
540 }
541
542 // CSS_inlineStyle from TS
543 $style = trim($controller->pSetup['CSS_inlineStyle'] ?? '');
544 $style .= $controller->cObj->cObjGet($controller->pSetup['cssInline.'] ?? null, 'cssInline.');
545 if (trim($style)) {
546 $this->addCssToPageRenderer($controller, $style, true, 'additionalTSFEInlineStyle');
547 }
548 // JavaScript library files
549 if (isset($controller->pSetup['includeJSLibs.']) && is_array($controller->pSetup['includeJSLibs.'])) {
550 foreach ($controller->pSetup['includeJSLibs.'] as $key => $JSfile) {
551 if (!is_array($JSfile)) {
552 if (isset($controller->pSetup['includeJSLibs.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSLibs.'][$key . '.']['if.'])) {
553 continue;
554 }
555 if ($controller->pSetup['includeJSLibs.'][$key . '.']['external']) {
556 $ss = $JSfile;
557 } else {
558 try {
559 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
560 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
561 $ss = null;
562 }
563 }
564 if ($ss) {
565 $jsFileConfig = &$controller->pSetup['includeJSLibs.'][$key . '.'];
566 $type = $jsFileConfig['type'];
567 if (!$type) {
568 $type = 'text/javascript';
569 }
570 $crossOrigin = $jsFileConfig['crossorigin'];
571 if (!$crossOrigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
572 $crossOrigin = 'anonymous';
573 }
574 $pageRenderer->addJsLibrary(
575 $key,
576 $ss,
577 $type,
578 $jsFileConfig['external'] ? false : empty($jsFileConfig['disableCompression']),
579 (bool)$jsFileConfig['forceOnTop'],
580 $jsFileConfig['allWrap'],
581 (bool)$jsFileConfig['excludeFromConcatenation'],
582 $jsFileConfig['allWrap.']['splitChar'],
583 (bool)$jsFileConfig['async'],
584 $jsFileConfig['integrity'],
585 (bool)$jsFileConfig['defer'],
586 $crossOrigin
587 );
588 unset($jsFileConfig);
589 }
590 }
591 }
592 }
593 if (isset($controller->pSetup['includeJSFooterlibs.']) && is_array($controller->pSetup['includeJSFooterlibs.'])) {
594 foreach ($controller->pSetup['includeJSFooterlibs.'] as $key => $JSfile) {
595 if (!is_array($JSfile)) {
596 if (isset($controller->pSetup['includeJSFooterlibs.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSFooterlibs.'][$key . '.']['if.'])) {
597 continue;
598 }
599 if ($controller->pSetup['includeJSFooterlibs.'][$key . '.']['external']) {
600 $ss = $JSfile;
601 } else {
602 try {
603 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
604 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
605 $ss = null;
606 }
607 }
608 if ($ss) {
609 $jsFileConfig = &$controller->pSetup['includeJSFooterlibs.'][$key . '.'];
610 $type = $jsFileConfig['type'];
611 if (!$type) {
612 $type = 'text/javascript';
613 }
614 $crossorigin = $jsFileConfig['crossorigin'];
615 if (!$crossorigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
616 $crossorigin = 'anonymous';
617 }
618 $pageRenderer->addJsFooterLibrary(
619 $key,
620 $ss,
621 $type,
622 $jsFileConfig['external'] ? false : empty($jsFileConfig['disableCompression']),
623 (bool)$jsFileConfig['forceOnTop'],
624 $jsFileConfig['allWrap'],
625 (bool)$jsFileConfig['excludeFromConcatenation'],
626 $jsFileConfig['allWrap.']['splitChar'],
627 (bool)$jsFileConfig['async'],
628 $jsFileConfig['integrity'],
629 (bool)$jsFileConfig['defer'],
630 $crossorigin
631 );
632 unset($jsFileConfig);
633 }
634 }
635 }
636 }
637 // JavaScript files
638 if (isset($controller->pSetup['includeJS.']) && is_array($controller->pSetup['includeJS.'])) {
639 foreach ($controller->pSetup['includeJS.'] as $key => $JSfile) {
640 if (!is_array($JSfile)) {
641 if (isset($controller->pSetup['includeJS.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJS.'][$key . '.']['if.'])) {
642 continue;
643 }
644 if ($controller->pSetup['includeJS.'][$key . '.']['external']) {
645 $ss = $JSfile;
646 } else {
647 try {
648 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
649 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
650 $ss = null;
651 }
652 }
653 if ($ss) {
654 $jsConfig = &$controller->pSetup['includeJS.'][$key . '.'];
655 $type = $jsConfig['type'];
656 if (!$type) {
657 $type = 'text/javascript';
658 }
659 $crossorigin = $jsConfig['crossorigin'];
660 if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
661 $crossorigin = 'anonymous';
662 }
663 $pageRenderer->addJsFile(
664 $ss,
665 $type,
666 $jsConfig['external'] ? false : empty($jsConfig['disableCompression']),
667 (bool)$jsConfig['forceOnTop'],
668 $jsConfig['allWrap'],
669 (bool)$jsConfig['excludeFromConcatenation'],
670 $jsConfig['allWrap.']['splitChar'],
671 (bool)$jsConfig['async'],
672 $jsConfig['integrity'],
673 (bool)$jsConfig['defer'],
674 $crossorigin
675 );
676 unset($jsConfig);
677 }
678 }
679 }
680 }
681 if (isset($controller->pSetup['includeJSFooter.']) && is_array($controller->pSetup['includeJSFooter.'])) {
682 foreach ($controller->pSetup['includeJSFooter.'] as $key => $JSfile) {
683 if (!is_array($JSfile)) {
684 if (isset($controller->pSetup['includeJSFooter.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSFooter.'][$key . '.']['if.'])) {
685 continue;
686 }
687 if ($controller->pSetup['includeJSFooter.'][$key . '.']['external']) {
688 $ss = $JSfile;
689 } else {
690 try {
691 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
692 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
693 $ss = null;
694 }
695 }
696 if ($ss) {
697 $jsConfig = &$controller->pSetup['includeJSFooter.'][$key . '.'];
698 $type = $jsConfig['type'];
699 if (!$type) {
700 $type = 'text/javascript';
701 }
702 $crossorigin = $jsConfig['crossorigin'];
703 if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
704 $crossorigin = 'anonymous';
705 }
706 $pageRenderer->addJsFooterFile(
707 $ss,
708 $type,
709 $jsConfig['external'] ? false : empty($jsConfig['disableCompression']),
710 (bool)$jsConfig['forceOnTop'],
711 $jsConfig['allWrap'],
712 (bool)$jsConfig['excludeFromConcatenation'],
713 $jsConfig['allWrap.']['splitChar'],
714 (bool)$jsConfig['async'],
715 $jsConfig['integrity'],
716 (bool)$jsConfig['defer'],
717 $crossorigin
718 );
719 unset($jsConfig);
720 }
721 }
722 }
723 }
724 // Headerdata
725 if (isset($controller->pSetup['headerData.']) && is_array($controller->pSetup['headerData.'])) {
726 $pageRenderer->addHeaderData($controller->cObj->cObjGet($controller->pSetup['headerData.'], 'headerData.'));
727 }
728 // Footerdata
729 if (isset($controller->pSetup['footerData.']) && is_array($controller->pSetup['footerData.'])) {
730 $pageRenderer->addFooterData($controller->cObj->cObjGet($controller->pSetup['footerData.'], 'footerData.'));
731 }
732 $controller->generatePageTitle();
733
734 // @internal hook for EXT:seo, will be gone soon, do not use it in your own extensions
735 $_params = ['page' => $controller->page];
736 $_ref = '';
737 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags'] ?? [] as $_funcRef) {
738 GeneralUtility::callUserFunction($_funcRef, $_params, $_ref);
739 }
740
741 $this->generateMetaTagHtml(
742 $controller->pSetup['meta.'] ?? [],
743 $controller->cObj
744 );
745
746 unset($controller->additionalHeaderData['JSCode']);
747 if (isset($controller->config['INTincScript']) && is_array($controller->config['INTincScript'])) {
748 $controller->additionalHeaderData['JSCode'] = $controller->JSCode;
749 // Storing the JSCode vars...
750 $controller->config['INTincScript_ext']['divKey'] = $controller->uniqueHash();
751 $controller->config['INTincScript_ext']['additionalHeaderData'] = $controller->additionalHeaderData;
752 // Storing the header-data array
753 $controller->config['INTincScript_ext']['additionalFooterData'] = $controller->additionalFooterData;
754 // Storing the footer-data array
755 $controller->config['INTincScript_ext']['additionalJavaScript'] = $controller->additionalJavaScript;
756 // Storing the JS-data array
757 $controller->config['INTincScript_ext']['additionalCSS'] = $controller->additionalCSS;
758 // Storing the Style-data array
759 $controller->additionalHeaderData = ['<!--HD_' . $controller->config['INTincScript_ext']['divKey'] . '-->'];
760 // Clearing the array
761 $controller->additionalFooterData = ['<!--FD_' . $controller->config['INTincScript_ext']['divKey'] . '-->'];
762 // Clearing the array
763 $controller->divSection .= '<!--TDS_' . $controller->config['INTincScript_ext']['divKey'] . '-->';
764 } else {
765 $controller->INTincScript_loadJSCode();
766 }
767 $scriptJsCode = '';
768
769 if ($controller->spamProtectEmailAddresses && $controller->spamProtectEmailAddresses !== 'ascii') {
770 $scriptJsCode = '
771 // decrypt helper function
772 function decryptCharcode(n,start,end,offset) {
773 n = n + offset;
774 if (offset > 0 && n > end) {
775 n = start + (n - end - 1);
776 } else if (offset < 0 && n < start) {
777 n = end - (start - n - 1);
778 }
779 return String.fromCharCode(n);
780 }
781 // decrypt string
782 function decryptString(enc,offset) {
783 var dec = "";
784 var len = enc.length;
785 for(var i=0; i < len; i++) {
786 var n = enc.charCodeAt(i);
787 if (n >= 0x2B && n <= 0x3A) {
788 dec += decryptCharcode(n,0x2B,0x3A,offset); // 0-9 . , - + / :
789 } else if (n >= 0x40 && n <= 0x5A) {
790 dec += decryptCharcode(n,0x40,0x5A,offset); // A-Z @
791 } else if (n >= 0x61 && n <= 0x7A) {
792 dec += decryptCharcode(n,0x61,0x7A,offset); // a-z
793 } else {
794 dec += enc.charAt(i);
795 }
796 }
797 return dec;
798 }
799 // decrypt spam-protected emails
800 function linkTo_UnCryptMailto(s) {
801 location.href = decryptString(s,' . $controller->spamProtectEmailAddresses * -1 . ');
802 }
803 ';
804 }
805 // Add inline JS
806 $inlineJS = '';
807 // defined in php
808 if (is_array($controller->inlineJS)) {
809 foreach ($controller->inlineJS as $key => $val) {
810 if (!is_array($val)) {
811 $inlineJS .= LF . $val . LF;
812 }
813 }
814 }
815 // defined in TS with page.inlineJS
816 // Javascript inline code
817 $inline = $controller->cObj->cObjGet($controller->pSetup['jsInline.'] ?? null, 'jsInline.');
818 if ($inline) {
819 $inlineJS .= LF . $inline . LF;
820 }
821 // Javascript inline code for Footer
822 $inlineFooterJs = $controller->cObj->cObjGet($controller->pSetup['jsFooterInline.'] ?? null, 'jsFooterInline.');
823 // Should minify?
824 if ($controller->config['config']['compressJs'] ?? false) {
825 $pageRenderer->enableCompressJavascript();
826 $minifyErrorScript = ($minifyErrorInline = '');
827 $scriptJsCode = GeneralUtility::minifyJavaScript($scriptJsCode, $minifyErrorScript);
828 if ($minifyErrorScript) {
829 $this->timeTracker->setTSlogMessage($minifyErrorScript, 3);
830 }
831 if ($inlineJS) {
832 $inlineJS = GeneralUtility::minifyJavaScript($inlineJS, $minifyErrorInline);
833 if ($minifyErrorInline) {
834 $this->timeTracker->setTSlogMessage($minifyErrorInline, 3);
835 }
836 }
837 if ($inlineFooterJs) {
838 $inlineFooterJs = GeneralUtility::minifyJavaScript($inlineFooterJs, $minifyErrorInline);
839 if ($minifyErrorInline) {
840 $this->timeTracker->setTSlogMessage($minifyErrorInline, 3);
841 }
842 }
843 }
844 if (!isset($controller->config['config']['removeDefaultJS']) || !$controller->config['config']['removeDefaultJS']) {
845 // include default and inlineJS
846 if ($scriptJsCode) {
847 $pageRenderer->addJsInlineCode('_scriptCode', $scriptJsCode, $controller->config['config']['compressJs']);
848 }
849 if ($inlineJS) {
850 $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $controller->config['config']['compressJs']);
851 }
852 if ($inlineFooterJs) {
853 $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $controller->config['config']['compressJs']);
854 }
855 } elseif ($controller->config['config']['removeDefaultJS'] === 'external') {
856 /*
857 * This keeps inlineJS from *_INT Objects from being moved to external files.
858 * At this point in frontend rendering *_INT Objects only have placeholders instead
859 * of actual content so moving these placeholders to external files would
860 * a) break the JS file (syntax errors due to the placeholders)
861 * b) the needed JS would never get included to the page
862 * Therefore inlineJS from *_INT Objects must not be moved to external files but
863 * kept internal.
864 */
865 $inlineJSint = '';
866 $this->stripIntObjectPlaceholder($inlineJS, $inlineJSint);
867 if ($inlineJSint) {
868 $pageRenderer->addJsInlineCode('TS_inlineJSint', $inlineJSint, $controller->config['config']['compressJs']);
869 }
870 if (trim($scriptJsCode . $inlineJS)) {
871 $pageRenderer->addJsFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($scriptJsCode . $inlineJS), 'text/javascript', $controller->config['config']['compressJs']);
872 }
873 if ($inlineFooterJs) {
874 $inlineFooterJSint = '';
875 $this->stripIntObjectPlaceholder($inlineFooterJs, $inlineFooterJSint);
876 if ($inlineFooterJSint) {
877 $pageRenderer->addJsFooterInlineCode('TS_inlineFooterJSint', $inlineFooterJSint, $controller->config['config']['compressJs']);
878 }
879 $pageRenderer->addJsFooterFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($inlineFooterJs), 'text/javascript', $controller->config['config']['compressJs']);
880 }
881 } else {
882 // Include only inlineJS
883 if ($inlineJS) {
884 $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $controller->config['config']['compressJs']);
885 }
886 if ($inlineFooterJs) {
887 $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $controller->config['config']['compressJs']);
888 }
889 }
890 if (isset($controller->pSetup['inlineLanguageLabelFiles.']) && is_array($controller->pSetup['inlineLanguageLabelFiles.'])) {
891 foreach ($controller->pSetup['inlineLanguageLabelFiles.'] as $key => $languageFile) {
892 if (is_array($languageFile)) {
893 continue;
894 }
895 $languageFileConfig = &$controller->pSetup['inlineLanguageLabelFiles.'][$key . '.'];
896 if (isset($languageFileConfig['if.']) && !$controller->cObj->checkIf($languageFileConfig['if.'])) {
897 continue;
898 }
899 $pageRenderer->addInlineLanguageLabelFile(
900 $languageFile,
901 $languageFileConfig['selectionPrefix'] ?: '',
902 $languageFileConfig['stripFromSelectionName'] ?: ''
903 );
904 }
905 }
906 if (isset($controller->pSetup['inlineSettings.']) && is_array($controller->pSetup['inlineSettings.'])) {
907 $pageRenderer->addInlineSettingArray('TS', $controller->pSetup['inlineSettings.']);
908 }
909 // Compression and concatenate settings
910 if ($controller->config['config']['compressCss'] ?? false) {
911 $pageRenderer->enableCompressCss();
912 }
913 if ($controller->config['config']['compressJs'] ?? false) {
914 $pageRenderer->enableCompressJavascript();
915 }
916 if ($controller->config['config']['concatenateCss'] ?? false) {
917 $pageRenderer->enableConcatenateCss();
918 }
919 if ($controller->config['config']['concatenateJs'] ?? false) {
920 $pageRenderer->enableConcatenateJavascript();
921 }
922 // Add header data block
923 if ($controller->additionalHeaderData) {
924 $pageRenderer->addHeaderData(implode(LF, $controller->additionalHeaderData));
925 }
926 // Add footer data block
927 if ($controller->additionalFooterData) {
928 $pageRenderer->addFooterData(implode(LF, $controller->additionalFooterData));
929 }
930 // Header complete, now add content
931 // Bodytag:
932 if ($controller->config['config']['disableBodyTag'] ?? false) {
933 $bodyTag = '';
934 } else {
935 $defBT = (isset($controller->pSetup['bodyTagCObject']) && $controller->pSetup['bodyTagCObject'])
936 ? $controller->cObj->cObjGetSingle($controller->pSetup['bodyTagCObject'], $controller->pSetup['bodyTagCObject.'], 'bodyTagCObject')
937 : '<body>';
938 $bodyTag = (isset($controller->pSetup['bodyTag']) && $controller->pSetup['bodyTag'])
939 ? $controller->pSetup['bodyTag']
940 : $defBT;
941 if (trim($controller->pSetup['bodyTagAdd'] ?? '')) {
942 $bodyTag = preg_replace('/>$/', '', trim($bodyTag)) . ' ' . trim($controller->pSetup['bodyTagAdd']) . '>';
943 }
944 }
945 $pageRenderer->addBodyContent(LF . $bodyTag);
946 // Div-sections
947 if ($controller->divSection) {
948 $pageRenderer->addBodyContent(LF . $controller->divSection);
949 }
950 // Page content
951 $pageRenderer->addBodyContent(LF . $pageContent);
952 if (!empty($controller->config['INTincScript']) && is_array($controller->config['INTincScript'])) {
953 // Store the serialized pageRenderer in configuration
954 $controller->config['INTincScript_ext']['pageRenderer'] = serialize($pageRenderer);
955 // Render complete page, keep placeholders for JavaScript and CSS
956 $pageContent = $pageRenderer->renderPageWithUncachedObjects($controller->config['INTincScript_ext']['divKey']);
957 } else {
958 // Render complete page
959 $pageContent = $pageRenderer->render();
960 }
961 return $pageContent ?: '';
962 }
963
964 /*************************
965 *
966 * Helper functions
967 *
968 *************************/
969
970 /**
971 * Searches for placeholder created from *_INT cObjects, removes them from
972 * $searchString and merges them to $intObjects
973 *
974 * @param string $searchString The String which should be cleaned from int-object markers
975 * @param string $intObjects The String the found int-placeholders are moved to (for further processing)
976 */
977 protected function stripIntObjectPlaceholder(&$searchString, &$intObjects)
978 {
979 $tempArray = [];
980 preg_match_all('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', $searchString, $tempArray);
981 $searchString = preg_replace('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', '', $searchString);
982 $intObjects = implode('', $tempArray[0]);
983 }
984
985 /**
986 * Generate meta tags from meta tag TypoScript
987 *
988 * @param array $metaTagTypoScript TypoScript configuration for meta tags (e.g. $GLOBALS['TSFE']->pSetup['meta.'])
989 * @param ContentObjectRenderer $cObj
990 */
991 protected function generateMetaTagHtml(array $metaTagTypoScript, ContentObjectRenderer $cObj)
992 {
993 $pageRenderer = $this->getPageRenderer();
994
995 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
996 $conf = $typoScriptService->convertTypoScriptArrayToPlainArray($metaTagTypoScript);
997 foreach ($conf as $key => $properties) {
998 $replace = false;
999 if (is_array($properties)) {
1000 $nodeValue = $properties['_typoScriptNodeValue'] ?? '';
1001 $value = trim($cObj->stdWrap($nodeValue, $metaTagTypoScript[$key . '.']) ?? '');
1002 if ($value === '' && !empty($properties['value'])) {
1003 $value = $properties['value'];
1004 $replace = false;
1005 }
1006 } else {
1007 $value = $properties;
1008 }
1009
1010 $attribute = 'name';
1011 if ((is_array($properties) && !empty($properties['httpEquivalent'])) || strtolower($key) === 'refresh') {
1012 $attribute = 'http-equiv';
1013 }
1014 if (is_array($properties) && !empty($properties['attribute'])) {
1015 $attribute = $properties['attribute'];
1016 }
1017 if (is_array($properties) && !empty($properties['replace'])) {
1018 $replace = true;
1019 }
1020
1021 if (!is_array($value)) {
1022 $value = (array)$value;
1023 }
1024 foreach ($value as $subValue) {
1025 if (trim($subValue ?? '') !== '') {
1026 $pageRenderer->setMetaTag($attribute, $key, $subValue, [], $replace);
1027 }
1028 }
1029 }
1030 }
1031
1032 /**
1033 * @return PageRenderer
1034 */
1035 protected function getPageRenderer(): PageRenderer
1036 {
1037 return GeneralUtility::makeInstance(PageRenderer::class);
1038 }
1039
1040 /**
1041 * Adds inline CSS code, by respecting the inlineStyle2TempFile option
1042 *
1043 * @param TypoScriptFrontendController $controller
1044 * @param string $cssStyles the inline CSS styling
1045 * @param bool $excludeFromConcatenation option to see if it should be concatenated
1046 * @param string $inlineBlockName the block name to add it
1047 */
1048 protected function addCssToPageRenderer(TypoScriptFrontendController $controller, string $cssStyles, bool $excludeFromConcatenation, string $inlineBlockName)
1049 {
1050 if (empty($controller->config['config']['inlineStyle2TempFile'] ?? false)) {
1051 $this->getPageRenderer()->addCssInlineBlock($inlineBlockName, $cssStyles, !empty($controller->config['config']['compressCss'] ?? false));
1052 } else {
1053 $this->getPageRenderer()->addCssFile(
1054 GeneralUtility::writeStyleSheetContentToTemporaryFile($cssStyles),
1055 'stylesheet',
1056 'all',
1057 '',
1058 (bool)($controller->config['config']['compressCss'] ?? false),
1059 false,
1060 '',
1061 $excludeFromConcatenation
1062 );
1063 }
1064 }
1065
1066 /**
1067 * Generates the <html> tag by evaluting TypoScript configuration, usually found via:
1068 *
1069 * - Adding extra attributes in addition to pre-generated ones (e.g. "dir")
1070 * config.htmlTag.attributes.no-js = 1
1071 * config.htmlTag.attributes.empty-attribute =
1072 *
1073 * - Adding one full string (no stdWrap!) to the "<html $htmlTagAttributes {config.htmlTag_setParams}>" tag
1074 * config.htmlTag_setParams = string|"none"
1075 *
1076 * If config.htmlTag_setParams = none is set, even the pre-generated values are not added at all anymore.
1077 *
1078 * - "config.htmlTag_stdWrap" always applies over the whole compiled tag.
1079 *
1080 * @param array $htmlTagAttributes pre-generated attributes by doctype/direction etc. values.
1081 * @param array $configuration the TypoScript configuration "config." array
1082 * @param ContentObjectRenderer $cObj
1083 * @return string the full <html> tag as string
1084 */
1085 protected function generateHtmlTag(array $htmlTagAttributes, array $configuration, ContentObjectRenderer $cObj): string
1086 {
1087 if (is_array($configuration['htmlTag.']['attributes.'] ?? null)) {
1088 $attributeString = '';
1089 foreach ($configuration['htmlTag.']['attributes.'] as $attributeName => $value) {
1090 $attributeString .= ' ' . htmlspecialchars($attributeName) . ($value !== '' ? '="' . htmlspecialchars((string)$value) . '"' : '');
1091 // If e.g. "htmlTag.attributes.dir" is set, make sure it is not added again with "implodeAttributes()"
1092 if (isset($htmlTagAttributes[$attributeName])) {
1093 unset($htmlTagAttributes[$attributeName]);
1094 }
1095 }
1096 $attributeString = ltrim(GeneralUtility::implodeAttributes($htmlTagAttributes) . $attributeString);
1097 } elseif (($configuration['htmlTag_setParams'] ?? '') === 'none') {
1098 $attributeString = '';
1099 } elseif (isset($configuration['htmlTag_setParams'])) {
1100 $attributeString = $configuration['htmlTag_setParams'];
1101 } else {
1102 $attributeString = GeneralUtility::implodeAttributes($htmlTagAttributes);
1103 }
1104 $htmlTag = '<html' . ($attributeString ? ' ' . $attributeString : '') . '>';
1105 if (isset($configuration['htmlTag_stdWrap.'])) {
1106 $htmlTag = $cObj->stdWrap($htmlTag, $configuration['htmlTag_stdWrap.']);
1107 }
1108 return $htmlTag;
1109 }
1110
1111 /**
1112 * This request handler can handle any frontend request.
1113 *
1114 * @param ServerRequestInterface $request
1115 * @return bool If the request is not an eID request, TRUE otherwise FALSE
1116 */
1117 public function canHandleRequest(ServerRequestInterface $request): bool
1118 {
1119 return true;
1120 }
1121
1122 /**
1123 * Returns the priority - how eager the handler is to actually handle the
1124 * request.
1125 *
1126 * @return int The priority of the request handler.
1127 */
1128 public function getPriority(): int
1129 {
1130 return 50;
1131 }
1132 }