[BUGFIX] Assure that $_POST is never set to null
[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 // @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0.
206 $redirectResponse = $controller->redirectToExternalUrl(true);
207 if ($redirectResponse instanceof ResponseInterface) {
208 $controller->sendHttpHeadersDirectly();
209 return $redirectResponse;
210 }
211
212 // Statistics
213 $GLOBALS['TYPO3_MISC']['microtime_end'] = microtime(true);
214 if ($isOutputting && ($controller->config['config']['debug'] ?? !empty($GLOBALS['TYPO3_CONF_VARS']['FE']['debug']))) {
215 $response = $response->withHeader('X-TYPO3-Parsetime', $this->timeTracker->getParseTime() . 'ms');
216 }
217
218 // Preview info
219 // @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
220 $controller->previewInfo(true);
221
222 // Hook for "end-of-frontend"
223 $_params = ['pObj' => &$controller];
224 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe'] ?? [] as $_funcRef) {
225 GeneralUtility::callUserFunction($_funcRef, $_params, $controller);
226 }
227
228 // Finish time tracking (started in TYPO3\CMS\Frontend\Middleware\TimeTrackerInitialization)
229 $this->timeTracker->pull();
230
231 if ($isOutputting) {
232 $response->getBody()->write($controller->content);
233 // if any code set a response code that is not 200 clear the cache's content
234 // if we fail to do so we would deliver cache content with a wrong header, which causes big mess.
235 if (http_response_code() !== 200 || $response->getStatusCode() !== 200) {
236 $controller->clearPageCacheContent();
237 }
238 }
239
240 return $isOutputting ? $response : new NullResponse();
241 }
242
243 /**
244 * Generates the main content part within <body> tags (except JS files/CSS files).
245 *
246 * @param TypoScriptFrontendController $controller
247 * @return string
248 */
249 protected function generatePageContent(TypoScriptFrontendController $controller): string
250 {
251 $pageContent = $controller->cObj->cObjGet($controller->pSetup) ?: '';
252 if ($controller->pSetup['wrap'] ?? false) {
253 $pageContent = $controller->cObj->wrap($pageContent, $controller->pSetup['wrap']);
254 }
255 if ($controller->pSetup['stdWrap.'] ?? false) {
256 $pageContent = $controller->cObj->stdWrap($pageContent, $controller->pSetup['stdWrap.']);
257 }
258 return $pageContent;
259 }
260
261 /**
262 * Rendering normal HTML-page with header by wrapping the generated content ($pageContent) in body-tags and setting the header accordingly.
263 * Render HTML page with header parts (<head> tag content and wrap around <body> tag) - this is done
264 * after the "main" page Content, since some JS may be inserted at that point.
265 *
266 * @param TypoScriptFrontendController $controller
267 * @param SiteLanguage|null $siteLanguage
268 * @return string
269 */
270 protected function generatePageContentWithHeader(TypoScriptFrontendController $controller, ?SiteLanguage $siteLanguage): string
271 {
272 // Generate the page content, this has to be first, as some additional TSFE-related code could have been written
273 $pageContent = $this->generatePageContent($controller);
274 $pageRenderer = $this->getPageRenderer();
275 if ($controller->config['config']['moveJsFromHeaderToFooter'] ?? false) {
276 $pageRenderer->enableMoveJsFromHeaderToFooter();
277 }
278 if ($controller->config['config']['pageRendererTemplateFile'] ?? false) {
279 try {
280 $file = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($controller->config['config']['pageRendererTemplateFile']);
281 $pageRenderer->setTemplateFile($file);
282 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
283 // do nothing
284 }
285 }
286 $headerComment = trim($controller->config['config']['headerComment'] ?? '');
287 if ($headerComment) {
288 $pageRenderer->addInlineComment("\t" . str_replace(LF, LF . "\t", $headerComment) . LF);
289 }
290 // Setting charset:
291 $theCharset = $controller->metaCharset;
292 // Reset the content variables:
293 $controller->content = '';
294 $htmlTagAttributes = [];
295 $htmlLang = $controller->config['config']['htmlTag_langKey'] ?? ($controller->sys_language_isocode ?: 'en');
296 $direction = $controller->config['config']['htmlTag_dir'] ?? null;
297 if ($siteLanguage !== null) {
298 $direction = $siteLanguage->getDirection();
299 $htmlLang = $siteLanguage->getTwoLetterIsoCode();
300 }
301
302 if ($direction) {
303 $htmlTagAttributes['dir'] = htmlspecialchars($direction);
304 }
305 // Setting document type:
306 $docTypeParts = [];
307 $xmlDocument = true;
308 // Part 1: XML prologue
309 switch ((string)($controller->config['config']['xmlprologue'] ?? '')) {
310 case 'none':
311 $xmlDocument = false;
312 break;
313 case 'xml_10':
314 $docTypeParts[] = '<?xml version="1.0" encoding="' . $theCharset . '"?>';
315 break;
316 case 'xml_11':
317 $docTypeParts[] = '<?xml version="1.1" encoding="' . $theCharset . '"?>';
318 break;
319 case '':
320 if ($controller->xhtmlVersion) {
321 $docTypeParts[] = '<?xml version="1.0" encoding="' . $theCharset . '"?>';
322 } else {
323 $xmlDocument = false;
324 }
325 break;
326 default:
327 $docTypeParts[] = $controller->config['config']['xmlprologue'];
328 }
329 // Part 2: DTD
330 $doctype = $controller->config['config']['doctype'] ?? null;
331 if ($doctype) {
332 switch ($doctype) {
333 case 'xhtml_trans':
334 $docTypeParts[] = '<!DOCTYPE html
335 PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
336 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
337 break;
338 case 'xhtml_strict':
339 $docTypeParts[] = '<!DOCTYPE html
340 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
341 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
342 break;
343 case 'xhtml_basic':
344 $docTypeParts[] = '<!DOCTYPE html
345 PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN"
346 "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">';
347 break;
348 case 'xhtml_11':
349 $docTypeParts[] = '<!DOCTYPE html
350 PUBLIC "-//W3C//DTD XHTML 1.1//EN"
351 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
352 break;
353 case 'xhtml+rdfa_10':
354 $docTypeParts[] = '<!DOCTYPE html
355 PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN"
356 "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">';
357 break;
358 case 'html5':
359 $docTypeParts[] = '<!DOCTYPE html>';
360 if ($xmlDocument) {
361 $pageRenderer->setMetaCharsetTag('<meta charset="|" />');
362 } else {
363 $pageRenderer->setMetaCharsetTag('<meta charset="|">');
364 }
365 break;
366 case 'none':
367 break;
368 default:
369 $docTypeParts[] = $doctype;
370 }
371 } else {
372 $docTypeParts[] = '<!DOCTYPE html>';
373 if ($xmlDocument) {
374 $pageRenderer->setMetaCharsetTag('<meta charset="|" />');
375 } else {
376 $pageRenderer->setMetaCharsetTag('<meta charset="|">');
377 }
378 }
379 if ($controller->xhtmlVersion) {
380 $htmlTagAttributes['xml:lang'] = $htmlLang;
381 }
382 if ($controller->xhtmlVersion < 110 || $doctype === 'html5') {
383 $htmlTagAttributes['lang'] = $htmlLang;
384 }
385 if ($controller->xhtmlVersion || $doctype === 'html5' && $xmlDocument) {
386 // We add this to HTML5 to achieve a slightly better backwards compatibility
387 $htmlTagAttributes['xmlns'] = 'http://www.w3.org/1999/xhtml';
388 if (is_array($controller->config['config']['namespaces.'])) {
389 foreach ($controller->config['config']['namespaces.'] as $prefix => $uri) {
390 // $uri gets htmlspecialchared later
391 $htmlTagAttributes['xmlns:' . htmlspecialchars($prefix)] = $uri;
392 }
393 }
394 }
395 // Swap XML and doctype order around (for MSIE / Opera standards compliance)
396 if ($controller->config['config']['doctypeSwitch'] ?? false) {
397 $docTypeParts = array_reverse($docTypeParts);
398 }
399 // Adding doctype parts:
400 if (!empty($docTypeParts)) {
401 $pageRenderer->setXmlPrologAndDocType(implode(LF, $docTypeParts));
402 }
403 // Begin header section:
404 if (($controller->config['config']['htmlTag_setParams'] ?? '') !== 'none') {
405 $_attr = $controller->config['config']['htmlTag_setParams'] ?? GeneralUtility::implodeAttributes($htmlTagAttributes);
406 } else {
407 $_attr = '';
408 }
409 $htmlTag = '<html' . ($_attr ? ' ' . $_attr : '') . '>';
410 if (isset($controller->config['config']['htmlTag_stdWrap.'])) {
411 $htmlTag = $controller->cObj->stdWrap($htmlTag, $controller->config['config']['htmlTag_stdWrap.']);
412 }
413 $pageRenderer->setHtmlTag($htmlTag);
414 // Head tag:
415 $headTag = $controller->pSetup['headTag'] ?? '<head>';
416 if (isset($controller->pSetup['headTag.'])) {
417 $headTag = $controller->cObj->stdWrap($headTag, $controller->pSetup['headTag.']);
418 }
419 $pageRenderer->setHeadTag($headTag);
420 // Setting charset meta tag:
421 $pageRenderer->setCharSet($theCharset);
422 $pageRenderer->addInlineComment(' This website is powered by TYPO3 - inspiring people to share!
423 TYPO3 is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.
424 TYPO3 is copyright ' . TYPO3_copyright_year . ' of Kasper Skaarhoj. Extensions are copyright of their respective owners.
425 Information and contribution at ' . TYPO3_URL_GENERAL . '
426 ');
427 if ($controller->baseUrl) {
428 $pageRenderer->setBaseUrl($controller->baseUrl);
429 }
430 if ($controller->pSetup['shortcutIcon'] ?? false) {
431 try {
432 $favIcon = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($controller->pSetup['shortcutIcon']);
433 $iconFileInfo = GeneralUtility::makeInstance(ImageInfo::class, Environment::getPublicPath() . '/' . $favIcon);
434 if ($iconFileInfo->isFile()) {
435 $iconMimeType = $iconFileInfo->getMimeType();
436 if ($iconMimeType) {
437 $iconMimeType = ' type="' . $iconMimeType . '"';
438 $pageRenderer->setIconMimeType($iconMimeType);
439 }
440 $pageRenderer->setFavIcon(PathUtility::getAbsoluteWebPath($controller->absRefPrefix . $favIcon));
441 }
442 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
443 // do nothing
444 }
445 }
446 // Including CSS files
447 if (isset($controller->tmpl->setup['plugin.']) && is_array($controller->tmpl->setup['plugin.'])) {
448 $stylesFromPlugins = '';
449 foreach ($controller->tmpl->setup['plugin.'] as $key => $iCSScode) {
450 if (is_array($iCSScode)) {
451 if ($iCSScode['_CSS_DEFAULT_STYLE'] && empty($controller->config['config']['removeDefaultCss'])) {
452 if (isset($iCSScode['_CSS_DEFAULT_STYLE.'])) {
453 $cssDefaultStyle = $controller->cObj->stdWrap($iCSScode['_CSS_DEFAULT_STYLE'], $iCSScode['_CSS_DEFAULT_STYLE.']);
454 } else {
455 $cssDefaultStyle = $iCSScode['_CSS_DEFAULT_STYLE'];
456 }
457 $stylesFromPlugins .= '/* default styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssDefaultStyle . LF;
458 }
459 if ($iCSScode['_CSS_PAGE_STYLE'] && empty($controller->config['config']['removePageCss'])) {
460 $cssPageStyle = implode(LF, $iCSScode['_CSS_PAGE_STYLE']);
461 if (isset($iCSScode['_CSS_PAGE_STYLE.'])) {
462 $cssPageStyle = $controller->cObj->stdWrap($cssPageStyle, $iCSScode['_CSS_PAGE_STYLE.']);
463 }
464 $cssPageStyle = '/* specific page styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssPageStyle;
465 $this->addCssToPageRenderer($controller, $cssPageStyle, true, 'InlinePageCss');
466 }
467 }
468 }
469 if (!empty($stylesFromPlugins)) {
470 $this->addCssToPageRenderer($controller, $stylesFromPlugins, false, 'InlineDefaultCss');
471 }
472 }
473 /**********************************************************************/
474 /* config.includeCSS / config.includeCSSLibs
475 /**********************************************************************/
476 if (isset($controller->pSetup['includeCSS.']) && is_array($controller->pSetup['includeCSS.'])) {
477 foreach ($controller->pSetup['includeCSS.'] as $key => $CSSfile) {
478 if (!is_array($CSSfile)) {
479 $cssFileConfig = &$controller->pSetup['includeCSS.'][$key . '.'];
480 if (isset($cssFileConfig['if.']) && !$controller->cObj->checkIf($cssFileConfig['if.'])) {
481 continue;
482 }
483 if ($cssFileConfig['external']) {
484 $ss = $CSSfile;
485 } else {
486 try {
487 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($CSSfile);
488 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
489 $ss = null;
490 }
491 }
492 if ($ss) {
493 if ($cssFileConfig['import']) {
494 if (!$cssFileConfig['external'] && $ss[0] !== '/') {
495 // To fix MSIE 6 that cannot handle these as relative paths (according to Ben v Ende)
496 $ss = GeneralUtility::dirname(GeneralUtility::getIndpEnv('SCRIPT_NAME')) . '/' . $ss;
497 }
498 $pageRenderer->addCssInlineBlock('import_' . $key, '@import url("' . htmlspecialchars($ss) . '") ' . htmlspecialchars($cssFileConfig['media']) . ';', empty($cssFileConfig['disableCompression']), (bool)$cssFileConfig['forceOnTop']);
499 } else {
500 $pageRenderer->addCssFile(
501 $ss,
502 $cssFileConfig['alternate'] ? 'alternate stylesheet' : 'stylesheet',
503 $cssFileConfig['media'] ?: 'all',
504 $cssFileConfig['title'] ?: '',
505 empty($cssFileConfig['disableCompression']),
506 (bool)$cssFileConfig['forceOnTop'],
507 $cssFileConfig['allWrap'],
508 (bool)$cssFileConfig['excludeFromConcatenation'] || (bool)$cssFileConfig['inline'],
509 $cssFileConfig['allWrap.']['splitChar'],
510 $cssFileConfig['inline']
511 );
512 unset($cssFileConfig);
513 }
514 }
515 }
516 }
517 }
518 if (isset($controller->pSetup['includeCSSLibs.']) && is_array($controller->pSetup['includeCSSLibs.'])) {
519 foreach ($controller->pSetup['includeCSSLibs.'] as $key => $CSSfile) {
520 if (!is_array($CSSfile)) {
521 $cssFileConfig = &$controller->pSetup['includeCSSLibs.'][$key . '.'];
522 if (isset($cssFileConfig['if.']) && !$controller->cObj->checkIf($cssFileConfig['if.'])) {
523 continue;
524 }
525 if ($cssFileConfig['external']) {
526 $ss = $CSSfile;
527 } else {
528 try {
529 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($CSSfile);
530 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
531 $ss = null;
532 }
533 }
534 if ($ss) {
535 if ($cssFileConfig['import']) {
536 if (!$cssFileConfig['external'] && $ss[0] !== '/') {
537 // To fix MSIE 6 that cannot handle these as relative paths (according to Ben v Ende)
538 $ss = GeneralUtility::dirname(GeneralUtility::getIndpEnv('SCRIPT_NAME')) . '/' . $ss;
539 }
540 $pageRenderer->addCssInlineBlock('import_' . $key, '@import url("' . htmlspecialchars($ss) . '") ' . htmlspecialchars($cssFileConfig['media']) . ';', empty($cssFileConfig['disableCompression']), (bool)$cssFileConfig['forceOnTop']);
541 } else {
542 $pageRenderer->addCssLibrary(
543 $ss,
544 $cssFileConfig['alternate'] ? 'alternate stylesheet' : 'stylesheet',
545 $cssFileConfig['media'] ?: 'all',
546 $cssFileConfig['title'] ?: '',
547 empty($cssFileConfig['disableCompression']),
548 (bool)$cssFileConfig['forceOnTop'],
549 $cssFileConfig['allWrap'],
550 (bool)$cssFileConfig['excludeFromConcatenation'] || (bool)$cssFileConfig['inline'],
551 $cssFileConfig['allWrap.']['splitChar'],
552 $cssFileConfig['inline']
553 );
554 unset($cssFileConfig);
555 }
556 }
557 }
558 }
559 }
560
561 // CSS_inlineStyle from TS
562 $style = trim($controller->pSetup['CSS_inlineStyle'] ?? '');
563 $style .= $controller->cObj->cObjGet($controller->pSetup['cssInline.'] ?? null, 'cssInline.');
564 if (trim($style)) {
565 $this->addCssToPageRenderer($controller, $style, true, 'additionalTSFEInlineStyle');
566 }
567 // Javascript Libraries
568 if (isset($controller->pSetup['javascriptLibs.']) && is_array($controller->pSetup['javascriptLibs.'])) {
569 // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, the setting page.javascriptLibs has been deprecated and will be removed in TYPO3 v10.0.
570 trigger_error('The setting page.javascriptLibs will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
571
572 // Include jQuery into the page renderer
573 if (!empty($controller->pSetup['javascriptLibs.']['jQuery'])) {
574 // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, the setting page.javascriptLibs.jQuery has been deprecated and will be removed in TYPO3 v10.0.
575 trigger_error('The setting page.javascriptLibs.jQuery will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
576
577 $jQueryTS = $controller->pSetup['javascriptLibs.']['jQuery.'];
578 // Check if version / source is set, if not set variable to "NULL" to use the default of the page renderer
579 $version = $jQueryTS['version'] ?? null;
580 $source = $jQueryTS['source'] ?? null;
581 // When "noConflict" is not set or "1" enable the default jQuery noConflict mode, otherwise disable the namespace
582 if (!isset($jQueryTS['noConflict']) || !empty($jQueryTS['noConflict'])) {
583 $namespace = 'noConflict';
584 } else {
585 $namespace = PageRenderer::JQUERY_NAMESPACE_NONE;
586 }
587 $pageRenderer->loadJquery($version, $source, $namespace, true);
588 }
589 }
590 // JavaScript library files
591 if (isset($controller->pSetup['includeJSLibs.']) && is_array($controller->pSetup['includeJSLibs.'])) {
592 foreach ($controller->pSetup['includeJSLibs.'] as $key => $JSfile) {
593 if (!is_array($JSfile)) {
594 if (isset($controller->pSetup['includeJSLibs.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSLibs.'][$key . '.']['if.'])) {
595 continue;
596 }
597 if ($controller->pSetup['includeJSLibs.'][$key . '.']['external']) {
598 $ss = $JSfile;
599 } else {
600 try {
601 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
602 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
603 $ss = null;
604 }
605 }
606 if ($ss) {
607 $jsFileConfig = &$controller->pSetup['includeJSLibs.'][$key . '.'];
608 $type = $jsFileConfig['type'];
609 if (!$type) {
610 $type = 'text/javascript';
611 }
612 $crossOrigin = $jsFileConfig['crossorigin'];
613 if (!$crossOrigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
614 $crossOrigin = 'anonymous';
615 }
616 $pageRenderer->addJsLibrary(
617 $key,
618 $ss,
619 $type,
620 empty($jsFileConfig['disableCompression']),
621 (bool)$jsFileConfig['forceOnTop'],
622 $jsFileConfig['allWrap'],
623 (bool)$jsFileConfig['excludeFromConcatenation'],
624 $jsFileConfig['allWrap.']['splitChar'],
625 (bool)$jsFileConfig['async'],
626 $jsFileConfig['integrity'],
627 (bool)$jsFileConfig['defer'],
628 $crossOrigin
629 );
630 unset($jsFileConfig);
631 }
632 }
633 }
634 }
635 if (isset($controller->pSetup['includeJSFooterlibs.']) && is_array($controller->pSetup['includeJSFooterlibs.'])) {
636 foreach ($controller->pSetup['includeJSFooterlibs.'] as $key => $JSfile) {
637 if (!is_array($JSfile)) {
638 if (isset($controller->pSetup['includeJSFooterlibs.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSFooterlibs.'][$key . '.']['if.'])) {
639 continue;
640 }
641 if ($controller->pSetup['includeJSFooterlibs.'][$key . '.']['external']) {
642 $ss = $JSfile;
643 } else {
644 try {
645 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
646 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
647 $ss = null;
648 }
649 }
650 if ($ss) {
651 $jsFileConfig = &$controller->pSetup['includeJSFooterlibs.'][$key . '.'];
652 $type = $jsFileConfig['type'];
653 if (!$type) {
654 $type = 'text/javascript';
655 }
656 $crossorigin = $jsFileConfig['crossorigin'];
657 if (!$crossorigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
658 $crossorigin = 'anonymous';
659 }
660 $pageRenderer->addJsFooterLibrary(
661 $key,
662 $ss,
663 $type,
664 empty($jsFileConfig['disableCompression']),
665 (bool)$jsFileConfig['forceOnTop'],
666 $jsFileConfig['allWrap'],
667 (bool)$jsFileConfig['excludeFromConcatenation'],
668 $jsFileConfig['allWrap.']['splitChar'],
669 (bool)$jsFileConfig['async'],
670 $jsFileConfig['integrity'],
671 (bool)$jsFileConfig['defer'],
672 $crossorigin
673 );
674 unset($jsFileConfig);
675 }
676 }
677 }
678 }
679 // JavaScript files
680 if (isset($controller->pSetup['includeJS.']) && is_array($controller->pSetup['includeJS.'])) {
681 foreach ($controller->pSetup['includeJS.'] as $key => $JSfile) {
682 if (!is_array($JSfile)) {
683 if (isset($controller->pSetup['includeJS.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJS.'][$key . '.']['if.'])) {
684 continue;
685 }
686 if ($controller->pSetup['includeJS.'][$key . '.']['external']) {
687 $ss = $JSfile;
688 } else {
689 try {
690 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
691 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
692 $ss = null;
693 }
694 }
695 if ($ss) {
696 $jsConfig = &$controller->pSetup['includeJS.'][$key . '.'];
697 $type = $jsConfig['type'];
698 if (!$type) {
699 $type = 'text/javascript';
700 }
701 $crossorigin = $jsConfig['crossorigin'];
702 if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
703 $crossorigin = 'anonymous';
704 }
705 $pageRenderer->addJsFile(
706 $ss,
707 $type,
708 empty($jsConfig['disableCompression']),
709 (bool)$jsConfig['forceOnTop'],
710 $jsConfig['allWrap'],
711 (bool)$jsConfig['excludeFromConcatenation'],
712 $jsConfig['allWrap.']['splitChar'],
713 (bool)$jsConfig['async'],
714 $jsConfig['integrity'],
715 (bool)$jsConfig['defer'],
716 $crossorigin
717 );
718 unset($jsConfig);
719 }
720 }
721 }
722 }
723 if (isset($controller->pSetup['includeJSFooter.']) && is_array($controller->pSetup['includeJSFooter.'])) {
724 foreach ($controller->pSetup['includeJSFooter.'] as $key => $JSfile) {
725 if (!is_array($JSfile)) {
726 if (isset($controller->pSetup['includeJSFooter.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSFooter.'][$key . '.']['if.'])) {
727 continue;
728 }
729 if ($controller->pSetup['includeJSFooter.'][$key . '.']['external']) {
730 $ss = $JSfile;
731 } else {
732 try {
733 $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
734 } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
735 $ss = null;
736 }
737 }
738 if ($ss) {
739 $jsConfig = &$controller->pSetup['includeJSFooter.'][$key . '.'];
740 $type = $jsConfig['type'];
741 if (!$type) {
742 $type = 'text/javascript';
743 }
744 $crossorigin = $jsConfig['crossorigin'];
745 if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
746 $crossorigin = 'anonymous';
747 }
748 $pageRenderer->addJsFooterFile(
749 $ss,
750 $type,
751 empty($jsConfig['disableCompression']),
752 (bool)$jsConfig['forceOnTop'],
753 $jsConfig['allWrap'],
754 (bool)$jsConfig['excludeFromConcatenation'],
755 $jsConfig['allWrap.']['splitChar'],
756 (bool)$jsConfig['async'],
757 $jsConfig['integrity'],
758 (bool)$jsConfig['defer'],
759 $crossorigin
760 );
761 unset($jsConfig);
762 }
763 }
764 }
765 }
766 // Headerdata
767 if (isset($controller->pSetup['headerData.']) && is_array($controller->pSetup['headerData.'])) {
768 $pageRenderer->addHeaderData($controller->cObj->cObjGet($controller->pSetup['headerData.'], 'headerData.'));
769 }
770 // Footerdata
771 if (isset($controller->pSetup['footerData.']) && is_array($controller->pSetup['footerData.'])) {
772 $pageRenderer->addFooterData($controller->cObj->cObjGet($controller->pSetup['footerData.'], 'footerData.'));
773 }
774 $controller->generatePageTitle();
775
776 // @internal hook for EXT:seo, will be gone soon, do not use it in your own extensions
777 $_params = ['page' => $controller->page];
778 $_ref = '';
779 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags'] ?? [] as $_funcRef) {
780 GeneralUtility::callUserFunction($_funcRef, $_params, $_ref);
781 }
782
783 $this->generateMetaTagHtml(
784 $controller->pSetup['meta.'] ?? [],
785 $controller->cObj
786 );
787
788 unset($controller->additionalHeaderData['JSCode']);
789 if (isset($controller->config['INTincScript']) && is_array($controller->config['INTincScript'])) {
790 $controller->additionalHeaderData['JSCode'] = $controller->JSCode;
791 // Storing the JSCode vars...
792 $controller->config['INTincScript_ext']['divKey'] = $controller->uniqueHash();
793 $controller->config['INTincScript_ext']['additionalHeaderData'] = $controller->additionalHeaderData;
794 // Storing the header-data array
795 $controller->config['INTincScript_ext']['additionalFooterData'] = $controller->additionalFooterData;
796 // Storing the footer-data array
797 $controller->config['INTincScript_ext']['additionalJavaScript'] = $controller->additionalJavaScript;
798 // Storing the JS-data array
799 $controller->config['INTincScript_ext']['additionalCSS'] = $controller->additionalCSS;
800 // Storing the Style-data array
801 $controller->additionalHeaderData = ['<!--HD_' . $controller->config['INTincScript_ext']['divKey'] . '-->'];
802 // Clearing the array
803 $controller->additionalFooterData = ['<!--FD_' . $controller->config['INTincScript_ext']['divKey'] . '-->'];
804 // Clearing the array
805 $controller->divSection .= '<!--TDS_' . $controller->config['INTincScript_ext']['divKey'] . '-->';
806 } else {
807 $controller->INTincScript_loadJSCode();
808 }
809 $scriptJsCode = '';
810
811 if ($controller->spamProtectEmailAddresses && $controller->spamProtectEmailAddresses !== 'ascii') {
812 $scriptJsCode = '
813 // decrypt helper function
814 function decryptCharcode(n,start,end,offset) {
815 n = n + offset;
816 if (offset > 0 && n > end) {
817 n = start + (n - end - 1);
818 } else if (offset < 0 && n < start) {
819 n = end - (start - n - 1);
820 }
821 return String.fromCharCode(n);
822 }
823 // decrypt string
824 function decryptString(enc,offset) {
825 var dec = "";
826 var len = enc.length;
827 for(var i=0; i < len; i++) {
828 var n = enc.charCodeAt(i);
829 if (n >= 0x2B && n <= 0x3A) {
830 dec += decryptCharcode(n,0x2B,0x3A,offset); // 0-9 . , - + / :
831 } else if (n >= 0x40 && n <= 0x5A) {
832 dec += decryptCharcode(n,0x40,0x5A,offset); // A-Z @
833 } else if (n >= 0x61 && n <= 0x7A) {
834 dec += decryptCharcode(n,0x61,0x7A,offset); // a-z
835 } else {
836 dec += enc.charAt(i);
837 }
838 }
839 return dec;
840 }
841 // decrypt spam-protected emails
842 function linkTo_UnCryptMailto(s) {
843 location.href = decryptString(s,' . $controller->spamProtectEmailAddresses * -1 . ');
844 }
845 ';
846 }
847 // Add inline JS
848 $inlineJS = '';
849 // defined in php
850 if (is_array($controller->inlineJS)) {
851 foreach ($controller->inlineJS as $key => $val) {
852 if (!is_array($val)) {
853 $inlineJS .= LF . $val . LF;
854 }
855 }
856 }
857 // defined in TS with page.inlineJS
858 // Javascript inline code
859 $inline = $controller->cObj->cObjGet($controller->pSetup['jsInline.'] ?? null, 'jsInline.');
860 if ($inline) {
861 $inlineJS .= LF . $inline . LF;
862 }
863 // Javascript inline code for Footer
864 $inlineFooterJs = $controller->cObj->cObjGet($controller->pSetup['jsFooterInline.'] ?? null, 'jsFooterInline.');
865 // Should minify?
866 if ($controller->config['config']['compressJs'] ?? false) {
867 $pageRenderer->enableCompressJavascript();
868 $minifyErrorScript = ($minifyErrorInline = '');
869 $scriptJsCode = GeneralUtility::minifyJavaScript($scriptJsCode, $minifyErrorScript);
870 if ($minifyErrorScript) {
871 $this->timeTracker->setTSlogMessage($minifyErrorScript, 3);
872 }
873 if ($inlineJS) {
874 $inlineJS = GeneralUtility::minifyJavaScript($inlineJS, $minifyErrorInline);
875 if ($minifyErrorInline) {
876 $this->timeTracker->setTSlogMessage($minifyErrorInline, 3);
877 }
878 }
879 if ($inlineFooterJs) {
880 $inlineFooterJs = GeneralUtility::minifyJavaScript($inlineFooterJs, $minifyErrorInline);
881 if ($minifyErrorInline) {
882 $this->timeTracker->setTSlogMessage($minifyErrorInline, 3);
883 }
884 }
885 }
886 if (!isset($controller->config['config']['removeDefaultJS']) || !$controller->config['config']['removeDefaultJS']) {
887 // include default and inlineJS
888 if ($scriptJsCode) {
889 $pageRenderer->addJsInlineCode('_scriptCode', $scriptJsCode, $controller->config['config']['compressJs']);
890 }
891 if ($inlineJS) {
892 $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $controller->config['config']['compressJs']);
893 }
894 if ($inlineFooterJs) {
895 $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $controller->config['config']['compressJs']);
896 }
897 } elseif ($controller->config['config']['removeDefaultJS'] === 'external') {
898 /*
899 * This keeps inlineJS from *_INT Objects from being moved to external files.
900 * At this point in frontend rendering *_INT Objects only have placeholders instead
901 * of actual content so moving these placeholders to external files would
902 * a) break the JS file (syntax errors due to the placeholders)
903 * b) the needed JS would never get included to the page
904 * Therefore inlineJS from *_INT Objects must not be moved to external files but
905 * kept internal.
906 */
907 $inlineJSint = '';
908 $this->stripIntObjectPlaceholder($inlineJS, $inlineJSint);
909 if ($inlineJSint) {
910 $pageRenderer->addJsInlineCode('TS_inlineJSint', $inlineJSint, $controller->config['config']['compressJs']);
911 }
912 if (trim($scriptJsCode . $inlineJS)) {
913 $pageRenderer->addJsFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($scriptJsCode . $inlineJS), 'text/javascript', $controller->config['config']['compressJs']);
914 }
915 if ($inlineFooterJs) {
916 $inlineFooterJSint = '';
917 $this->stripIntObjectPlaceholder($inlineFooterJs, $inlineFooterJSint);
918 if ($inlineFooterJSint) {
919 $pageRenderer->addJsFooterInlineCode('TS_inlineFooterJSint', $inlineFooterJSint, $controller->config['config']['compressJs']);
920 }
921 $pageRenderer->addJsFooterFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($inlineFooterJs), 'text/javascript', $controller->config['config']['compressJs']);
922 }
923 } else {
924 // Include only inlineJS
925 if ($inlineJS) {
926 $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $controller->config['config']['compressJs']);
927 }
928 if ($inlineFooterJs) {
929 $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $controller->config['config']['compressJs']);
930 }
931 }
932 if (isset($controller->pSetup['inlineLanguageLabelFiles.']) && is_array($controller->pSetup['inlineLanguageLabelFiles.'])) {
933 foreach ($controller->pSetup['inlineLanguageLabelFiles.'] as $key => $languageFile) {
934 if (is_array($languageFile)) {
935 continue;
936 }
937 $languageFileConfig = &$controller->pSetup['inlineLanguageLabelFiles.'][$key . '.'];
938 if (isset($languageFileConfig['if.']) && !$controller->cObj->checkIf($languageFileConfig['if.'])) {
939 continue;
940 }
941 $pageRenderer->addInlineLanguageLabelFile(
942 $languageFile,
943 $languageFileConfig['selectionPrefix'] ?: '',
944 $languageFileConfig['stripFromSelectionName'] ?: ''
945 );
946 }
947 }
948 if (isset($controller->pSetup['inlineSettings.']) && is_array($controller->pSetup['inlineSettings.'])) {
949 $pageRenderer->addInlineSettingArray('TS', $controller->pSetup['inlineSettings.']);
950 }
951 // Compression and concatenate settings
952 if ($controller->config['config']['compressCss'] ?? false) {
953 $pageRenderer->enableCompressCss();
954 }
955 if ($controller->config['config']['compressJs'] ?? false) {
956 $pageRenderer->enableCompressJavascript();
957 }
958 if ($controller->config['config']['concatenateCss'] ?? false) {
959 $pageRenderer->enableConcatenateCss();
960 }
961 if ($controller->config['config']['concatenateJs'] ?? false) {
962 $pageRenderer->enableConcatenateJavascript();
963 }
964 // Backward compatibility for old configuration
965 // @deprecated - remove this option in TYPO3 v10.0.
966 if ($controller->config['config']['concatenateJsAndCss'] ?? false) {
967 trigger_error('Setting config.concatenateJsAndCss is deprecated in favor of config.concatenateJs and config.concatenateCss, and will have no effect anymore in TYPO3 v10.0.', E_USER_DEPRECATED);
968 $pageRenderer->enableConcatenateCss();
969 $pageRenderer->enableConcatenateJavascript();
970 }
971 // Add header data block
972 if ($controller->additionalHeaderData) {
973 $pageRenderer->addHeaderData(implode(LF, $controller->additionalHeaderData));
974 }
975 // Add footer data block
976 if ($controller->additionalFooterData) {
977 $pageRenderer->addFooterData(implode(LF, $controller->additionalFooterData));
978 }
979 // Header complete, now add content
980 // Bodytag:
981 if ($controller->config['config']['disableBodyTag'] ?? false) {
982 $bodyTag = '';
983 } else {
984 $defBT = (isset($controller->pSetup['bodyTagCObject']) && $controller->pSetup['bodyTagCObject'])
985 ? $controller->cObj->cObjGetSingle($controller->pSetup['bodyTagCObject'], $controller->pSetup['bodyTagCObject.'], 'bodyTagCObject')
986 : '<body>';
987 $bodyTag = (isset($controller->pSetup['bodyTag']) && $controller->pSetup['bodyTag'])
988 ? $controller->pSetup['bodyTag']
989 : $defBT;
990 if (trim($controller->pSetup['bodyTagAdd'] ?? '')) {
991 $bodyTag = preg_replace('/>$/', '', trim($bodyTag)) . ' ' . trim($controller->pSetup['bodyTagAdd']) . '>';
992 }
993 }
994 $pageRenderer->addBodyContent(LF . $bodyTag);
995 // Div-sections
996 if ($controller->divSection) {
997 $pageRenderer->addBodyContent(LF . $controller->divSection);
998 }
999 // Page content
1000 $pageRenderer->addBodyContent(LF . $pageContent);
1001 if (!empty($controller->config['INTincScript']) && is_array($controller->config['INTincScript'])) {
1002 // Store the serialized pageRenderer in configuration
1003 $controller->config['INTincScript_ext']['pageRenderer'] = serialize($pageRenderer);
1004 // Render complete page, keep placeholders for JavaScript and CSS
1005 $pageContent = $pageRenderer->renderPageWithUncachedObjects($controller->config['INTincScript_ext']['divKey']);
1006 } else {
1007 // Render complete page
1008 $pageContent = $pageRenderer->render();
1009 }
1010 return $pageContent ?: '';
1011 }
1012
1013 /*************************
1014 *
1015 * Helper functions
1016 *
1017 *************************/
1018
1019 /**
1020 * Searches for placeholder created from *_INT cObjects, removes them from
1021 * $searchString and merges them to $intObjects
1022 *
1023 * @param string $searchString The String which should be cleaned from int-object markers
1024 * @param string $intObjects The String the found int-placeholders are moved to (for further processing)
1025 */
1026 protected function stripIntObjectPlaceholder(&$searchString, &$intObjects)
1027 {
1028 $tempArray = [];
1029 preg_match_all('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', $searchString, $tempArray);
1030 $searchString = preg_replace('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', '', $searchString);
1031 $intObjects = implode('', $tempArray[0]);
1032 }
1033
1034 /**
1035 * Generate meta tags from meta tag TypoScript
1036 *
1037 * @param array $metaTagTypoScript TypoScript configuration for meta tags (e.g. $GLOBALS['TSFE']->pSetup['meta.'])
1038 * @param ContentObjectRenderer $cObj
1039 */
1040 protected function generateMetaTagHtml(array $metaTagTypoScript, ContentObjectRenderer $cObj)
1041 {
1042 $pageRenderer = $this->getPageRenderer();
1043
1044 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1045 $conf = $typoScriptService->convertTypoScriptArrayToPlainArray($metaTagTypoScript);
1046 foreach ($conf as $key => $properties) {
1047 $replace = false;
1048 if (is_array($properties)) {
1049 $nodeValue = $properties['_typoScriptNodeValue'] ?? '';
1050 $value = trim($cObj->stdWrap($nodeValue, $metaTagTypoScript[$key . '.']) ?? '');
1051 if ($value === '' && !empty($properties['value'])) {
1052 $value = $properties['value'];
1053 $replace = false;
1054 }
1055 } else {
1056 $value = $properties;
1057 }
1058
1059 $attribute = 'name';
1060 if ((is_array($properties) && !empty($properties['httpEquivalent'])) || strtolower($key) === 'refresh') {
1061 $attribute = 'http-equiv';
1062 }
1063 if (is_array($properties) && !empty($properties['attribute'])) {
1064 $attribute = $properties['attribute'];
1065 }
1066 if (is_array($properties) && !empty($properties['replace'])) {
1067 $replace = true;
1068 }
1069
1070 if (!is_array($value)) {
1071 $value = (array)$value;
1072 }
1073 foreach ($value as $subValue) {
1074 if (trim($subValue ?? '') !== '') {
1075 $pageRenderer->setMetaTag($attribute, $key, $subValue, [], $replace);
1076 }
1077 }
1078 }
1079 }
1080
1081 /**
1082 * @return PageRenderer
1083 */
1084 protected function getPageRenderer(): PageRenderer
1085 {
1086 return GeneralUtility::makeInstance(PageRenderer::class);
1087 }
1088
1089 /**
1090 * Adds inline CSS code, by respecting the inlineStyle2TempFile option
1091 *
1092 * @param TypoScriptFrontendController $controller
1093 * @param string $cssStyles the inline CSS styling
1094 * @param bool $excludeFromConcatenation option to see if it should be concatenated
1095 * @param string $inlineBlockName the block name to add it
1096 */
1097 protected function addCssToPageRenderer(TypoScriptFrontendController $controller, string $cssStyles, bool $excludeFromConcatenation, string $inlineBlockName)
1098 {
1099 if (empty($controller->config['config']['inlineStyle2TempFile'] ?? false)) {
1100 $this->getPageRenderer()->addCssInlineBlock($inlineBlockName, $cssStyles, !empty($controller->config['config']['compressCss'] ?? false));
1101 } else {
1102 $this->getPageRenderer()->addCssFile(
1103 GeneralUtility::writeStyleSheetContentToTemporaryFile($cssStyles),
1104 'stylesheet',
1105 'all',
1106 '',
1107 (bool)($controller->config['config']['compressCss'] ?? false),
1108 false,
1109 '',
1110 $excludeFromConcatenation
1111 );
1112 }
1113 }
1114
1115 /**
1116 * This request handler can handle any frontend request.
1117 *
1118 * @param ServerRequestInterface $request
1119 * @return bool If the request is not an eID request, TRUE otherwise FALSE
1120 */
1121 public function canHandleRequest(ServerRequestInterface $request): bool
1122 {
1123 return true;
1124 }
1125
1126 /**
1127 * Returns the priority - how eager the handler is to actually handle the
1128 * request.
1129 *
1130 * @return int The priority of the request handler.
1131 */
1132 public function getPriority(): int
1133 {
1134 return 50;
1135 }
1136 }