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