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