[!!!][TASK] Drop "documentation" extension
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Controller / TypoScriptFrontendController.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Doctrine\DBAL\DBALException;
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use Psr\Log\LoggerAwareInterface;
21 use Psr\Log\LoggerAwareTrait;
22 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
23 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24 use TYPO3\CMS\Core\Cache\CacheManager;
25 use TYPO3\CMS\Core\Charset\CharsetConverter;
26 use TYPO3\CMS\Core\Charset\UnknownCharsetException;
27 use TYPO3\CMS\Core\Controller\ErrorPageController;
28 use TYPO3\CMS\Core\Core\Environment;
29 use TYPO3\CMS\Core\Database\ConnectionPool;
30 use TYPO3\CMS\Core\Database\Query\QueryHelper;
31 use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
32 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
33 use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
34 use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
35 use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
36 use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException;
37 use TYPO3\CMS\Core\Error\Http\ShortcutTargetPageNotFoundException;
38 use TYPO3\CMS\Core\Localization\LanguageService;
39 use TYPO3\CMS\Core\Locking\Exception\LockAcquireWouldBlockException;
40 use TYPO3\CMS\Core\Locking\LockFactory;
41 use TYPO3\CMS\Core\Locking\LockingStrategyInterface;
42 use TYPO3\CMS\Core\Log\LogManager;
43 use TYPO3\CMS\Core\Page\PageRenderer;
44 use TYPO3\CMS\Core\Resource\StorageRepository;
45 use TYPO3\CMS\Core\Service\DependencyOrderingService;
46 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
47 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
48 use TYPO3\CMS\Core\Type\Bitmask\Permission;
49 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
50 use TYPO3\CMS\Core\TypoScript\TemplateService;
51 use TYPO3\CMS\Core\Utility\ArrayUtility;
52 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
53 use TYPO3\CMS\Core\Utility\GeneralUtility;
54 use TYPO3\CMS\Core\Utility\HttpUtility;
55 use TYPO3\CMS\Core\Utility\MathUtility;
56 use TYPO3\CMS\Core\Utility\PathUtility;
57 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
58 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
59 use TYPO3\CMS\Frontend\Http\UrlHandlerInterface;
60 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
61 use TYPO3\CMS\Frontend\Page\PageRepository;
62
63 /**
64 * Class for the built TypoScript based frontend. Instantiated in
65 * \TYPO3\CMS\Frontend\Http\RequestHandler as the global object TSFE.
66 *
67 * Main frontend class, instantiated in \TYPO3\CMS\Frontend\Http\RequestHandler
68 * as the global object TSFE.
69 *
70 * This class has a lot of functions and internal variable which are used from
71 * \TYPO3\CMS\Frontend\Http\RequestHandler
72 *
73 * The class is instantiated as $GLOBALS['TSFE'] in \TYPO3\CMS\Frontend\Http\RequestHandler.
74 *
75 * The use of this class should be inspired by the order of function calls as
76 * found in \TYPO3\CMS\Frontend\Http\RequestHandler.
77 */
78 class TypoScriptFrontendController implements LoggerAwareInterface
79 {
80 use LoggerAwareTrait;
81
82 /**
83 * The page id (int)
84 * @var string
85 */
86 public $id = '';
87
88 /**
89 * The type (read-only)
90 * @var int
91 */
92 public $type = '';
93
94 /**
95 * The submitted cHash
96 * @var string
97 */
98 public $cHash = '';
99
100 /**
101 * Page will not be cached. Write only TRUE. Never clear value (some other
102 * code might have reasons to set it TRUE).
103 * @var bool
104 */
105 public $no_cache = false;
106
107 /**
108 * The rootLine (all the way to tree root, not only the current site!)
109 * @var array
110 */
111 public $rootLine = '';
112
113 /**
114 * The pagerecord
115 * @var array
116 */
117 public $page = '';
118
119 /**
120 * This will normally point to the same value as id, but can be changed to
121 * point to another page from which content will then be displayed instead.
122 * @var int
123 */
124 public $contentPid = 0;
125
126 /**
127 * Gets set when we are processing a page of type mounpoint with enabled overlay in getPageAndRootline()
128 * Used later in checkPageForMountpointRedirect() to determine the final target URL where the user
129 * should be redirected to.
130 *
131 * @var array|null
132 */
133 protected $originalMountPointPage;
134
135 /**
136 * Gets set when we are processing a page of type shortcut in the early stages
137 * of the request when we do not know about languages yet, used later in the request
138 * to determine the correct shortcut in case a translation changes the shortcut
139 * target
140 * @var array|null
141 * @see checkTranslatedShortcut()
142 */
143 protected $originalShortcutPage;
144
145 /**
146 * sys_page-object, pagefunctions
147 *
148 * @var PageRepository
149 */
150 public $sys_page = '';
151
152 /**
153 * Contains all URL handler instances that are active for the current request.
154 *
155 * The methods isGeneratePage(), isOutputting() and isINTincScript() depend on this property.
156 *
157 * @var \TYPO3\CMS\Frontend\Http\UrlHandlerInterface[]
158 * @see initializeRedirectUrlHandlers()
159 * @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0.
160 */
161 protected $activeUrlHandlers = [];
162
163 /**
164 * Is set to 1 if a pageNotFound handler could have been called.
165 * @var int
166 */
167 public $pageNotFound = 0;
168
169 /**
170 * Domain start page
171 * @var int
172 */
173 public $domainStartPage = 0;
174
175 /**
176 * Array containing a history of why a requested page was not accessible.
177 * @var array
178 */
179 public $pageAccessFailureHistory = [];
180
181 /**
182 * @var string
183 */
184 public $MP = '';
185
186 /**
187 * This can be set from applications as a way to tag cached versions of a page
188 * and later perform some external cache management, like clearing only a part
189 * of the cache of a page...
190 * @var int
191 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.
192 */
193 public $page_cache_reg1 = 0;
194
195 /**
196 * Contains the value of the current script path that activated the frontend.
197 * Typically "index.php" but by rewrite rules it could be something else! Used
198 * for Speaking Urls / Simulate Static Documents.
199 * @var string
200 */
201 public $siteScript = '';
202
203 /**
204 * The frontend user
205 *
206 * @var FrontendUserAuthentication
207 */
208 public $fe_user = '';
209
210 /**
211 * Global flag indicating that a frontend user is logged in. This is set only if
212 * a user really IS logged in. The group-list may show other groups (like added
213 * by IP filter or so) even though there is no user.
214 * @var bool
215 */
216 public $loginUser = false;
217
218 /**
219 * (RO=readonly) The group list, sorted numerically. Group '0,-1' is the default
220 * group, but other groups may be added by other means than a user being logged
221 * in though...
222 * @var string
223 */
224 public $gr_list = '';
225
226 /**
227 * Flag that indicates if a backend user is logged in!
228 * @var bool
229 */
230 public $beUserLogin = false;
231
232 /**
233 * Integer, that indicates which workspace is being previewed.
234 * Not in use anymore, as this is part of the workspace preview functionality, use $TSFE->whichWorkspace() instead.
235 * @var int
236 */
237 public $workspacePreview = 0;
238
239 /**
240 * Shows whether logins are allowed in branch
241 * @var bool
242 */
243 public $loginAllowedInBranch = true;
244
245 /**
246 * Shows specific mode (all or groups)
247 * @var string
248 */
249 public $loginAllowedInBranch_mode = '';
250
251 /**
252 * Set to backend user ID to initialize when keyword-based preview is used
253 * @var int
254 */
255 public $ADMCMD_preview_BEUSER_uid = 0;
256
257 /**
258 * Flag indication that preview is active. This is based on the login of a
259 * backend user and whether the backend user has read access to the current
260 * page.
261 * @var int
262 */
263 public $fePreview = 0;
264
265 /**
266 * Flag indicating that hidden pages should be shown, selected and so on. This
267 * goes for almost all selection of pages!
268 * @var bool
269 */
270 public $showHiddenPage = false;
271
272 /**
273 * Flag indicating that hidden records should be shown. This includes
274 * sys_template and even fe_groups in addition to all
275 * other regular content. So in effect, this includes everything except pages.
276 * @var bool
277 */
278 public $showHiddenRecords = false;
279
280 /**
281 * Value that contains the simulated usergroup if any
282 * @var int
283 */
284 public $simUserGroup = 0;
285
286 /**
287 * "CONFIG" object from TypoScript. Array generated based on the TypoScript
288 * configuration of the current page. Saved with the cached pages.
289 * @var array
290 */
291 public $config = [];
292
293 /**
294 * The TypoScript template object. Used to parse the TypoScript template
295 *
296 * @var TemplateService
297 */
298 public $tmpl;
299
300 /**
301 * Is set to the time-to-live time of cached pages. If FALSE, default is
302 * 60*60*24, which is 24 hours.
303 * @var bool|int
304 */
305 public $cacheTimeOutDefault = false;
306
307 /**
308 * Set internally if cached content is fetched from the database
309 * @var bool
310 * @internal
311 */
312 public $cacheContentFlag = false;
313
314 /**
315 * Set to the expire time of cached content
316 * @var int
317 */
318 public $cacheExpires = 0;
319
320 /**
321 * Set if cache headers allowing caching are sent.
322 * @var bool
323 */
324 public $isClientCachable = false;
325
326 /**
327 * Used by template fetching system. This array is an identification of
328 * the template. If $this->all is empty it's because the template-data is not
329 * cached, which it must be.
330 * @var array
331 */
332 public $all = [];
333
334 /**
335 * Toplevel - objArrayName, eg 'page'
336 * @var string
337 */
338 public $sPre = '';
339
340 /**
341 * TypoScript configuration of the page-object pointed to by sPre.
342 * $this->tmpl->setup[$this->sPre.'.']
343 * @var array
344 */
345 public $pSetup = '';
346
347 /**
348 * This hash is unique to the template, the $this->id and $this->type vars and
349 * the gr_list (list of groups). Used to get and later store the cached data
350 * @var string
351 */
352 public $newHash = '';
353
354 /**
355 * If config.ftu (Frontend Track User) is set in TypoScript for the current
356 * page, the string value of this var is substituted in the rendered source-code
357 * with the string, '&ftu=[token...]' which enables GET-method usertracking as
358 * opposed to cookie based
359 * @var string
360 */
361 public $getMethodUrlIdToken = '';
362
363 /**
364 * This flag is set before inclusion of pagegen.php IF no_cache is set. If this
365 * flag is set after the inclusion of pagegen.php, no_cache is forced to be set.
366 * This is done in order to make sure that php-code from pagegen does not falsely
367 * clear the no_cache flag.
368 * @var bool
369 */
370 public $no_cacheBeforePageGen = false;
371
372 /**
373 * This flag indicates if temporary content went into the cache during
374 * page-generation.
375 * @var mixed
376 */
377 public $tempContent = false;
378
379 /**
380 * Passed to TypoScript template class and tells it to force template rendering
381 * @var bool
382 */
383 public $forceTemplateParsing = false;
384
385 /**
386 * The array which cHash_calc is based on, see ->makeCacheHash().
387 * @var array
388 */
389 public $cHash_array = [];
390
391 /**
392 * May be set to the pagesTSconfig
393 * @var array
394 */
395 public $pagesTSconfig = '';
396
397 /**
398 * Eg. insert JS-functions in this array ($additionalHeaderData) to include them
399 * once. Use associative keys.
400 *
401 * Keys in use:
402 *
403 * used to accumulate additional HTML-code for the header-section,
404 * <head>...</head>. Insert either associative keys (like
405 * additionalHeaderData['myStyleSheet'], see reserved keys above) or num-keys
406 * (like additionalHeaderData[] = '...')
407 *
408 * @var array
409 */
410 public $additionalHeaderData = [];
411
412 /**
413 * Used to accumulate additional HTML-code for the footer-section of the template
414 * @var array
415 */
416 public $additionalFooterData = [];
417
418 /**
419 * Used to accumulate additional JavaScript-code. Works like
420 * additionalHeaderData. Reserved keys at 'openPic' and 'mouseOver'
421 *
422 * @var array
423 */
424 public $additionalJavaScript = [];
425
426 /**
427 * Used to accumulate additional Style code. Works like additionalHeaderData.
428 *
429 * @var array
430 */
431 public $additionalCSS = [];
432
433 /**
434 * @var string
435 */
436 public $JSCode;
437
438 /**
439 * @var string
440 */
441 public $inlineJS;
442
443 /**
444 * Used to accumulate DHTML-layers.
445 * @var string
446 */
447 public $divSection = '';
448
449 /**
450 * Debug flag. If TRUE special debug-output maybe be shown (which includes html-formatting).
451 * @var bool
452 */
453 public $debug = false;
454
455 /**
456 * Default internal target
457 * @var string
458 */
459 public $intTarget = '';
460
461 /**
462 * Default external target
463 * @var string
464 */
465 public $extTarget = '';
466
467 /**
468 * Default file link target
469 * @var string
470 */
471 public $fileTarget = '';
472
473 /**
474 * Keys are page ids and values are default &MP (mount point) values to set
475 * when using the linking features...)
476 * @var array
477 */
478 public $MP_defaults = [];
479
480 /**
481 * If set, typolink() function encrypts email addresses. Is set in pagegen-class.
482 * @var string|int
483 */
484 public $spamProtectEmailAddresses = 0;
485
486 /**
487 * Absolute Reference prefix
488 * @var string
489 */
490 public $absRefPrefix = '';
491
492 /**
493 * <A>-tag parameters
494 * @var string
495 */
496 public $ATagParams = '';
497
498 /**
499 * Search word regex, calculated if there has been search-words send. This is
500 * used to mark up the found search words on a page when jumped to from a link
501 * in a search-result.
502 * @var string
503 */
504 public $sWordRegEx = '';
505
506 /**
507 * Is set to the incoming array sword_list in case of a page-view jumped to from
508 * a search-result.
509 * @var string
510 */
511 public $sWordList = '';
512
513 /**
514 * A string prepared for insertion in all links on the page as url-parameters.
515 * Based on configuration in TypoScript where you defined which GET_VARS you
516 * would like to pass on.
517 * @var string
518 */
519 public $linkVars = '';
520
521 /**
522 * If set, edit icons are rendered aside content records. Must be set only if
523 * the ->beUserLogin flag is set and set_no_cache() must be called as well.
524 * @var string
525 */
526 public $displayEditIcons = '';
527
528 /**
529 * If set, edit icons are rendered aside individual fields of content. Must be
530 * set only if the ->beUserLogin flag is set and set_no_cache() must be called as
531 * well.
532 * @var string
533 */
534 public $displayFieldEditIcons = '';
535
536 /**
537 * Site language, 0 (zero) is default, int+ is uid pointing to a sys_language
538 * record. Should reflect which language menus, templates etc is displayed in
539 * (master language) - but not necessarily the content which could be falling
540 * back to default (see sys_language_content)
541 * @var int
542 */
543 public $sys_language_uid = 0;
544
545 /**
546 * Site language mode for content fall back.
547 * @var string
548 */
549 public $sys_language_mode = '';
550
551 /**
552 * Site content selection uid (can be different from sys_language_uid if content
553 * is to be selected from a fall-back language. Depends on sys_language_mode)
554 * @var int
555 */
556 public $sys_language_content = 0;
557
558 /**
559 * Site content overlay flag; If set - and sys_language_content is > 0 - ,
560 * records selected will try to look for a translation pointing to their uid. (If
561 * configured in [ctrl][languageField] / [ctrl][transOrigP...]
562 * Possible values: [0,1,hideNonTranslated]
563 * This flag is set based on TypoScript config.sys_language_overlay setting
564 *
565 * @var int|string
566 */
567 public $sys_language_contentOL = 0;
568
569 /**
570 * Is set to the iso code of the sys_language_content if that is properly defined
571 * by the sys_language record representing the sys_language_uid.
572 * @var string
573 */
574 public $sys_language_isocode = '';
575
576 /**
577 * 'Global' Storage for various applications. Keys should be 'tx_'.extKey for
578 * extensions.
579 * @var array
580 */
581 public $applicationData = [];
582
583 /**
584 * @var array
585 */
586 public $register = [];
587
588 /**
589 * Stack used for storing array and retrieving register arrays (see
590 * LOAD_REGISTER and RESTORE_REGISTER)
591 * @var array
592 */
593 public $registerStack = [];
594
595 /**
596 * Checking that the function is not called eternally. This is done by
597 * interrupting at a depth of 50
598 * @var int
599 */
600 public $cObjectDepthCounter = 50;
601
602 /**
603 * Used by RecordContentObject and ContentContentObject to ensure the a records is NOT
604 * rendered twice through it!
605 * @var array
606 */
607 public $recordRegister = [];
608
609 /**
610 * This is set to the [table]:[uid] of the latest record rendered. Note that
611 * class ContentObjectRenderer has an equal value, but that is pointing to the
612 * record delivered in the $data-array of the ContentObjectRenderer instance, if
613 * the cObjects CONTENT or RECORD created that instance
614 * @var string
615 */
616 public $currentRecord = '';
617
618 /**
619 * Used by class \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject
620 * to keep track of access-keys.
621 * @var array
622 */
623 public $accessKey = [];
624
625 /**
626 * Numerical array where image filenames are added if they are referenced in the
627 * rendered document. This includes only TYPO3 generated/inserted images.
628 * @var array
629 */
630 public $imagesOnPage = [];
631
632 /**
633 * Is set in ContentObjectRenderer->cImage() function to the info-array of the
634 * most recent rendered image. The information is used in ImageTextContentObject
635 * @var array
636 */
637 public $lastImageInfo = [];
638
639 /**
640 * Used to generate page-unique keys. Point is that uniqid() functions is very
641 * slow, so a unikey key is made based on this, see function uniqueHash()
642 * @var int
643 */
644 public $uniqueCounter = 0;
645
646 /**
647 * @var string
648 */
649 public $uniqueString = '';
650
651 /**
652 * This value will be used as the title for the page in the indexer (if
653 * indexing happens)
654 * @var string
655 */
656 public $indexedDocTitle = '';
657
658 /**
659 * Alternative page title (normally the title of the page record). Can be set
660 * from applications you make.
661 * @var string
662 */
663 public $altPageTitle = '';
664
665 /**
666 * The base URL set for the page header.
667 * @var string
668 */
669 public $baseUrl = '';
670
671 /**
672 * IDs we already rendered for this page (to make sure they are unique)
673 * @var array
674 */
675 private $usedUniqueIds = [];
676
677 /**
678 * Page content render object
679 *
680 * @var ContentObjectRenderer
681 */
682 public $cObj = '';
683
684 /**
685 * All page content is accumulated in this variable. See pagegen.php
686 * @var string
687 */
688 public $content = '';
689
690 /**
691 * Output charset of the websites content. This is the charset found in the
692 * header, meta tag etc. If different than utf-8 a conversion
693 * happens before output to browser. Defaults to utf-8.
694 * @var string
695 */
696 public $metaCharset = 'utf-8';
697
698 /**
699 * Set to the system language key (used on the site)
700 * @var string
701 */
702 public $lang = '';
703
704 /**
705 * Internal calculations for labels
706 *
707 * @var LanguageService
708 */
709 protected $languageService;
710
711 /**
712 * @var LockingStrategyInterface[][]
713 */
714 protected $locks = [];
715
716 /**
717 * @var PageRenderer
718 */
719 protected $pageRenderer;
720
721 /**
722 * The page cache object, use this to save pages to the cache and to
723 * retrieve them again
724 *
725 * @var \TYPO3\CMS\Core\Cache\Backend\AbstractBackend
726 */
727 protected $pageCache;
728
729 /**
730 * @var array
731 */
732 protected $pageCacheTags = [];
733
734 /**
735 * The cHash Service class used for cHash related functionality
736 *
737 * @var CacheHashCalculator
738 */
739 protected $cacheHash;
740
741 /**
742 * Runtime cache of domains per processed page ids.
743 *
744 * @var array
745 */
746 protected $domainDataCache = [];
747
748 /**
749 * Content type HTTP header being sent in the request.
750 * @todo Ticket: #63642 Should be refactored to a request/response model later
751 * @internal Should only be used by TYPO3 core for now
752 *
753 * @var string
754 */
755 protected $contentType = 'text/html';
756
757 /**
758 * Doctype to use
759 *
760 * @var string
761 */
762 public $xhtmlDoctype = '';
763
764 /**
765 * @var int
766 */
767 public $xhtmlVersion;
768
769 /**
770 * Originally requested id from the initial $_GET variable
771 *
772 * @var int
773 */
774 protected $requestedId;
775
776 /**
777 * Class constructor
778 * Takes a number of GET/POST input variable as arguments and stores them internally.
779 * The processing of these variables goes on later in this class.
780 * Also sets a unique string (->uniqueString) for this script instance; A md5 hash of the microtime()
781 *
782 * @param array $_ unused, previously defined to set TYPO3_CONF_VARS
783 * @param mixed $id The value of GeneralUtility::_GP('id')
784 * @param int $type The value of GeneralUtility::_GP('type')
785 * @param bool|string $no_cache The value of GeneralUtility::_GP('no_cache'), evaluated to 1/0
786 * @param string $cHash The value of GeneralUtility::_GP('cHash')
787 * @param string $_2 previously was used to define the jumpURL
788 * @param string $MP The value of GeneralUtility::_GP('MP')
789 * @see \TYPO3\CMS\Frontend\Http\RequestHandler
790 */
791 public function __construct($_ = null, $id, $type, $no_cache = '', $cHash = '', $_2 = null, $MP = '')
792 {
793 // Setting some variables:
794 $this->id = $id;
795 $this->type = $type;
796 if ($no_cache) {
797 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter']) {
798 $warning = '&no_cache=1 has been ignored because $TYPO3_CONF_VARS[\'FE\'][\'disableNoCacheParameter\'] is set!';
799 $this->getTimeTracker()->setTSlogMessage($warning, 2);
800 } else {
801 $warning = '&no_cache=1 has been supplied, so caching is disabled! URL: "' . GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL') . '"';
802 $this->disableCache();
803 }
804 // note: we need to instantiate the logger manually here since the injection happens after the constructor
805 GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__)->warning($warning);
806 }
807 $this->cHash = $cHash;
808 $this->MP = $GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids'] ? (string)$MP : '';
809 $this->uniqueString = md5(microtime());
810 $this->initPageRenderer();
811 // Call post processing function for constructor:
812 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-PostProc'] ?? [] as $_funcRef) {
813 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
814 }
815 $this->cacheHash = GeneralUtility::makeInstance(CacheHashCalculator::class);
816 $this->initCaches();
817 }
818
819 /**
820 * Initializes the page renderer object
821 */
822 protected function initPageRenderer()
823 {
824 if ($this->pageRenderer !== null) {
825 return;
826 }
827 $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
828 $this->pageRenderer->setTemplateFile('EXT:frontend/Resources/Private/Templates/MainPage.html');
829 }
830
831 /**
832 * @param string $contentType
833 * @internal Should only be used by TYPO3 core for now
834 */
835 public function setContentType($contentType)
836 {
837 $this->contentType = $contentType;
838 }
839
840 /**
841 * Connect to SQL database. May exit after outputting an error message
842 * or some JavaScript redirecting to the install tool.
843 *
844 * @throws \RuntimeException
845 * @throws ServiceUnavailableException
846 * @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0.
847 */
848 public function connectToDB()
849 {
850 trigger_error('The method "' . __METHOD__ . '" will be removed in TYPO3 v10.0, as the database connection is checked in the TypoScriptFrontendInitialization middleware.', E_USER_DEPRECATED);
851 try {
852 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');
853 $connection->connect();
854 } catch (DBALException $exception) {
855 // Cannot connect to current database
856 $message = sprintf(
857 'Cannot connect to the configured database. Connection failed with: "%s"',
858 $exception->getMessage()
859 );
860 $this->logger->emergency($message, ['exception' => $exception]);
861 try {
862 $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message);
863 $this->sendResponseAndExit($response);
864 } catch (ServiceUnavailableException $e) {
865 throw new ServiceUnavailableException($message, 1301648782);
866 }
867 }
868 // Call post processing function for DB connection:
869 $_params = ['pObj' => &$this];
870 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['connectToDB'] ?? [] as $_funcRef) {
871 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
872 }
873 }
874
875 /********************************************
876 *
877 * Initializing, resolving page id
878 *
879 ********************************************/
880 /**
881 * Initializes the caching system.
882 */
883 protected function initCaches()
884 {
885 $this->pageCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_pages');
886 }
887
888 /**
889 * Initializes the front-end login user.
890 */
891 public function initFEuser()
892 {
893 $this->fe_user = GeneralUtility::makeInstance(FrontendUserAuthentication::class);
894 // List of pid's acceptable
895 $pid = GeneralUtility::_GP('pid');
896 $this->fe_user->checkPid_value = $pid ? implode(',', GeneralUtility::intExplode(',', $pid)) : 0;
897 // Check if a session is transferred:
898 if (GeneralUtility::_GP('FE_SESSION_KEY')) {
899 $fe_sParts = explode('-', GeneralUtility::_GP('FE_SESSION_KEY'));
900 // If the session key hash check is OK:
901 if (md5($fe_sParts[0] . '/' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']) === (string)$fe_sParts[1]) {
902 $cookieName = FrontendUserAuthentication::getCookieName();
903 $_COOKIE[$cookieName] = $fe_sParts[0];
904 if (isset($_SERVER['HTTP_COOKIE'])) {
905 // See http://forge.typo3.org/issues/27740
906 $_SERVER['HTTP_COOKIE'] .= ';' . $cookieName . '=' . $fe_sParts[0];
907 }
908 $this->fe_user->forceSetCookie = true;
909 $this->fe_user->dontSetCookie = false;
910 unset($cookieName);
911 }
912 }
913 $this->fe_user->start();
914 $this->fe_user->unpack_uc();
915
916 // Call hook for possible manipulation of frontend user object
917 $_params = ['pObj' => &$this];
918 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['initFEuser'] ?? [] as $_funcRef) {
919 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
920 }
921 }
922
923 /**
924 * Initializes the front-end user groups.
925 * Sets ->loginUser and ->gr_list based on front-end user status.
926 */
927 public function initUserGroups()
928 {
929 // This affects the hidden-flag selecting the fe_groups for the user!
930 $this->fe_user->showHiddenRecords = $this->showHiddenRecords;
931 // no matter if we have an active user we try to fetch matching groups which can be set without an user (simulation for instance!)
932 $this->fe_user->fetchGroupData();
933 if (is_array($this->fe_user->user) && !empty($this->fe_user->groupData['uid'])) {
934 // global flag!
935 $this->loginUser = true;
936 // group -2 is not an existing group, but denotes a 'default' group when a user IS logged in. This is used to let elements be shown for all logged in users!
937 $this->gr_list = '0,-2';
938 $gr_array = $this->fe_user->groupData['uid'];
939 } else {
940 $this->loginUser = false;
941 // group -1 is not an existing group, but denotes a 'default' group when not logged in. This is used to let elements be hidden, when a user is logged in!
942 $this->gr_list = '0,-1';
943 if ($this->loginAllowedInBranch) {
944 // For cases where logins are not banned from a branch usergroups can be set based on IP masks so we should add the usergroups uids.
945 $gr_array = $this->fe_user->groupData['uid'];
946 } else {
947 // Set to blank since we will NOT risk any groups being set when no logins are allowed!
948 $gr_array = [];
949 }
950 }
951 // Clean up.
952 // Make unique...
953 $gr_array = array_unique($gr_array);
954 // sort
955 sort($gr_array);
956 if (!empty($gr_array) && !$this->loginAllowedInBranch_mode) {
957 $this->gr_list .= ',' . implode(',', $gr_array);
958 }
959
960 // For every 60 seconds the is_online timestamp for a logged-in user is updated
961 if ($this->loginUser) {
962 $this->fe_user->updateOnlineTimestamp();
963 }
964
965 $this->logger->debug('Valid usergroups for TSFE: ' . $this->gr_list);
966 }
967
968 /**
969 * Checking if a user is logged in or a group constellation different from "0,-1"
970 *
971 * @return bool TRUE if either a login user is found (array fe_user->user) OR if the gr_list is set to something else than '0,-1' (could be done even without a user being logged in!)
972 */
973 public function isUserOrGroupSet()
974 {
975 return is_array($this->fe_user->user) || $this->gr_list !== '0,-1';
976 }
977
978 /**
979 * Provides ways to bypass the '?id=[xxx]&type=[xx]' format, using either PATH_INFO or virtual HTML-documents (using Apache mod_rewrite)
980 *
981 * Two options:
982 * 1) Use PATH_INFO (also Apache) to extract id and type from that var. Does not require any special modules compiled with apache. (less typical)
983 * 2) Using hook which enables features like those provided from "realurl" extension (AKA "Speaking URLs")
984 *
985 * @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0.
986 */
987 public function checkAlternativeIdMethods()
988 {
989 trigger_error('This method "' . __METHOD__ . '" is removed, extensions should use a Frontend PSR-15-based middleware to hook into the frontend process. There is no need to call this method directly.', E_USER_DEPRECATED);
990 $this->siteScript = GeneralUtility::getIndpEnv('TYPO3_SITE_SCRIPT');
991 // Call post processing function for custom URL methods.
992 $_params = ['pObj' => &$this];
993 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'] ?? [] as $_funcRef) {
994 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
995 }
996 }
997
998 /**
999 * Clears the preview-flags, sets sim_exec_time to current time.
1000 * Hidden pages must be hidden as default, $GLOBALS['SIM_EXEC_TIME'] is set to $GLOBALS['EXEC_TIME']
1001 * in bootstrap initializeGlobalTimeVariables(). Alter it by adding or subtracting seconds.
1002 */
1003 public function clear_preview()
1004 {
1005 $this->showHiddenPage = false;
1006 $this->showHiddenRecords = false;
1007 $GLOBALS['SIM_EXEC_TIME'] = $GLOBALS['EXEC_TIME'];
1008 $GLOBALS['SIM_ACCESS_TIME'] = $GLOBALS['ACCESS_TIME'];
1009 $this->fePreview = 0;
1010 }
1011
1012 /**
1013 * Checks if a backend user is logged in
1014 *
1015 * @return bool whether a backend user is logged in
1016 */
1017 public function isBackendUserLoggedIn()
1018 {
1019 return (bool)$this->beUserLogin;
1020 }
1021
1022 /**
1023 * Creates the backend user object and returns it.
1024 *
1025 * @return FrontendBackendUserAuthentication the backend user object
1026 * @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0.
1027 */
1028 public function initializeBackendUser()
1029 {
1030 trigger_error('The method "' . __METHOD__ . '" is deprecated, and will be removed in TYPO3 v10. Extensions should ensure that the BackendAuthenticator middleware is run to load a backend user.', E_USER_DEPRECATED);
1031 // PRE BE_USER HOOK
1032 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preBeUser'] ?? [] as $_funcRef) {
1033 $_params = [];
1034 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1035 }
1036 $backendUserObject = null;
1037 // If the backend cookie is set,
1038 // we proceed and check if a backend user is logged in.
1039 if ($_COOKIE[BackendUserAuthentication::getCookieName()]) {
1040 $GLOBALS['TYPO3_MISC']['microtime_BE_USER_start'] = microtime(true);
1041 $this->getTimeTracker()->push('Back End user initialized');
1042 $this->beUserLogin = false;
1043 // New backend user object
1044 $backendUserObject = GeneralUtility::makeInstance(FrontendBackendUserAuthentication::class);
1045 $backendUserObject->start();
1046 $backendUserObject->unpack_uc();
1047 if (!empty($backendUserObject->user['uid'])) {
1048 $backendUserObject->fetchGroupData();
1049 }
1050 // Unset the user initialization if any setting / restriction applies
1051 if (!$backendUserObject->checkBackendAccessSettingsFromInitPhp()) {
1052 $backendUserObject = null;
1053 } elseif (!empty($backendUserObject->user['uid'])) {
1054 // If the user is active now, let the controller know
1055 $this->beUserLogin = true;
1056 } else {
1057 $backendUserObject = null;
1058 }
1059 $this->getTimeTracker()->pull();
1060 $GLOBALS['TYPO3_MISC']['microtime_BE_USER_end'] = microtime(true);
1061 }
1062 // POST BE_USER HOOK
1063 $_params = [
1064 'BE_USER' => &$backendUserObject
1065 ];
1066 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['postBeUser'] ?? [] as $_funcRef) {
1067 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1068 }
1069 return $backendUserObject;
1070 }
1071
1072 /**
1073 * Determines the id and evaluates any preview settings
1074 * Basically this function is about determining whether a backend user is logged in,
1075 * if he has read access to the page and if he's previewing the page.
1076 * That all determines which id to show and how to initialize the id.
1077 */
1078 public function determineId()
1079 {
1080 // Call pre processing function for id determination
1081 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PreProcessing'] ?? [] as $functionReference) {
1082 $parameters = ['parentObject' => $this];
1083 GeneralUtility::callUserFunction($functionReference, $parameters, $this);
1084 }
1085 // If there is a Backend login we are going to check for any preview settings
1086 $originalFrontendUserGroups = $this->applyPreviewSettings($this->getBackendUser());
1087 // If the front-end is showing a preview, caching MUST be disabled.
1088 if ($this->fePreview) {
1089 $this->disableCache();
1090 }
1091 // Now, get the id, validate access etc:
1092 $this->fetch_the_id();
1093 // Check if backend user has read access to this page. If not, recalculate the id.
1094 if ($this->beUserLogin && $this->fePreview && !$this->getBackendUser()->doesUserHaveAccess($this->page, Permission::PAGE_SHOW)) {
1095 // Resetting
1096 $this->clear_preview();
1097 $this->fe_user->user[$this->fe_user->usergroup_column] = $originalFrontendUserGroups;
1098 // Fetching the id again, now with the preview settings reset.
1099 $this->fetch_the_id();
1100 }
1101 // Checks if user logins are blocked for a certain branch and if so, will unset user login and re-fetch ID.
1102 $this->loginAllowedInBranch = $this->checkIfLoginAllowedInBranch();
1103 // Logins are not allowed:
1104 if (!$this->loginAllowedInBranch) {
1105 // Only if there is a login will we run this...
1106 if ($this->isUserOrGroupSet()) {
1107 if ($this->loginAllowedInBranch_mode === 'all') {
1108 // Clear out user and group:
1109 $this->fe_user->hideActiveLogin();
1110 $this->gr_list = '0,-1';
1111 } else {
1112 $this->gr_list = '0,-2';
1113 }
1114 // Fetching the id again, now with the preview settings reset.
1115 $this->fetch_the_id();
1116 }
1117 }
1118 // Final cleaning.
1119 // Make sure it's an integer
1120 $this->id = ($this->contentPid = (int)$this->id);
1121 // Make sure it's an integer
1122 $this->type = (int)$this->type;
1123 // Call post processing function for id determination:
1124 $_params = ['pObj' => &$this];
1125 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PostProc'] ?? [] as $_funcRef) {
1126 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1127 }
1128 }
1129
1130 /**
1131 * Evaluates admin panel or workspace settings to see if
1132 * visibility settings like
1133 * - $fePreview
1134 * - $showHiddenPage
1135 * - $showHiddenRecords
1136 * - $simUserGroup
1137 * should be applied to the current object.
1138 *
1139 * @param FrontendBackendUserAuthentication $backendUser
1140 * @return string|null null if no changes to the current frontend usergroups have been made, otherwise the original list of frontend usergroups
1141 * @private do not use this in your extension code.
1142 */
1143 protected function applyPreviewSettings($backendUser = null)
1144 {
1145 if (!$backendUser) {
1146 return null;
1147 }
1148 $originalFrontendUser = null;
1149 if ($this->fe_user->user) {
1150 $originalFrontendUser = $this->fe_user->user[$this->fe_user->usergroup_column];
1151 }
1152
1153 // The preview flag is set if the current page turns out to be hidden
1154 if ($this->id && $this->determineIdIsHiddenPage()) {
1155 $this->fePreview = 1;
1156 $this->showHiddenPage = true;
1157 }
1158 // The preview flag will be set if an offline workspace will be previewed
1159 if ($this->whichWorkspace() > 0) {
1160 $this->fePreview = 1;
1161 }
1162 return $this->simUserGroup ? $originalFrontendUser : null;
1163 }
1164
1165 /**
1166 * Checks if the page is hidden in the active workspace.
1167 * If it is hidden, preview flags will be set.
1168 *
1169 * @return bool
1170 */
1171 protected function determineIdIsHiddenPage()
1172 {
1173 $field = MathUtility::canBeInterpretedAsInteger($this->id) ? 'uid' : 'alias';
1174
1175 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1176 ->getQueryBuilderForTable('pages');
1177 $queryBuilder
1178 ->getRestrictions()
1179 ->removeAll()
1180 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1181
1182 $page = $queryBuilder
1183 ->select('uid', 'hidden', 'starttime', 'endtime')
1184 ->from('pages')
1185 ->where(
1186 $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($this->id)),
1187 $queryBuilder->expr()->gte('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
1188 )
1189 ->setMaxResults(1)
1190 ->execute()
1191 ->fetch();
1192
1193 if ($this->whichWorkspace() > 0) {
1194 // Fetch overlay of page if in workspace and check if it is hidden
1195 $pageSelectObject = GeneralUtility::makeInstance(PageRepository::class);
1196 $pageSelectObject->init(false);
1197 $targetPage = $pageSelectObject->getWorkspaceVersionOfRecord($this->whichWorkspace(), 'pages', $page['uid']);
1198 $result = $targetPage === -1 || $targetPage === -2;
1199 } else {
1200 $result = is_array($page) && ($page['hidden'] || $page['starttime'] > $GLOBALS['SIM_EXEC_TIME'] || $page['endtime'] != 0 && $page['endtime'] <= $GLOBALS['SIM_EXEC_TIME']);
1201 }
1202 return $result;
1203 }
1204
1205 /**
1206 * Resolves the page id and sets up several related properties.
1207 *
1208 * If $this->id is not set at all or is not a plain integer, the method
1209 * does it's best to set the value to an integer. Resolving is based on
1210 * this options:
1211 *
1212 * - Splitting $this->id if it contains an additional type parameter.
1213 * - Getting the id for an alias in $this->id
1214 * - Finding the domain record start page
1215 * - First visible page
1216 * - Relocating the id below the domain record if outside
1217 *
1218 * The following properties may be set up or updated:
1219 *
1220 * - id
1221 * - requestedId
1222 * - type
1223 * - domainStartPage
1224 * - sys_page
1225 * - sys_page->where_groupAccess
1226 * - sys_page->where_hid_del
1227 * - loginUser
1228 * - gr_list
1229 * - no_cache
1230 * - register['SYS_LASTCHANGED']
1231 * - pageNotFound
1232 *
1233 * Via getPageAndRootlineWithDomain()
1234 *
1235 * - rootLine
1236 * - page
1237 * - MP
1238 * - originalShortcutPage
1239 * - originalMountPointPage
1240 * - pageAccessFailureHistory['direct_access']
1241 * - pageNotFound
1242 *
1243 * @todo:
1244 *
1245 * On the first impression the method does to much. This is increased by
1246 * the fact, that is is called repeated times by the method determineId.
1247 * The reasons are manifold.
1248 *
1249 * 1.) The first part, the creation of sys_page, the type and alias
1250 * resolution don't need to be repeated. They could be separated to be
1251 * called only once.
1252 *
1253 * 2.) The user group setup could be done once on a higher level.
1254 *
1255 * 3.) The workflow of the resolution could be elaborated to be less
1256 * tangled. Maybe the check of the page id to be below the domain via the
1257 * root line doesn't need to be done each time, but for the final result
1258 * only.
1259 *
1260 * 4.) The root line does not need to be directly addressed by this class.
1261 * A root line is always related to one page. The rootline could be handled
1262 * indirectly by page objects. Page objects still don't exist.
1263 *
1264 * @throws ServiceUnavailableException
1265 * @access private
1266 */
1267 public function fetch_the_id()
1268 {
1269 $timeTracker = $this->getTimeTracker();
1270 $timeTracker->push('fetch_the_id initialize/');
1271 // Initialize the page-select functions.
1272 $this->sys_page = GeneralUtility::makeInstance(PageRepository::class);
1273 $this->sys_page->versioningWorkspaceId = $this->whichWorkspace();
1274 $this->sys_page->init($this->showHiddenPage);
1275 // Set the valid usergroups for FE
1276 $this->initUserGroups();
1277 // Sets sys_page where-clause
1278 $this->setSysPageWhereClause();
1279 // If $this->id is a string, it's an alias
1280 $this->checkAndSetAlias();
1281 // The id and type is set to the integer-value - just to be sure...
1282 $this->id = (int)$this->id;
1283 $this->type = (int)$this->type;
1284 $timeTracker->pull();
1285 // We find the first page belonging to the current domain
1286 $timeTracker->push('fetch_the_id domain/');
1287 if (!$this->id) {
1288 if ($this->domainStartPage) {
1289 // If the id was not previously set, set it to the id of the domain.
1290 $this->id = $this->domainStartPage;
1291 } else {
1292 // Find the first 'visible' page in that domain
1293 $theFirstPage = $this->sys_page->getFirstWebPage($this->id);
1294 if ($theFirstPage) {
1295 $this->id = $theFirstPage['uid'];
1296 } else {
1297 $message = 'No pages are found on the rootlevel!';
1298 $this->logger->alert($message);
1299 try {
1300 $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message);
1301 $this->sendResponseAndExit($response);
1302 } catch (ServiceUnavailableException $e) {
1303 throw new ServiceUnavailableException($message, 1301648975);
1304 }
1305 }
1306 }
1307 }
1308 $timeTracker->pull();
1309 $timeTracker->push('fetch_the_id rootLine/');
1310 // We store the originally requested id
1311 $this->requestedId = $this->id;
1312 try {
1313 $this->getPageAndRootlineWithDomain($this->domainStartPage);
1314 } catch (ShortcutTargetPageNotFoundException $e) {
1315 $this->pageNotFound = 1;
1316 }
1317 $timeTracker->pull();
1318 if ($this->pageNotFound) {
1319 $pNotFoundMsg = [
1320 1 => 'ID was not an accessible page',
1321 2 => 'Subsection was found and not accessible',
1322 3 => 'ID was outside the domain',
1323 4 => 'The requested page alias does not exist'
1324 ];
1325 $message = $pNotFoundMsg[$this->pageNotFound];
1326 if ($this->pageNotFound === 1 || $this->pageNotFound === 2) {
1327 $response = GeneralUtility::makeInstance(ErrorController::class)->accessDeniedAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
1328 } else {
1329 $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
1330 }
1331 $this->sendResponseAndExit($response);
1332 }
1333 // Init SYS_LASTCHANGED
1334 $this->register['SYS_LASTCHANGED'] = (int)$this->page['tstamp'];
1335 if ($this->register['SYS_LASTCHANGED'] < (int)$this->page['SYS_LASTCHANGED']) {
1336 $this->register['SYS_LASTCHANGED'] = (int)$this->page['SYS_LASTCHANGED'];
1337 }
1338 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['fetchPageId-PostProcessing'] ?? [] as $functionReference) {
1339 $parameters = ['parentObject' => $this];
1340 GeneralUtility::callUserFunction($functionReference, $parameters, $this);
1341 }
1342 }
1343
1344 /**
1345 * Loads the page and root line records based on $this->id
1346 *
1347 * A final page and the matching root line are determined and loaded by
1348 * the algorithm defined by this method.
1349 *
1350 * First it loads the initial page from the page repository for $this->id.
1351 * If that can't be loaded directly, it gets the root line for $this->id.
1352 * It walks up the root line towards the root page until the page
1353 * repository can deliver a page record. (The loading restrictions of
1354 * the root line records are more liberal than that of the page record.)
1355 *
1356 * Now the page type is evaluated and handled if necessary. If the page is
1357 * a short cut, it is replaced by the target page. If the page is a mount
1358 * point in overlay mode, the page is replaced by the mounted page.
1359 *
1360 * After this potential replacements are done, the root line is loaded
1361 * (again) for this page record. It walks up the root line up to
1362 * the first viewable record.
1363 *
1364 * (While upon the first accessibility check of the root line it was done
1365 * by loading page by page from the page repository, this time the method
1366 * checkRootlineForIncludeSection() is used to find the most distant
1367 * accessible page within the root line.)
1368 *
1369 * Having found the final page id, the page record and the root line are
1370 * loaded for last time by this method.
1371 *
1372 * Exceptions may be thrown for DOKTYPE_SPACER and not loadable page records
1373 * or root lines.
1374 *
1375 * If $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'] is set,
1376 * instead of throwing an exception it's handled by a page unavailable
1377 * handler.
1378 *
1379 * May set or update this properties:
1380 *
1381 * @see TypoScriptFrontendController::$id
1382 * @see TypoScriptFrontendController::$MP
1383 * @see TypoScriptFrontendController::$page
1384 * @see TypoScriptFrontendController::$pageNotFound
1385 * @see TypoScriptFrontendController::$pageAccessFailureHistory
1386 * @see TypoScriptFrontendController::$originalMountPointPage
1387 * @see TypoScriptFrontendController::$originalShortcutPage
1388 *
1389 * @throws ServiceUnavailableException
1390 * @throws PageNotFoundException
1391 */
1392 protected function getPageAndRootline()
1393 {
1394 $this->resolveTranslatedPageId();
1395 if (empty($this->page)) {
1396 // If no page, we try to find the page before in the rootLine.
1397 // Page is 'not found' in case the id itself was not an accessible page. code 1
1398 $this->pageNotFound = 1;
1399 $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1400 if (!empty($this->rootLine)) {
1401 $c = count($this->rootLine) - 1;
1402 while ($c > 0) {
1403 // Add to page access failure history:
1404 $this->pageAccessFailureHistory['direct_access'][] = $this->rootLine[$c];
1405 // Decrease to next page in rootline and check the access to that, if OK, set as page record and ID value.
1406 $c--;
1407 $this->id = $this->rootLine[$c]['uid'];
1408 $this->page = $this->sys_page->getPage($this->id);
1409 if (!empty($this->page)) {
1410 break;
1411 }
1412 }
1413 }
1414 // If still no page...
1415 if (empty($this->page)) {
1416 $message = 'The requested page does not exist!';
1417 $this->logger->error($message);
1418 try {
1419 $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
1420 $this->sendResponseAndExit($response);
1421 } catch (PageNotFoundException $e) {
1422 throw new PageNotFoundException($message, 1301648780);
1423 }
1424 }
1425 }
1426 // Spacer is not accessible in frontend
1427 if ($this->page['doktype'] == PageRepository::DOKTYPE_SPACER) {
1428 $message = 'The requested page does not exist!';
1429 $this->logger->error($message);
1430 try {
1431 $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
1432 $this->sendResponseAndExit($response);
1433 } catch (PageNotFoundException $e) {
1434 throw new PageNotFoundException($message, 1301648781);
1435 }
1436 }
1437 // Is the ID a link to another page??
1438 if ($this->page['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
1439 // We need to clear MP if the page is a shortcut. Reason is if the short cut goes to another page, then we LEAVE the rootline which the MP expects.
1440 $this->MP = '';
1441 // saving the page so that we can check later - when we know
1442 // about languages - whether we took the correct shortcut or
1443 // whether a translation of the page overwrites the shortcut
1444 // target and we need to follow the new target
1445 $this->originalShortcutPage = $this->page;
1446 $this->page = $this->sys_page->getPageShortcut($this->page['shortcut'], $this->page['shortcut_mode'], $this->page['uid']);
1447 $this->id = $this->page['uid'];
1448 }
1449 // If the page is a mountpoint which should be overlaid with the contents of the mounted page,
1450 // it must never be accessible directly, but only in the mountpoint context. Therefore we change
1451 // the current ID and the user is redirected by checkPageForMountpointRedirect().
1452 if ($this->page['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT && $this->page['mount_pid_ol']) {
1453 $this->originalMountPointPage = $this->page;
1454 $this->page = $this->sys_page->getPage($this->page['mount_pid']);
1455 if (empty($this->page)) {
1456 $message = 'This page (ID ' . $this->originalMountPointPage['uid'] . ') is of type "Mount point" and '
1457 . 'mounts a page which is not accessible (ID ' . $this->originalMountPointPage['mount_pid'] . ').';
1458 throw new PageNotFoundException($message, 1402043263);
1459 }
1460 $this->MP = $this->page['uid'] . '-' . $this->originalMountPointPage['uid'];
1461 $this->id = $this->page['uid'];
1462 }
1463 // Gets the rootLine
1464 $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1465 // If not rootline we're off...
1466 if (empty($this->rootLine)) {
1467 $message = 'The requested page didn\'t have a proper connection to the tree-root!';
1468 $this->logger->error($message);
1469 try {
1470 $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
1471 $this->sendResponseAndExit($response);
1472 } catch (ServiceUnavailableException $e) {
1473 throw new ServiceUnavailableException($message, 1301648167);
1474 }
1475 }
1476 // Checking for include section regarding the hidden/starttime/endtime/fe_user (that is access control of a whole subbranch!)
1477 if ($this->checkRootlineForIncludeSection()) {
1478 if (empty($this->rootLine)) {
1479 $message = 'The requested page was not accessible!';
1480 try {
1481 $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
1482 $this->sendResponseAndExit($response);
1483 } catch (ServiceUnavailableException $e) {
1484 $this->logger->warning($message);
1485 throw new ServiceUnavailableException($message, 1301648234);
1486 }
1487 } else {
1488 $el = reset($this->rootLine);
1489 $this->id = $el['uid'];
1490 $this->page = $this->sys_page->getPage($this->id);
1491 $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1492 }
1493 }
1494 }
1495
1496 /**
1497 * If $this->id contains a translated page record, this needs to be resolved to the default language
1498 * in order for all rootline functionality and access restrictions to be in place further on.
1499 *
1500 * Additionally, if a translated page is found, $this->sys_language_uid/sys_language_content is set as well.
1501 */
1502 protected function resolveTranslatedPageId()
1503 {
1504 $this->page = $this->sys_page->getPage($this->id);
1505 // Accessed a default language page record, nothing to resolve
1506 if (empty($this->page) || (int)$this->page[$GLOBALS['TCA']['pages']['ctrl']['languageField']] === 0) {
1507 return;
1508 }
1509 $this->sys_language_uid = (int)$this->page[$GLOBALS['TCA']['pages']['ctrl']['languageField']];
1510 $this->sys_language_content = $this->sys_language_uid;
1511 $this->page = $this->sys_page->getPage($this->page[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']]);
1512 $this->id = $this->page['uid'];
1513 // For common best-practice reasons, this is set, however, will be optional for new routing mechanisms
1514 $this->mergingWithGetVars(['L' => $this->sys_language_uid]);
1515 }
1516
1517 /**
1518 * Get page shortcut; Finds the records pointed to by input value $SC (the shortcut value).
1519 *
1520 * @param int $SC The value of the "shortcut" field from the pages record
1521 * @param int $mode The shortcut mode: 1 will select first subpage, 2 a random subpage, 3 the parent page; default is the page pointed to by $SC
1522 * @param int $thisUid The current page UID of the page which is a shortcut
1523 * @param int $itera Safety feature which makes sure that the function is calling itself recursively max 20 times (since this function can find shortcuts to other shortcuts to other shortcuts...)
1524 * @param array $pageLog An array filled with previous page uids tested by the function - new page uids are evaluated against this to avoid going in circles.
1525 * @param bool $disableGroupCheck If true, the group check is disabled when fetching the target page (needed e.g. for menu generation)
1526 * @throws \RuntimeException
1527 * @throws ShortcutTargetPageNotFoundException
1528 * @return mixed Returns the page record of the page that the shortcut pointed to.
1529 * @access private
1530 * @see getPageAndRootline()
1531 * @deprecated As this method conceptually belongs to PageRepository, it is moved in PageRepository, and will be removed in TYPO3 v10.0.
1532 */
1533 public function getPageShortcut($SC, $mode, $thisUid, $itera = 20, $pageLog = [], $disableGroupCheck = false)
1534 {
1535 trigger_error('Method "TypoScriptFrontendController::getPageShortcut()" as been moved to PageRepository - use the page repository directly to call this functionality, as this method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
1536 return $this->sys_page->getPageShortcut($SC, $mode, $thisUid, $itera, $pageLog, $disableGroupCheck);
1537 }
1538
1539 /**
1540 * Checks if visibility of the page is blocked upwards in the root line.
1541 *
1542 * If any page in the root line is blocking visibility, true is returend.
1543 *
1544 * All pages from the blocking page downwards are removed from the root
1545 * line, so that the remaning pages can be used to relocate the page up
1546 * to lowest visible page.
1547 *
1548 * The blocking feature of a page must be turned on by setting the page
1549 * record field 'extendToSubpages' to 1.
1550 *
1551 * The following fields are evaluated then:
1552 *
1553 * hidden, starttime, endtime, fe_group
1554 *
1555 * @todo Find a better name, i.e. checkVisibilityByRootLine
1556 * @todo Invert boolean return value. Return true if visible.
1557 *
1558 * @return bool
1559 */
1560 protected function checkRootlineForIncludeSection(): bool
1561 {
1562 $c = count($this->rootLine);
1563 $removeTheRestFlag = false;
1564 for ($a = 0; $a < $c; $a++) {
1565 if (!$this->checkPagerecordForIncludeSection($this->rootLine[$a])) {
1566 // Add to page access failure history:
1567 $this->pageAccessFailureHistory['sub_section'][] = $this->rootLine[$a];
1568 $removeTheRestFlag = true;
1569 }
1570
1571 if ($this->rootLine[$a]['doktype'] == PageRepository::DOKTYPE_BE_USER_SECTION) {
1572 // If there is a backend user logged in, check if he has read access to the page:
1573 if ($this->beUserLogin) {
1574 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1575 ->getQueryBuilderForTable('pages');
1576
1577 $queryBuilder
1578 ->getRestrictions()
1579 ->removeAll();
1580
1581 $row = $queryBuilder
1582 ->select('uid')
1583 ->from('pages')
1584 ->where(
1585 $queryBuilder->expr()->eq(
1586 'uid',
1587 $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1588 ),
1589 $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)
1590 )
1591 ->execute()
1592 ->fetch();
1593
1594 // versionOL()?
1595 if (!$row) {
1596 // If there was no page selected, the user apparently did not have read access to the current PAGE (not position in rootline) and we set the remove-flag...
1597 $removeTheRestFlag = true;
1598 }
1599 } else {
1600 // Don't go here, if there is no backend user logged in.
1601 $removeTheRestFlag = true;
1602 }
1603 }
1604 if ($removeTheRestFlag) {
1605 // Page is 'not found' in case a subsection was found and not accessible, code 2
1606 $this->pageNotFound = 2;
1607 unset($this->rootLine[$a]);
1608 }
1609 }
1610 return $removeTheRestFlag;
1611 }
1612
1613 /**
1614 * Checks page record for enableFields
1615 * Returns TRUE if enableFields does not disable the page record.
1616 * Takes notice of the ->showHiddenPage flag and uses SIM_ACCESS_TIME for start/endtime evaluation
1617 *
1618 * @param array $row The page record to evaluate (needs fields: hidden, starttime, endtime, fe_group)
1619 * @param bool $bypassGroupCheck Bypass group-check
1620 * @return bool TRUE, if record is viewable.
1621 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getTreeList(), checkPagerecordForIncludeSection()
1622 */
1623 public function checkEnableFields($row, $bypassGroupCheck = false)
1624 {
1625 $_params = ['pObj' => $this, 'row' => &$row, 'bypassGroupCheck' => &$bypassGroupCheck];
1626 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_checkEnableFields'] ?? [] as $_funcRef) {
1627 // Call hooks: If one returns FALSE, method execution is aborted with result "This record is not available"
1628 $return = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1629 if ($return === false) {
1630 return false;
1631 }
1632 }
1633 if ((!$row['hidden'] || $this->showHiddenPage) && $row['starttime'] <= $GLOBALS['SIM_ACCESS_TIME'] && ($row['endtime'] == 0 || $row['endtime'] > $GLOBALS['SIM_ACCESS_TIME']) && ($bypassGroupCheck || $this->checkPageGroupAccess($row))) {
1634 return true;
1635 }
1636 return false;
1637 }
1638
1639 /**
1640 * Check group access against a page record
1641 *
1642 * @param array $row The page record to evaluate (needs field: fe_group)
1643 * @param mixed $groupList List of group id's (comma list or array). Default is $this->gr_list
1644 * @return bool TRUE, if group access is granted.
1645 * @access private
1646 */
1647 public function checkPageGroupAccess($row, $groupList = null)
1648 {
1649 if ($groupList === null) {
1650 $groupList = $this->gr_list;
1651 }
1652 if (!is_array($groupList)) {
1653 $groupList = explode(',', $groupList);
1654 }
1655 $pageGroupList = explode(',', $row['fe_group'] ?: 0);
1656 return count(array_intersect($groupList, $pageGroupList)) > 0;
1657 }
1658
1659 /**
1660 * Checks if the current page of the root line is visible.
1661 *
1662 * If the field extendToSubpages is 0, access is granted,
1663 * else the fields hidden, starttime, endtime, fe_group are evaluated.
1664 *
1665 * @todo Find a better name, i.e. isVisibleRecord()
1666 *
1667 * @param array $row The page record
1668 * @return bool true if visible
1669 * @access private
1670 * @see checkEnableFields()
1671 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getTreeList()
1672 * @see checkRootlineForIncludeSection()
1673 */
1674 public function checkPagerecordForIncludeSection(array $row): bool
1675 {
1676 return !$row['extendToSubpages'] || $this->checkEnableFields($row);
1677 }
1678
1679 /**
1680 * Checks if logins are allowed in the current branch of the page tree. Traverses the full root line and returns TRUE if logins are OK, otherwise FALSE (and then the login user must be unset!)
1681 *
1682 * @return bool returns TRUE if logins are OK, otherwise FALSE (and then the login user must be unset!)
1683 */
1684 public function checkIfLoginAllowedInBranch()
1685 {
1686 // Initialize:
1687 $c = count($this->rootLine);
1688 $loginAllowed = true;
1689 // Traverse root line from root and outwards:
1690 for ($a = 0; $a < $c; $a++) {
1691 // If a value is set for login state:
1692 if ($this->rootLine[$a]['fe_login_mode'] > 0) {
1693 // Determine state from value:
1694 if ((int)$this->rootLine[$a]['fe_login_mode'] === 1) {
1695 $loginAllowed = false;
1696 $this->loginAllowedInBranch_mode = 'all';
1697 } elseif ((int)$this->rootLine[$a]['fe_login_mode'] === 3) {
1698 $loginAllowed = false;
1699 $this->loginAllowedInBranch_mode = 'groups';
1700 } else {
1701 $loginAllowed = true;
1702 }
1703 }
1704 }
1705 return $loginAllowed;
1706 }
1707
1708 /**
1709 * Analysing $this->pageAccessFailureHistory into a summary array telling which features disabled display and on which pages and conditions. That data can be used inside a page-not-found handler
1710 *
1711 * @return array Summary of why page access was not allowed.
1712 */
1713 public function getPageAccessFailureReasons()
1714 {
1715 $output = [];
1716 $combinedRecords = array_merge(is_array($this->pageAccessFailureHistory['direct_access']) ? $this->pageAccessFailureHistory['direct_access'] : [['fe_group' => 0]], is_array($this->pageAccessFailureHistory['sub_section']) ? $this->pageAccessFailureHistory['sub_section'] : []);
1717 if (!empty($combinedRecords)) {
1718 foreach ($combinedRecords as $k => $pagerec) {
1719 // If $k=0 then it is the very first page the original ID was pointing at and that will get a full check of course
1720 // If $k>0 it is parent pages being tested. They are only significant for the access to the first page IF they had the extendToSubpages flag set, hence checked only then!
1721 if (!$k || $pagerec['extendToSubpages']) {
1722 if ($pagerec['hidden']) {
1723 $output['hidden'][$pagerec['uid']] = true;
1724 }
1725 if ($pagerec['starttime'] > $GLOBALS['SIM_ACCESS_TIME']) {
1726 $output['starttime'][$pagerec['uid']] = $pagerec['starttime'];
1727 }
1728 if ($pagerec['endtime'] != 0 && $pagerec['endtime'] <= $GLOBALS['SIM_ACCESS_TIME']) {
1729 $output['endtime'][$pagerec['uid']] = $pagerec['endtime'];
1730 }
1731 if (!$this->checkPageGroupAccess($pagerec)) {
1732 $output['fe_group'][$pagerec['uid']] = $pagerec['fe_group'];
1733 }
1734 }
1735 }
1736 }
1737 return $output;
1738 }
1739
1740 /**
1741 * Gets ->page and ->rootline information based on ->id. ->id may change during this operation.
1742 * If not inside domain, then default to first page in domain.
1743 *
1744 * @param int $domainStartPage Page uid of the page where the found domain record is (pid of the domain record)
1745 * @access private
1746 */
1747 public function getPageAndRootlineWithDomain($domainStartPage)
1748 {
1749 $this->getPageAndRootline();
1750 // Checks if the $domain-startpage is in the rootLine. This is necessary so that references to page-id's from other domains are not possible.
1751 if ($domainStartPage && is_array($this->rootLine)) {
1752 $idFound = false;
1753 foreach ($this->rootLine as $key => $val) {
1754 if ($val['uid'] == $domainStartPage) {
1755 $idFound = true;
1756 break;
1757 }
1758 }
1759 if (!$idFound) {
1760 // Page is 'not found' in case the id was outside the domain, code 3
1761 $this->pageNotFound = 3;
1762 $this->id = $domainStartPage;
1763 // re-get the page and rootline if the id was not found.
1764 $this->getPageAndRootline();
1765 }
1766 }
1767 }
1768
1769 /**
1770 * Sets sys_page where-clause
1771 */
1772 protected function setSysPageWhereClause()
1773 {
1774 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1775 ->getConnectionForTable('pages')
1776 ->getExpressionBuilder();
1777 $this->sys_page->where_hid_del = ' AND ' . (string)$expressionBuilder->andX(
1778 QueryHelper::stripLogicalOperatorPrefix($this->sys_page->where_hid_del),
1779 $expressionBuilder->lt('pages.doktype', 200)
1780 );
1781 $this->sys_page->where_groupAccess = $this->sys_page->getMultipleGroupsWhereClause('pages.fe_group', 'pages');
1782 }
1783
1784 /**
1785 * Page unavailable handler for use in frontend plugins from extensions.
1786 *
1787 * @param string $reason Reason text
1788 * @param string $header HTTP header to send
1789 * @deprecated
1790 */
1791 public function pageUnavailableAndExit($reason = '', $header = '')
1792 {
1793 trigger_error('This method will be removed in TYPO3 v10. Use TYPO3\'s ErrorController with Request/Response objects instead.', E_USER_DEPRECATED);
1794 $header = $header ?: $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling_statheader'];
1795 $this->pageUnavailableHandler($GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'], $header, $reason);
1796 die;
1797 }
1798
1799 /**
1800 * Page-not-found handler for use in frontend plugins from extensions.
1801 *
1802 * @param string $reason Reason text
1803 * @param string $header HTTP header to send
1804 * @deprecated
1805 */
1806 public function pageNotFoundAndExit($reason = '', $header = '')
1807 {
1808 trigger_error('This method will be removed in TYPO3 v10. Use TYPO3\'s ErrorController with Request/Response objects instead.', E_USER_DEPRECATED);
1809 $header = $header ?: $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_statheader'];
1810 $this->pageNotFoundHandler($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'], $header, $reason);
1811 die;
1812 }
1813
1814 /**
1815 * Checks whether the pageUnavailableHandler should be used. To be used, pageUnavailable_handling must be set
1816 * and devIPMask must not match the current visitor's IP address.
1817 *
1818 * @return bool TRUE/FALSE whether the pageUnavailable_handler should be used.
1819 * @deprecated
1820 */
1821 public function checkPageUnavailableHandler()
1822 {
1823 trigger_error('This method will be removed in TYPO3 v10. Use TYPO3\'s ErrorController with Request/Response objects instead.', E_USER_DEPRECATED);
1824 if (
1825 $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling']
1826 && !GeneralUtility::cmpIP(
1827 GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1828 $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
1829 )
1830 ) {
1831 $checkPageUnavailableHandler = true;
1832 } else {
1833 $checkPageUnavailableHandler = false;
1834 }
1835 return $checkPageUnavailableHandler;
1836 }
1837
1838 /**
1839 * Page unavailable handler. Acts a wrapper for the pageErrorHandler method.
1840 *
1841 * @param mixed $code See ['FE']['pageUnavailable_handling'] for possible values
1842 * @param string $header If set, this is passed directly to the PHP function, header()
1843 * @param string $reason If set, error messages will also mention this as the reason for the page-not-found.
1844 * @deprecated
1845 */
1846 public function pageUnavailableHandler($code, $header, $reason)
1847 {
1848 trigger_error('This method will be removed in TYPO3 v10. Use TYPO3\'s ErrorController with Request/Response objects instead.', E_USER_DEPRECATED);
1849 $this->pageErrorHandler($code, $header, $reason);
1850 }
1851
1852 /**
1853 * Page not found handler. Acts a wrapper for the pageErrorHandler method.
1854 *
1855 * @param mixed $code See docs of ['FE']['pageNotFound_handling'] for possible values
1856 * @param string $header If set, this is passed directly to the PHP function, header()
1857 * @param string $reason If set, error messages will also mention this as the reason for the page-not-found.
1858 * @deprecated
1859 */
1860 public function pageNotFoundHandler($code, $header = '', $reason = '')
1861 {
1862 trigger_error('This method will be removed in TYPO3 v10. Use TYPO3\'s ErrorController with Request/Response objects instead.', E_USER_DEPRECATED);
1863 $this->pageErrorHandler($code, $header, $reason);
1864 }
1865
1866 /**
1867 * Generic error page handler.
1868 * Exits.
1869 *
1870 * @param mixed $code See docs of ['FE']['pageNotFound_handling'] and ['FE']['pageUnavailable_handling'] for all possible values
1871 * @param string $header If set, this is passed directly to the PHP function, header()
1872 * @param string $reason If set, error messages will also mention this as the reason for the page-not-found.
1873 * @throws \RuntimeException
1874 * @deprecated
1875 */
1876 public function pageErrorHandler($code, $header = '', $reason = '')
1877 {
1878 trigger_error('This method will be removed in TYPO3 v10. Use TYPO3\'s ErrorController with Request/Response objects instead.', E_USER_DEPRECATED);
1879 // Issue header in any case:
1880 if ($header) {
1881 $headerArr = preg_split('/\\r|\\n/', $header, -1, PREG_SPLIT_NO_EMPTY);
1882 foreach ($headerArr as $header) {
1883 header($header);
1884 }
1885 }
1886 // Create response:
1887 // Simply boolean; Just shows TYPO3 error page with reason:
1888 if (strtolower($code) === 'true' || (string)$code === '1' || gettype($code) === 'boolean') {
1889 echo GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
1890 'Page Not Found',
1891 'The page did not exist or was inaccessible.' . ($reason ? ' Reason: ' . $reason : '')
1892 );
1893 } elseif (GeneralUtility::isFirstPartOfStr($code, 'USER_FUNCTION:')) {
1894 $funcRef = trim(substr($code, 14));
1895 $params = [
1896 'currentUrl' => GeneralUtility::getIndpEnv('REQUEST_URI'),
1897 'reasonText' => $reason,
1898 'pageAccessFailureReasons' => $this->getPageAccessFailureReasons()
1899 ];
1900 try {
1901 echo GeneralUtility::callUserFunction($funcRef, $params, $this);
1902 } catch (\Exception $e) {
1903 throw new \RuntimeException('Error: 404 page by USER_FUNCTION "' . $funcRef . '" failed.', 1509296032, $e);
1904 }
1905 } elseif (GeneralUtility::isFirstPartOfStr($code, 'READFILE:')) {
1906 $readFile = GeneralUtility::getFileAbsFileName(trim(substr($code, 9)));
1907 if (@is_file($readFile)) {
1908 echo str_replace(
1909 [
1910 '###CURRENT_URL###',
1911 '###REASON###'
1912 ],
1913 [
1914 GeneralUtility::getIndpEnv('REQUEST_URI'),
1915 htmlspecialchars($reason)
1916 ],
1917 file_get_contents($readFile)
1918 );
1919 } else {
1920 throw new \RuntimeException('Configuration Error: 404 page "' . $readFile . '" could not be found.', 1294587214);
1921 }
1922 } elseif (GeneralUtility::isFirstPartOfStr($code, 'REDIRECT:')) {
1923 HttpUtility::redirect(substr($code, 9));
1924 } elseif ($code !== '') {
1925 // Check if URL is relative
1926 $url_parts = parse_url($code);
1927 // parse_url could return an array without the key "host", the empty check works better than strict check
1928 if (empty($url_parts['host'])) {
1929 $url_parts['host'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1930 if ($code[0] === '/') {
1931 $code = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST') . $code;
1932 } else {
1933 $code = GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR') . $code;
1934 }
1935 $checkBaseTag = false;
1936 } else {
1937 $checkBaseTag = true;
1938 }
1939 // Check recursion
1940 if ($code == GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')) {
1941 if ($reason == '') {
1942 $reason = 'Page cannot be found.';
1943 }
1944 $reason .= LF . LF . 'Additionally, ' . $code . ' was not found while trying to retrieve the error document.';
1945 throw new \RuntimeException(nl2br(htmlspecialchars($reason)), 1294587215);
1946 }
1947 // Prepare headers
1948 $headerArr = [
1949 'User-agent: ' . GeneralUtility::getIndpEnv('HTTP_USER_AGENT'),
1950 'Referer: ' . GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')
1951 ];
1952 $report = [];
1953 $res = GeneralUtility::getUrl($code, 1, $headerArr, $report);
1954 if ((int)$report['error'] !== 0 && (int)$report['error'] !== 200) {
1955 throw new \RuntimeException('Failed to fetch error page "' . $code . '", reason: ' . $report['message'], 1509296606);
1956 }
1957 // Header and content are separated by an empty line
1958 list($header, $content) = explode(CRLF . CRLF, $res, 2);
1959 $content .= CRLF;
1960 if (false === $res) {
1961 // Last chance -- redirect
1962 HttpUtility::redirect($code);
1963 } else {
1964 // Forward these response headers to the client
1965 $forwardHeaders = [
1966 'Content-Type:'
1967 ];
1968 $headerArr = preg_split('/\\r|\\n/', $header, -1, PREG_SPLIT_NO_EMPTY);
1969 foreach ($headerArr as $header) {
1970 foreach ($forwardHeaders as $h) {
1971 if (preg_match('/^' . $h . '/', $header)) {
1972 header($header);
1973 }
1974 }
1975 }
1976 // Put <base> if necessary
1977 if ($checkBaseTag) {
1978 // If content already has <base> tag, we do not need to do anything
1979 if (false === stristr($content, '<base ')) {
1980 // Generate href for base tag
1981 $base = $url_parts['scheme'] . '://';
1982 if ($url_parts['user'] != '') {
1983 $base .= $url_parts['user'];
1984 if ($url_parts['pass'] != '') {
1985 $base .= ':' . $url_parts['pass'];
1986 }
1987 $base .= '@';
1988 }
1989 $base .= $url_parts['host'];
1990 // Add path portion skipping possible file name
1991 $base .= preg_replace('/(.*\\/)[^\\/]*/', '${1}', $url_parts['path']);
1992 // Put it into content (generate also <head> if necessary)
1993 $replacement = LF . '<base href="' . htmlentities($base) . '" />' . LF;
1994 if (stristr($content, '<head>')) {
1995 $content = preg_replace('/(<head>)/i', '\\1' . $replacement, $content);
1996 } else {
1997 $content = preg_replace('/(<html[^>]*>)/i', '\\1<head>' . $replacement . '</head>', $content);
1998 }
1999 }
2000 }
2001 // Output the content
2002 echo $content;
2003 }
2004 } else {
2005 echo GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
2006 'Page Not Found',
2007 $reason ? 'Reason: ' . $reason : 'Page cannot be found.'
2008 );
2009 }
2010 die;
2011 }
2012
2013 /**
2014 * Fetches the integer page id for a page alias.
2015 * Looks if ->id is not an integer and if so it will search for a page alias and if found the page uid of that page is stored in $this->id
2016 */
2017 protected function checkAndSetAlias()
2018 {
2019 if ($this->id && !MathUtility::canBeInterpretedAsInteger($this->id)) {
2020 $aid = $this->sys_page->getPageIdFromAlias($this->id);
2021 if ($aid) {
2022 $this->id = $aid;
2023 } else {
2024 $this->pageNotFound = 4;
2025 }
2026 }
2027 }
2028
2029 /**
2030 * Merging values into the global $_GET
2031 *
2032 * @param array $GET_VARS Array of key/value pairs that will be merged into the current GET-vars. (Non-escaped values)
2033 */
2034 public function mergingWithGetVars($GET_VARS)
2035 {
2036 if (is_array($GET_VARS)) {
2037 // Getting $_GET var, unescaped.
2038 $realGet = GeneralUtility::_GET();
2039 if (!is_array($realGet)) {
2040 $realGet = [];
2041 }
2042 // Merge new values on top:
2043 ArrayUtility::mergeRecursiveWithOverrule($realGet, $GET_VARS);
2044 // Write values back to $_GET:
2045 GeneralUtility::_GETset($realGet);
2046 // Setting these specifically (like in the init-function):
2047 if (isset($GET_VARS['type'])) {
2048 $this->type = (int)$GET_VARS['type'];
2049 }
2050 if (isset($GET_VARS['cHash'])) {
2051 $this->cHash = $GET_VARS['cHash'];
2052 }
2053 if (isset($GET_VARS['MP'])) {
2054 $this->MP = $GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids'] ? $GET_VARS['MP'] : '';
2055 }
2056 if (isset($GET_VARS['no_cache']) && $GET_VARS['no_cache']) {
2057 $this->set_no_cache('no_cache is requested via GET parameter');
2058 }
2059 }
2060 }
2061
2062 /********************************************
2063 *
2064 * Template and caching related functions.
2065 *
2066 *******************************************/
2067 /**
2068 * Calculates a hash string based on additional parameters in the url.
2069 *
2070 * Calculated hash is stored in $this->cHash_array.
2071 * This is used to cache pages with more parameters than just id and type.
2072 *
2073 * @see reqCHash()
2074 */
2075 public function makeCacheHash()
2076 {
2077 // No need to test anything if caching was already disabled.
2078 if ($this->no_cache && !$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']) {
2079 return;
2080 }
2081 $GET = GeneralUtility::_GET();
2082 if ($this->cHash && is_array($GET)) {
2083 // Make sure we use the page uid and not the page alias
2084 $GET['id'] = $this->id;
2085 $this->cHash_array = $this->cacheHash->getRelevantParameters(GeneralUtility::implodeArrayForUrl('', $GET));
2086 $cHash_calc = $this->cacheHash->calculateCacheHash($this->cHash_array);
2087 if (!hash_equals($cHash_calc, $this->cHash)) {
2088 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']) {
2089 $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], 'Request parameters could not be validated (&cHash comparison failed)');
2090 $this->sendResponseAndExit($response);
2091 } else {
2092 $this->disableCache();
2093 $this->getTimeTracker()->setTSlogMessage('The incoming cHash "' . $this->cHash . '" and calculated cHash "' . $cHash_calc . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($this->cHash_array)) . '"', 2);
2094 }
2095 }
2096 } elseif (is_array($GET)) {
2097 // No cHash is set, check if that is correct
2098 if ($this->cacheHash->doParametersRequireCacheHash(GeneralUtility::implodeArrayForUrl('', $GET))) {
2099 $this->reqCHash();
2100 }
2101 }
2102 }
2103
2104 /**
2105 * Will disable caching if the cHash value was not set.
2106 * This function should be called to check the _existence_ of "&cHash" whenever a plugin generating cacheable output is using extra GET variables. If there _is_ a cHash value the validation of it automatically takes place in makeCacheHash() (see above)
2107 *
2108 * @see makeCacheHash(), \TYPO3\CMS\Frontend\Plugin\AbstractPlugin::pi_cHashCheck()
2109 */
2110 public function reqCHash()
2111 {
2112 if (!$this->cHash) {
2113 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']) {
2114 if ($this->tempContent) {
2115 $this->clearPageCacheContent();
2116 }
2117 $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], 'Request parameters could not be validated (&cHash empty)');
2118 $this->sendResponseAndExit($response);
2119 } else {
2120 $this->disableCache();
2121 $this->getTimeTracker()->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', 2);
2122 }
2123 }
2124 }
2125
2126 /**
2127 * Initialize the TypoScript template parser
2128 */
2129 public function initTemplate()
2130 {
2131 $this->tmpl = GeneralUtility::makeInstance(TemplateService::class);
2132 $this->tmpl->setVerbose((bool)$this->beUserLogin);
2133 $this->tmpl->init();
2134 $this->tmpl->tt_track = (bool)$this->beUserLogin;
2135 }
2136
2137 /**
2138 * See if page is in cache and get it if so
2139 * Stores the page content in $this->content if something is found.
2140 *
2141 * @throws \InvalidArgumentException
2142 * @throws \RuntimeException
2143 */
2144 public function getFromCache()
2145 {
2146 // clearing the content-variable, which will hold the pagecontent
2147 $this->content = '';
2148 // Unsetting the lowlevel config
2149 $this->config = [];
2150 $this->cacheContentFlag = false;
2151
2152 if ($this->no_cache) {
2153 return;
2154 }
2155
2156 $pageSectionCacheContent = $this->tmpl->getCurrentPageData();
2157 if (!is_array($pageSectionCacheContent)) {
2158 // Nothing in the cache, we acquire an "exclusive lock" for the key now.
2159 // We use the Registry to store this lock centrally,
2160 // but we protect the access again with a global exclusive lock to avoid race conditions
2161
2162 $this->acquireLock('pagesection', $this->id . '::' . $this->MP);
2163 //
2164 // from this point on we're the only one working on that page ($key)
2165 //
2166
2167 // query the cache again to see if the page data are there meanwhile
2168 $pageSectionCacheContent = $this->tmpl->getCurrentPageData();
2169 if (is_array($pageSectionCacheContent)) {
2170 // we have the content, nice that some other process did the work for us already
2171 $this->releaseLock('pagesection');
2172 }
2173 // We keep the lock set, because we are the ones generating the page now
2174 // and filling the cache.
2175 // This indicates that we have to release the lock in the Registry later in releaseLocks()
2176 }
2177
2178 if (is_array($pageSectionCacheContent)) {
2179 // BE CAREFUL to change the content of the cc-array. This array is serialized and an md5-hash based on this is used for caching the page.
2180 // If this hash is not the same in here in this section and after page-generation, then the page will not be properly cached!
2181 // This array is an identification of the template. If $this->all is empty it's because the template-data is not cached, which it must be.
2182 $pageSectionCacheContent = $this->tmpl->matching($pageSectionCacheContent);
2183 ksort($pageSectionCacheContent);
2184 $this->all = $pageSectionCacheContent;
2185 }
2186 unset($pageSectionCacheContent);
2187
2188 // Look for page in cache only if a shift-reload is not sent to the server.
2189 $lockHash = $this->getLockHash();
2190 if (!$this->headerNoCache()) {
2191 if ($this->all) {
2192 // we got page section information
2193 $this->newHash = $this->getHash();
2194 $this->getTimeTracker()->push('Cache Row');
2195 $row = $this->getFromCache_queryRow();
2196 if (!is_array($row)) {
2197 // nothing in the cache, we acquire an exclusive lock now
2198
2199 $this->acquireLock('pages', $lockHash);
2200 //
2201 // from this point on we're the only one working on that page ($lockHash)
2202 //
2203
2204 // query the cache again to see if the data are there meanwhile
2205 $row = $this->getFromCache_queryRow();
2206 if (is_array($row)) {
2207 // we have the content, nice that some other process did the work for us
2208 $this->releaseLock('pages');
2209 }
2210 // We keep the lock set, because we are the ones generating the page now
2211 // and filling the cache.
2212 // This indicates that we have to release the lock in the Registry later in releaseLocks()
2213 }
2214 if (is_array($row)) {
2215 // we have data from cache
2216
2217 // Call hook when a page is retrieved from cache:
2218 $_params = ['pObj' => &$this, 'cache_pages_row' => &$row];
2219 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageLoadedFromCache'] ?? [] as $_funcRef) {
2220 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2221 }
2222 // Fetches the lowlevel config stored with the cached data
2223 $this->config = $row['cache_data'];
2224 // Getting the content
2225 $this->content = $row['content'];
2226 // Flag for temp content
2227 $this->tempContent = $row['temp_content'];
2228 // Setting flag, so we know, that some cached content has been loaded
2229 $this->cacheContentFlag = true;
2230 $this->cacheExpires = $row['expires'];
2231
2232 // Restore page title information, this is needed to generate the page title for
2233 // partially cached pages.
2234 $this->page['title'] = $row['pageTitleInfo']['title'];
2235 $this->altPageTitle = $row['pageTitleInfo']['altPageTitle'];
2236 $this->indexedDocTitle = $row['pageTitleInfo']['indexedDocTitle'];
2237
2238 if (isset($this->config['config']['debug'])) {
2239 $debugCacheTime = (bool)$this->config['config']['debug'];
2240 } else {
2241 $debugCacheTime = !empty($GLOBALS['TYPO3_CONF_VARS']['FE']['debug']);
2242 }
2243 if ($debugCacheTime) {
2244 $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
2245 $timeFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
2246 $this->content .= LF . '<!-- Cached page generated ' . date($dateFormat . ' ' . $timeFormat, $row['tstamp']) . '. Expires ' . date($dateFormat . ' ' . $timeFormat, $row['expires']) . ' -->';
2247 }
2248 }
2249 $this->getTimeTracker()->pull();
2250
2251 return;
2252 }
2253 }
2254 // the user forced rebuilding the page cache or there was no pagesection information
2255 // get a lock for the page content so other processes will not interrupt the regeneration
2256 $this->acquireLock('pages', $lockHash);
2257 }
2258
2259 /**
2260 * Returning the cached version of page with hash = newHash
2261 *
2262 * @return array Cached row, if any. Otherwise void.
2263 */
2264 public function getFromCache_queryRow()
2265 {
2266 $this->getTimeTracker()->push('Cache Query');
2267 $row = $this->pageCache->get($this->newHash);
2268 $this->getTimeTracker()->pull();
2269 return $row;
2270 }
2271
2272 /**
2273 * Detecting if shift-reload has been clicked
2274 * Will not be called if re-generation of page happens by other reasons (for instance that the page is not in cache yet!)
2275 * Also, a backend user MUST be logged in for the shift-reload to be detected due to DoS-attack-security reasons.
2276 *
2277 * @return bool If shift-reload in client browser has been clicked, disable getting cached page (and regenerate it).
2278 */
2279 public function headerNoCache()
2280 {
2281 $disableAcquireCacheData = false;
2282 if ($this->beUserLogin) {
2283 if (strtolower($_SERVER['HTTP_CACHE_CONTROL']) === 'no-cache' || strtolower($_SERVER['HTTP_PRAGMA']) === 'no-cache') {
2284 $disableAcquireCacheData = true;
2285 }
2286 }
2287 // Call hook for possible by-pass of requiring of page cache (for recaching purpose)
2288 $_params = ['pObj' => &$this, 'disableAcquireCacheData' => &$disableAcquireCacheData];
2289 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['headerNoCache'] ?? [] as $_funcRef) {
2290 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2291 }
2292 return $disableAcquireCacheData;
2293 }
2294
2295 /**
2296 * Calculates the cache-hash
2297 * This hash is unique to the template, the variables ->id, ->type, ->gr_list (list of groups), ->MP (Mount Points) and cHash array
2298 * Used to get and later store the cached data.
2299 *
2300 * @return string MD5 hash of serialized hash base from createHashBase()
2301 * @see getFromCache(), getLockHash()
2302 */
2303 protected function getHash()
2304 {
2305 return md5($this->createHashBase(false));
2306 }
2307
2308 /**
2309 * Calculates the lock-hash
2310 * This hash is unique to the above hash, except that it doesn't contain the template information in $this->all.
2311 *
2312 * @return string MD5 hash
2313 * @see getFromCache(), getHash()
2314 */
2315 protected function getLockHash()
2316 {
2317 $lockHash = $this->createHashBase(true);
2318 return md5($lockHash);
2319 }
2320
2321 /**
2322 * Calculates the cache-hash (or the lock-hash)
2323 * This hash is unique to the template,
2324 * the variables ->id, ->type, ->gr_list (list of groups),
2325 * ->MP (Mount Points) and cHash array
2326 * Used to get and later store the cached data.
2327 *
2328 * @param bool $createLockHashBase Whether to create the lock hash, which doesn't contain the "this->all" (the template information)
2329 * @return string the serialized hash base
2330 */
2331 protected function createHashBase($createLockHashBase = false)
2332 {
2333 // Ensure the language base is used for the hash base calculation as well, otherwise TypoScript and page-related rendering
2334 // is not cached properly as we don't have any language-specific conditions anymore
2335 $siteBase = $this->getCurrentSiteLanguage() ? $this->getCurrentSiteLanguage()->getBase() : '';
2336 $hashParameters = [
2337 'id' => (int)$this->id,
2338 'type' => (int)$this->type,
2339 'gr_list' => (string)$this->gr_list,
2340 'MP' => (string)$this->MP,
2341 'siteBase' => $siteBase,
2342 'cHash' => $this->cHash_array,
2343 'domainStartPage' => $this->domainStartPage
2344 ];
2345 // Include the template information if we shouldn't create a lock hash
2346 if (!$createLockHashBase) {
2347 $hashParameters['all'] = $this->all;
2348 }
2349 // Call hook to influence the hash calculation
2350 $_params = [
2351 'hashParameters' => &$hashParameters,
2352 'createLockHashBase' => $createLockHashBase
2353 ];
2354 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['createHashBase'] ?? [] as $_funcRef) {
2355 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2356 }
2357 return serialize($hashParameters);
2358 }
2359
2360 /**
2361 * Checks if config-array exists already but if not, gets it
2362 *
2363 * @throws ServiceUnavailableException
2364 */
2365 public function getConfigArray()
2366 {
2367 // If config is not set by the cache (which would be a major mistake somewhere) OR if INTincScripts-include-scripts have been registered, then we must parse the template in order to get it
2368 if (empty($this->config) || is_array($this->config['INTincScript']) || $this->forceTemplateParsing) {
2369 $timeTracker = $this->getTimeTracker();
2370 $timeTracker->push('Parse template');
2371 // Force parsing, if set?:
2372 $this->tmpl->forceTemplateParsing = $this->forceTemplateParsing;
2373 // Start parsing the TS template. Might return cached version.
2374 $this->tmpl->start($this->rootLine);
2375 $timeTracker->pull();
2376 if ($this->tmpl->loaded) {
2377 $timeTracker->push('Setting the config-array');
2378 // toplevel - objArrayName
2379 $this->sPre = $this->tmpl->setup['types.'][$this->type];
2380 $this->pSetup = $this->tmpl->setup[$this->sPre . '.'];
2381 if (!is_array($this->pSetup)) {
2382 $message = 'The page is not configured! [type=' . $this->type . '][' . $this->sPre . '].';
2383 $this->logger->alert($message);
2384 try {
2385 $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message);
2386 $this->sendResponseAndExit($response);
2387 } catch (ServiceUnavailableException $e) {
2388 $explanation = 'This means that there is no TypoScript object of type PAGE with typeNum=' . $this->type . ' configured.';
2389 throw new ServiceUnavailableException($message . ' ' . $explanation, 1294587217);
2390 }
2391 } else {
2392 if (!isset($this->config['config'])) {
2393 $this->config['config'] = [];
2394 }
2395 // Filling the config-array, first with the main "config." part
2396 if (is_array($this->tmpl->setup['config.'])) {
2397 ArrayUtility::mergeRecursiveWithOverrule($this->tmpl->setup['config.'], $this->config['config']);
2398 $this->config['config'] = $this->tmpl->setup['config.'];
2399 }
2400 // override it with the page/type-specific "config."
2401 if (is_array($this->pSetup['config.'])) {
2402 ArrayUtility::mergeRecursiveWithOverrule($this->config['config'], $this->pSetup['config.']);
2403 }
2404 // @deprecated since TYPO3 v9, can be removed in TYPO3 v10
2405 if ($this->config['config']['typolinkCheckRootline']) {
2406 $this->logDeprecatedTyposcript('config.typolinkCheckRootline', 'The functionality is always enabled since TYPO3 v9 and can be removed from your TypoScript code');
2407 }
2408 // Set default values for removeDefaultJS and inlineStyle2TempFile so CSS and JS are externalized if compatversion is higher than 4.0
2409 if (!isset($this->config['config']['removeDefaultJS'])) {
2410 $this->config['config']['removeDefaultJS'] = 'external';
2411 }
2412 if (!isset($this->config['config']['inlineStyle2TempFile'])) {
2413 $this->config['config']['inlineStyle2TempFile'] = 1;
2414 }
2415
2416 if (!isset($this->config['config']['compressJs'])) {
2417 $this->config['config']['compressJs'] = 0;
2418 }
2419 // Processing for the config_array:
2420 $this->config['rootLine'] = $this->tmpl->rootLine;
2421 // Class for render Header and Footer parts
2422 if ($this->pSetup['pageHeaderFooterTemplateFile']) {
2423 $file = $this->tmpl->getFileName($this->pSetup['pageHeaderFooterTemplateFile']);
2424 if ($file) {
2425 $this->pageRenderer->setTemplateFile($file);
2426 }
2427 }
2428 }
2429 $timeTracker->pull();
2430 } else {
2431 $message = 'No TypoScript template found!';
2432 $this->logger->alert($message);
2433 try {
2434 $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message);
2435 $this->sendResponseAndExit($response);
2436 } catch (ServiceUnavailableException $e) {
2437 throw new ServiceUnavailableException($message, 1294587218);
2438 }
2439 }
2440 }
2441
2442 // No cache
2443 // Set $this->no_cache TRUE if the config.no_cache value is set!
2444 if ($this->config['config']['no_cache']) {
2445 $this->set_no_cache('config.no_cache is set');
2446 }
2447 // Merge GET with defaultGetVars
2448 if (!empty($this->config['config']['defaultGetVars.'])) {
2449 $modifiedGetVars = GeneralUtility::removeDotsFromTS($this->config['config']['defaultGetVars.']);
2450 ArrayUtility::mergeRecursiveWithOverrule($modifiedGetVars, GeneralUtility::_GET());
2451 GeneralUtility::_GETset($modifiedGetVars);
2452 }
2453
2454 // Auto-configure settings when a site is configured
2455 if ($this->getCurrentSiteLanguage()) {
2456 $this->config['config']['absRefPrefix'] = $this->config['config']['absRefPrefix'] ?? 'auto';
2457 }
2458
2459 $this->setUrlIdToken();
2460
2461 // Hook for postProcessing the configuration array
2462 $params = ['config' => &$this->config['config']];
2463 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['configArrayPostProc'] ?? [] as $funcRef) {
2464 GeneralUtility::callUserFunction($funcRef, $params, $this);
2465 }
2466 }
2467
2468 /********************************************
2469 *
2470 * Further initialization and data processing
2471 *
2472 *******************************************/
2473
2474 /**
2475 * Setting the language key that will be used by the current page.
2476 * In this function it should be checked, 1) that this language exists, 2) that a page_overlay_record exists, .. and if not the default language, 0 (zero), should be set.
2477 *
2478 * @access private
2479 */
2480 public function settingLanguage()
2481 {
2482 $_params = [];
2483 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_preProcess'] ?? [] as $_funcRef) {
2484 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2485 }
2486
2487 $siteLanguage = $this->getCurrentSiteLanguage();
2488
2489 // Initialize charset settings etc.
2490 if ($siteLanguage) {
2491 $languageKey = $siteLanguage->getTypo3Language();
2492 } else {
2493 $languageKey = $this->config['config']['language'] ?? 'default';
2494 }
2495 $this->lang = $languageKey;
2496 $this->setOutputLanguage($languageKey);
2497
2498 // Rendering charset of HTML page.
2499 if (isset($this->config['config']['metaCharset']) && $this->config['config']['metaCharset'] !== 'utf-8') {
2500 $this->metaCharset = $this->config['config']['metaCharset'];
2501 }
2502
2503 // Get values from site language
2504 if ($siteLanguage) {
2505 $this->sys_language_uid = ($this->sys_language_content = $siteLanguage->getLanguageId());
2506 $this->sys_language_mode = $siteLanguage->getFallbackType();
2507 if ($this->sys_language_mode === 'fallback') {
2508 $fallbackOrder = $siteLanguage->getFallbackLanguageIds();
2509 $fallbackOrder[] = 'pageNotFound';
2510 }
2511 } else {
2512 // Get values from TypoScript, if not set before
2513 if ($this->sys_language_uid === 0) {
2514 $this->sys_language_uid = ($this->sys_language_content = (int)$this->config['config']['sys_language_uid']);
2515 }
2516 list($this->sys_language_mode, $fallbackOrder) = GeneralUtility::trimExplode(';', $this->config['config']['sys_language_mode']);
2517 if (!empty($fallbackOrder)) {
2518 $fallBackOrder = GeneralUtility::trimExplode(',', $fallbackOrder);
2519 } else {
2520 $fallBackOrder = [0];
2521 }
2522 }
2523
2524 $this->sys_language_contentOL = $this->config['config']['sys_language_overlay'];
2525 // If sys_language_uid is set to another language than default:
2526 if ($this->sys_language_uid > 0) {
2527 // check whether a shortcut is overwritten by a translated page
2528 // we can only do this now, as this is the place where we get
2529 // to know about translations
2530 $this->checkTranslatedShortcut();
2531 // Request the overlay record for the sys_language_uid:
2532 $olRec = $this->sys_page->getPageOverlay($this->id, $this->sys_language_uid);
2533 if (empty($olRec)) {
2534 // If no OL record exists and a foreign language is asked for...
2535 if ($this->sys_language_uid) {
2536 // If requested translation is not available:
2537 if (GeneralUtility::hideIfNotTranslated($this->page['l18n_cfg'])) {
2538 $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], 'Page is not available in the requested language.');
2539 $this->sendResponseAndExit($response);
2540 } else {
2541 switch ((string)$this->sys_language_mode) {
2542 case 'strict':
2543 $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], 'Page is not available in the requested language (strict).');
2544 $this->sendResponseAndExit($response);
2545 break;
2546 case 'fallback':
2547 case 'content_fallback':
2548 // Setting content uid (but leaving the sys_language_uid) when a content_fallback
2549 // value was found.
2550 foreach ($fallBackOrder ?? [] as $orderValue) {
2551 if ($orderValue === '0' || $orderValue === 0 || $orderValue === '') {
2552 $this->sys_language_content = 0;
2553 break;
2554 }
2555 if (MathUtility::canBeInterpretedAsInteger($orderValue) && !empty($this->sys_page->getPageOverlay($this->id, (int)$orderValue))) {
2556 $this->sys_language_content = (int)$orderValue;
2557 break;
2558 }
2559 if ($orderValue === 'pageNotFound') {
2560 // The existing fallbacks have not been found, but instead of continuing
2561 // page rendering with default language, a "page not found" message should be shown
2562 // instead.
2563 $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], 'Page is not available in the requested language (fallbacks did not apply).');
2564 $this->sendResponseAndExit($response);
2565 }
2566 }
2567 break;
2568 case 'ignore':
2569 $this->sys_language_content = $this->sys_language_uid;
2570 break;
2571 default:
2572 // Default is that everything defaults to the default language...
2573 $this->sys_language_uid = ($this->sys_language_content = 0);
2574 }
2575 }
2576 }
2577 } else {
2578 // Setting sys_language if an overlay record was found (which it is only if a language is used)
2579 $this->page = $this->sys_page->getPageOverlay($this->page, $this->sys_language_uid);
2580 }
2581 }
2582 // Setting sys_language_uid inside sys-page:
2583 $this->sys_page->sys_language_uid = $this->sys_language_uid;
2584 // If default translation is not available:
2585 if ((!$this->sys_language_uid || !$this->sys_language_content) && GeneralUtility::hideIfDefaultLanguage($this->page['l18n_cfg'])) {
2586 $message = 'Page is not available in default language.';
2587 $this->logger->error($message);
2588 $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], $message);
2589 $this->sendResponseAndExit($response);
2590 }
2591 $this->updateRootLinesWithTranslations();
2592
2593 // Finding the ISO code for the currently selected language
2594 // fetched by the sys_language record when not fetching content from the default language
2595 if ($siteLanguage = $this->getCurrentSiteLanguage()) {
2596 $this->sys_language_isocode = $siteLanguage->getTwoLetterIsoCode();
2597 } elseif ($this->sys_language_content > 0) {
2598 // using sys_language_content because the ISO code only (currently) affect content selection from FlexForms - which should follow "sys_language_content"
2599 // Set the fourth parameter to TRUE in the next two getRawRecord() calls to
2600 // avoid versioning overlay to be applied as it generates an SQL error
2601 $sys_language_row = $this->sys_page->getRawRecord('sys_language', $this->sys_language_content, 'language_isocode,static_lang_isocode');
2602 if (is_array($sys_language_row) && !empty($sys_language_row['language_isocode'])) {
2603 $this->sys_language_isocode = $sys_language_row['language_isocode'];
2604 }
2605 // the DB value is overridden by TypoScript
2606 if (!empty($this->config['config']['sys_language_isocode'])) {
2607 $this->sys_language_isocode = $this->config['config']['sys_language_isocode'];
2608 }
2609 } else {
2610 // fallback to the TypoScript option when rendering with sys_language_uid=0
2611 // also: use "en" by default
2612 if (!empty($this->config['config']['sys_language_isocode_default'])) {
2613 $this->sys_language_isocode = $this->config['config']['sys_language_isocode_default'];
2614 } else {
2615 $this->sys_language_isocode = $languageKey !== 'default' ? $languageKey : 'en';
2616 }
2617 }
2618
2619 $_params = [];
2620 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_postProcess'] ?? [] as $_funcRef) {
2621 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2622 }
2623 }
2624
2625 /**
2626 * Updating content of the two rootLines IF the language key is set!
2627 */
2628 protected function updateRootLinesWithTranslations()
2629 {
2630 if ($this->sys_language_uid) {
2631 $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
2632 $this->tmpl->updateRootlineData($this->rootLine);
2633 }
2634 }
2635
2636 /**
2637 * Setting locale for frontend rendering
2638 */
2639 public function settingLocale()
2640 {
2641 // Setting locale
2642 $locale = $this->config['config']['locale_all'];
2643 $siteLanguage = $this->getCurrentSiteLanguage();
2644 if ($siteLanguage) {
2645 $locale = $siteLanguage->getLocale();
2646 }
2647 if ($locale) {
2648 $availableLocales = GeneralUtility::trimExplode(',', $locale, true);
2649 // If LC_NUMERIC is set e.g. to 'de_DE' PHP parses float values locale-aware resulting in strings with comma
2650 // as decimal point which causes problems with value conversions - so we set all locale types except LC_NUMERIC
2651 // @see https://bugs.php.net/bug.php?id=53711
2652 $locale = setlocale(LC_COLLATE, ...$availableLocales);
2653 if ($locale) {
2654 // As str_* methods are locale aware and turkish has no upper case I
2655 // Class autoloading and other checks depending on case changing break with turkish locale LC_CTYPE
2656 // @see http://bugs.php.net/bug.php?id=35050
2657 if (substr($locale, 0, 2) !== 'tr') {
2658 setlocale(LC_CTYPE, ...$availableLocales);
2659 }
2660 setlocale(LC_MONETARY, ...$availableLocales);
2661 setlocale(LC_TIME, ...$availableLocales);
2662 } else {
2663 $this->getTimeTracker()->setTSlogMessage('Locale "' . htmlspecialchars($locale) . '" not found.', 3);
2664 }
2665 }
2666 }
2667
2668 /**
2669 * Checks whether a translated shortcut page has a different shortcut
2670 * target than the original language page.
2671 * If that is the case, things get corrected to follow that alternative
2672 * shortcut
2673 */
2674 protected function checkTranslatedShortcut()
2675 {
2676 if (!is_null($this->originalShortcutPage)) {
2677 $originalShortcutPageOverlay = $this->sys_page->getPageOverlay($this->originalShortcutPage['uid'], $this->sys_language_uid);
2678 if (!empty($originalShortcutPageOverlay['shortcut']) && $originalShortcutPageOverlay['shortcut'] != $this->id) {
2679 // the translation of the original shortcut page has a different shortcut target!
2680 // set the correct page and id
2681 $shortcut = $this->sys_page->getPageShortcut($originalShortcutPageOverlay['shortcut'], $originalShortcutPageOverlay['shortcut_mode'], $originalShortcutPageOverlay['uid']);
2682 $this->id = ($this->contentPid = $shortcut['uid']);
2683 $this->page = $this->sys_page->getPage($this->id);
2684 // Fix various effects on things like menus f.e.
2685 $this->fetch_the_id();
2686 $this->tmpl->rootLine = array_reverse($this->rootLine);
2687 }
2688 }
2689 }
2690
2691 /**
2692 * Handle data submission
2693 * This is done at this point, because we need the config values
2694 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.
2695 */
2696 public function handleDataSubmission()
2697 {
2698 trigger_error('The method "' . __METHOD__ . '" is deprecated since TYPO3 v9, and will be removed in TYPO3 v10. Implement the hooks directly, as they are still executed within TYPO3 via a PSR-15 middleware.', E_USER_DEPRECATED);
2699 // Hook for processing data submission to extensions
2700 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkDataSubmission'] ?? [] as $className) {
2701 $_procObj = GeneralUtility::makeInstance($className);
2702 $_procObj->checkDataSubmission($this);
2703 }
2704 }
2705
2706 /**
2707 * Loops over all configured URL handlers and registers all active handlers in the redirect URL handler array.
2708 *
2709 * @param bool $calledFromCore if set to true, no deprecation warning will be triggered
2710 * @see $activeRedirectUrlHandlers
2711 * @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0. Do not call this method anymore, and also ensure that all urlHandlers are migrated to PSR-15 middlewares.
2712 */
2713 public function initializeRedirectUrlHandlers($calledFromCore = false)
2714 {
2715 $urlHandlers = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlHandlers'] ?? false;
2716 if (!$urlHandlers) {
2717 if (!$calledFromCore) {
2718 trigger_error('The method $TSFE->initializeRedirectUrlHandlers() will be removed in TYPO3 v10.0. Do not call this method anymore and implement UrlHandlers by PSR-15 middlewares instead.', E_USER_DEPRECATED);
2719 }
2720 return;
2721 }
2722 trigger_error('The system has registered RedirectUrlHandlers via $TYPO3_CONF_VARS[SC_OPTIONS][urlProcessing][urlHandlers]. This functionality will be removed in TYPO3 v10.0. Ensure that extensions using this functionality switch to PSR-15 middelwares instead.', E_USER_DEPRECATED);
2723
2724 foreach ($urlHandlers as $identifier => $configuration) {
2725 if (empty($configuration) || !is_array($configuration)) {
2726 throw new \RuntimeException('Missing configuration for URL handler "' . $identifier . '".', 1442052263);
2727 }
2728 if (!is_string($configuration['handler']) || empty($configuration['handler']) || !class_exists($configuration['handler']) || !is_subclass_of($configuration['handler'], UrlHandlerInterface::class)) {
2729 throw new \RuntimeException('The URL handler "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlHandlerInterface::class . '".', 1442052249);
2730 }
2731 }
2732
2733 $orderedHandlers = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlHandlers);
2734
2735 foreach ($orderedHandlers as $configuration) {
2736 /** @var UrlHandlerInterface $urlHandler */
2737 $urlHandler = GeneralUtility::makeInstance($configuration['handler']);
2738 if ($urlHandler->canHandleCurrentUrl()) {
2739 $this->activeUrlHandlers[] = $urlHandler;
2740 }
2741 }
2742 }
2743
2744 /**
2745 * Loops over all registered URL handlers and lets them process the current URL.
2746 *
2747 * If no handler has stopped the current process (e.g. by redirecting) and a
2748 * the redirectUrl property is not empty, the user will be redirected to this URL.
2749 *
2750 * @internal Should be called by the FrontendRequestHandler only.
2751 * @return ResponseInterface|null
2752 * @param bool $calledFromCore if set to true, no deprecation warning will be triggered
2753 * @deprecated since TYPO3 v9.3, will be removed in TYPO3 v10.0. Do not call this method anymore, and also ensure that all urlHandlers are migrated to PSR-15 middlewares.
2754 */
2755 public function redirectToExternalUrl($calledFromCore = false)
2756 {
2757 if (!$calledFromCore) {
2758 trigger_error('The method $TSFE->redirectToExternalUrl() will be removed in TYPO3 v10.0. Do not call this method anymore and implement UrlHandlers by PSR-15 middlewares instead.', E_USER_DEPRECATED);
2759 }
2760 foreach ($this->activeUrlHandlers as $redirectHandler) {
2761 $response = $redirectHandler->handle();
2762 if ($response instanceof ResponseInterface) {
2763 return $response;
2764 }
2765 }
2766
2767 if (!empty($this->activeUrlHandlers)) {
2768 throw new \RuntimeException('A URL handler is active but did not process the URL.', 1442305505);
2769 }
2770
2771 return null;
2772 }
2773
2774 /**
2775 * Sets the URL_ID_TOKEN in the internal var, $this->getMethodUrlIdToken
2776 * This feature allows sessions to use a GET-parameter instead of a cookie.
2777 */
2778 protected function setUrlIdToken()
2779 {
2780 if ($this->config['config']['ftu']) {
2781 $this->getMethodUrlIdToken = $GLOBALS['TYPO3_CONF_VARS']['FE']['get_url_id_token'];
2782 } else {
2783 $this->getMethodUrlIdToken = '';
2784 }
2785 }
2786
2787 /**
2788 * Calculates and sets the internal linkVars based upon the current
2789 * $_GET parameters and the setting "config.linkVars".
2790 */
2791 public function calculateLinkVars()
2792 {
2793 $this->linkVars = '';
2794 if (empty($this->config['config']['linkVars'])) {
2795 return;
2796 }
2797
2798 $linkVars = $this->splitLinkVarsString((string)$this->config['config']['linkVars']);
2799
2800 if (empty($linkVars)) {
2801 return;
2802 }
2803 $getData = GeneralUtility::_GET();
2804 foreach ($linkVars as $linkVar) {
2805 $test = $value = '';
2806 if (preg_match('/^(.*)\\((.+)\\)$/', $linkVar, $match)) {
2807 $linkVar = trim($match[1]);
2808 $test = trim($match[2]);
2809 }
2810
2811 $keys = explode('|', $linkVar);
2812 $numberOfLevels = count($keys);
2813 $rootKey = trim($keys[0]);
2814 if (!isset($getData[$rootKey])) {
2815 continue;
2816 }
2817 $value = $getData[$rootKey];
2818 for ($i = 1; $i < $numberOfLevels; $i++) {
2819 $currentKey = trim($keys[$i]);
2820 if (isset($value[$currentKey])) {
2821 $value = $value[$currentKey];
2822 } else {
2823 $value = false;
2824 break;
2825 }
2826 }
2827 if ($value !== false) {
2828 $parameterName = $keys[0];
2829 for ($i = 1; $i < $numberOfLevels; $i++) {
2830 $parameterName .= '[' . $keys[$i] . ']';
2831 }
2832 if (!is_array($value)) {
2833 $temp = rawurlencode($value);
2834 if ($test !== '' && !$this->isAllowedLinkVarValue($temp, $test)) {
2835 // Error: This value was not allowed for this key
2836 continue;
2837 }
2838 $value = '&' . $parameterName . '=' . $temp;
2839 } else {
2840 if ($test !== '' && $test !== 'array') {
2841 // Error: This key must not be an array!
2842 continue;
2843 }
2844 $value = GeneralUtility::implodeArrayForUrl($parameterName, $value);
2845 }
2846 $this->linkVars .= $value;
2847 }
2848 }
2849 }
2850
2851 /**
2852 * Split the link vars string by "," but not if the "," is inside of braces
2853 *
2854 * @param $string
2855 *
2856 * @return array
2857 */
2858 protected function splitLinkVarsString(string $string): array
2859 {
2860 $tempCommaReplacementString = '###KASPER###';
2861
2862 // replace every "," wrapped in "()" by a "unique" string
2863 $string = preg_replace_callback('/\((?>[^()]|(?R))*\)/', function ($result) use ($tempCommaReplacementString) {
2864 return str_replace(',', $tempCommaReplacementString, $result[0]);
2865 }, $string);
2866
2867 $string = GeneralUtility::trimExplode(',', $string);
2868
2869 // replace all "unique" strings back to ","
2870 return str_replace($tempCommaReplacementString, ',', $string);
2871 }
2872
2873 /**
2874 * Checks if the value defined in "config.linkVars" contains an allowed value.
2875 * Otherwise, return FALSE which means the value will not be added to any links.
2876 *
2877 * @param string $haystack The string in which to find $needle
2878 * @param string $needle The string to find in $haystack
2879 * @return bool Returns TRUE if $needle matches or is found in $haystack
2880 */
2881 protected function isAllowedLinkVarValue(string $haystack, string $needle): bool
2882 {
2883 $isAllowed = false;
2884 // Integer
2885 if ($needle === 'int' || $needle === 'integer') {
2886 if (MathUtility::canBeInterpretedAsInteger($haystack)) {
2887 $isAllowed = true;
2888 }
2889 } elseif (preg_match('/^\\/.+\\/[imsxeADSUXu]*$/', $needle)) {
2890 // Regular expression, only "//" is allowed as delimiter
2891 if (@preg_match($needle, $haystack)) {
2892 $isAllowed = true;
2893 }
2894 } elseif (strstr($needle, '-')) {
2895 // Range
2896 if (MathUtility::canBeInterpretedAsInteger($haystack)) {
2897 $range = explode('-', $needle);
2898 if ($range[0] <= $haystack && $range[1] >= $haystack) {
2899 $isAllowed = true;
2900 }
2901 }
2902 } elseif (strstr($needle, '|')) {
2903 // List
2904 // Trim the input
2905 $haystack = str_replace(' ', '', $haystack);
2906 if (strstr('|' . $needle . '|', '|' . $haystack . '|')) {
2907 $isAllowed = true;
2908 }
2909 } elseif ((string)$needle === (string)$haystack) {
2910 // String comparison
2911 $isAllowed = true;
2912 }
2913 return $isAllowed;
2914 }
2915
2916 /**
2917 * Returns URI of target page, if the current page is an overlaid mountpoint.
2918 *
2919 * If the current page is of type mountpoint and should be overlaid with the contents of the mountpoint page
2920 * and is accessed directly, the user will be redirected to the mountpoint context.
2921 * @internal
2922 */
2923 public function getRedirectUriForMountPoint(): ?string
2924 {
2925 if (!empty($this->originalMountPointPage) && (int)$this->originalMountPointPage['doktype'] === PageRepository::DOKTYPE_MOUNTPOINT) {
2926 return $this->getUriToCurrentPageForRedirect();
2927 }
2928
2929 return null;
2930 }
2931
2932 /**
2933 * Redirect to target page if the current page is an overlaid mountpoint.
2934 *
2935 * If the current page is of type mountpoint and should be overlaid with the contents of the mountpoint page
2936 * and is accessed directly, the user will be redirected to the mountpoint context.
2937 * @deprecated in TYPO3 9, will be removed in TYPO3 10
2938 */
2939 public function checkPageForMountpointRedirect()
2940 {
2941 trigger_error('Method ' . __FUNCTION__ . 'is deprecated.', \E_USER_DEPRECATED);
2942 if (!empty($this->originalMountPointPage) && $this->originalMountPointPage['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT) {
2943 $this->redirectToCurrentPage();
2944 }
2945 }
2946
2947 /**
2948 * Returns URI of target page, if the current page is a Shortcut.
2949 *
2950 * If the current page is of type shortcut and accessed directly via its URL,
2951 * the user will be redirected to shortcut target.
2952 * @internal
2953 */
2954 public function getRedirectUriForShortcut(): ?string
2955 {
2956 if (!empty($this->originalShortcutPage) && $this->originalShortcutPage['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
2957 return $this->getUriToCurrentPageForRedirect();
2958 }
2959
2960 return null;
2961 }
2962
2963 /**
2964 * Redirect to target page, if the current page is a Shortcut.
2965 *
2966 * If the current page is of type shortcut and accessed directly via its URL, this function redirects to the
2967 * Shortcut target using a Location header.
2968 * @deprecated in TYPO3 9, will be removed in TYPO3 10
2969 */
2970 public function checkPageForShortcutRedirect()
2971 {
2972 trigger_error('Method ' . __FUNCTION__ . 'is deprecated.', \E_USER_DEPRECATED);