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