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