TYPO3  7.6
TypoScriptFrontendController.php
Go to the documentation of this file.
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 
25 use TYPO3\CMS\Core\Locking\Exception\LockAcquireWouldBlockException;
48 
65 {
70  public $id = '';
71 
76  public $type = '';
77 
82  public $cHash = '';
83 
89  public $no_cache = false;
90 
95  public $rootLine = '';
96 
101  public $page = '';
102 
108  public $contentPid = 0;
109 
117  protected $originalMountPointPage = null;
118 
127  protected $originalShortcutPage = null;
128 
134  public $sys_page = '';
135 
140  public $jumpurl = '';
141 
150  protected $activeUrlHandlers = [];
151 
156  public $pageNotFound = 0;
157 
162  public $domainStartPage = 0;
163 
168  public $pageAccessFailureHistory = array();
169 
173  public $MP = '';
174 
178  public $RDCT = '';
179 
186  public $page_cache_reg1 = 0;
187 
194  public $siteScript = '';
195 
201  public $fe_user = '';
202 
209  public $loginUser = false;
210 
217  public $gr_list = '';
218 
223  public $beUserLogin = false;
224 
229  public $workspacePreview = 0;
230 
235  public $loginAllowedInBranch = true;
236 
242 
248 
256  public $fePreview = 0;
257 
263  public $showHiddenPage = false;
264 
271  public $showHiddenRecords = false;
272 
277  public $simUserGroup = 0;
278 
284  public $TYPO3_CONF_VARS = array();
285 
291  public $config = '';
292 
298  public $tmpl = null;
299 
305  public $cacheTimeOutDefault = false;
306 
312  public $cacheContentFlag = false;
313 
318  public $cacheExpires = 0;
319 
324  public $isClientCachable = false;
325 
332  public $all = array();
333 
338  public $sPre = '';
339 
345  public $pSetup = '';
346 
352  public $newHash = '';
353 
361  public $getMethodUrlIdToken = '';
362 
370  public $no_cacheBeforePageGen = false;
371 
377  public $tempContent = false;
378 
383  public $forceTemplateParsing = false;
384 
389  public $cHash_array = array();
390 
395  public $pagesTSconfig = '';
396 
414  public $additionalHeaderData = array();
415 
420  public $additionalFooterData = array();
421 
428  public $additionalJavaScript = array();
429 
435  public $additionalCSS = array();
436 
445  public $JSeventFuncCalls = array(
446  'onmousemove' => array(),
447  'onmouseup' => array(),
448  'onkeydown' => array(),
449  'onkeyup' => array(),
450  'onkeypress' => array(),
451  'onload' => array(),
452  'onunload' => array()
453  );
454 
458  public $JSCode;
459 
463  public $inlineJS;
464 
469  public $divSection = '';
470 
476  public $defaultBodyTag = '<body>';
477 
482  public $debug = '';
483 
488  public $intTarget = '';
489 
494  public $extTarget = '';
495 
500  public $fileTarget = '';
501 
507  public $MP_defaults = array();
508 
514 
519  public $absRefPrefix = '';
520 
526 
531  public $lockFilePath = '';
532 
537  public $ATagParams = '';
538 
545  public $sWordRegEx = '';
546 
552  public $sWordList = '';
553 
560  public $linkVars = '';
561 
568  public $excludeCHashVars = '';
569 
575  public $displayEditIcons = '';
576 
584 
592  public $sys_language_uid = 0;
593 
598  public $sys_language_mode = '';
599 
606 
614 
621 
627  public $applicationData = array();
628 
632  public $register = array();
633 
639  public $registerStack = array();
640 
646  public $cObjectDepthCounter = 50;
647 
653  public $recordRegister = array();
654 
662  public $currentRecord = '';
663 
669  public $accessKey = array();
670 
676  public $imagesOnPage = array();
677 
683  public $lastImageInfo = array();
684 
690  public $uniqueCounter = 0;
691 
695  public $uniqueString = '';
696 
702  public $indexedDocTitle = '';
703 
709  public $altPageTitle = '';
710 
715  public $baseUrl = '';
716 
723  public $anchorPrefix = '';
724 
729  private $usedUniqueIds = array();
730 
736  public $cObj = '';
737 
742  public $content = '';
743 
748  public $clientInfo = '';
749 
753  public $scriptParseTime = 0;
754 
761  public $csConvObj;
762 
767  public $defaultCharSet = 'utf-8';
768 
773  public $renderCharset = 'utf-8';
774 
781  public $metaCharset = 'utf-8';
782 
787  public $localeCharset = '';
788 
793  public $lang = '';
794 
798  public $LL_labels_cache = array();
799 
803  public $LL_files_cache = array();
804 
812  protected $languageDependencies = array();
813 
817  protected $locks = [];
818 
822  protected $pageRenderer = null;
823 
830  protected $pageCache;
831 
835  protected $pageCacheTags = array();
836 
842  protected $cacheHash;
843 
849  protected $domainDataCache = array();
850 
858  protected $contentType = 'text/html';
859 
867  public $xhtmlDoctype = '';
868 
873 
879  protected $requestedId;
880 
885 
902  public function __construct($TYPO3_CONF_VARS, $id, $type, $no_cache = '', $cHash = '', $jumpurl = '', $MP = '', $RDCT = '')
903  {
904  // Setting some variables:
905  $this->TYPO3_CONF_VARS = $TYPO3_CONF_VARS;
906  $this->id = $id;
907  $this->type = $type;
908  if ($no_cache) {
909  if ($this->TYPO3_CONF_VARS['FE']['disableNoCacheParameter']) {
910  $warning = '&no_cache=1 has been ignored because $TYPO3_CONF_VARS[\'FE\'][\'disableNoCacheParameter\'] is set!';
911  $this->getTimeTracker()->setTSlogMessage($warning, 2);
912  } else {
913  $warning = '&no_cache=1 has been supplied, so caching is disabled! URL: "' . GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL') . '"';
914  $this->disableCache();
915  }
916  GeneralUtility::sysLog($warning, 'cms', GeneralUtility::SYSLOG_SEVERITY_WARNING);
917  }
918  $this->cHash = $cHash;
919  $this->jumpurl = $jumpurl;
920  $this->MP = $this->TYPO3_CONF_VARS['FE']['enable_mount_pids'] ? (string)$MP : '';
921  $this->RDCT = $RDCT;
922  $this->clientInfo = GeneralUtility::clientInfo();
923  $this->uniqueString = md5(microtime());
924  $this->csConvObj = GeneralUtility::makeInstance(CharsetConverter::class);
925  $this->initPageRenderer();
926  // Call post processing function for constructor:
927  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-PostProc'])) {
928  $_params = array('pObj' => &$this);
929  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-PostProc'] as $_funcRef) {
930  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
931  }
932  }
933  $this->cacheHash = GeneralUtility::makeInstance(CacheHashCalculator::class);
934  $this->initCaches();
935  }
936 
940  protected function initPageRenderer()
941  {
942  if ($this->pageRenderer !== null) {
943  return;
944  }
945  $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
946  $this->pageRenderer->setTemplateFile('EXT:frontend/Resources/Private/Templates/MainPage.html');
947  $this->pageRenderer->setBackPath(TYPO3_mainDir);
948  }
949 
954  public function setContentType($contentType)
955  {
956  $this->contentType = $contentType;
957  }
958 
967  public function connectToDB()
968  {
969  try {
970  $this->getDatabaseConnection()->connectDB();
971  } catch (\RuntimeException $exception) {
972  switch ($exception->getCode()) {
973  case 1270853883:
974  // Cannot connect to current database
975  $message = 'Cannot connect to the configured database "' . TYPO3_db . '"';
976  if ($this->checkPageUnavailableHandler()) {
977  $this->pageUnavailableAndExit($message);
978  } else {
979  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
980  throw new ServiceUnavailableException($message, 1301648782);
981  }
982  break;
983  case 1270853884:
984  // Username / password not accepted
985  $message = 'The current username, password or host was not accepted when' . ' the connection to the database was attempted to be established!';
986  if ($this->checkPageUnavailableHandler()) {
987  $this->pageUnavailableAndExit($message);
988  } else {
989  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
990  throw new ServiceUnavailableException('Database Error: ' . $message, 1301648945);
991  }
992  break;
993  default:
994  throw $exception;
995  }
996  }
997  // Call post processing function for DB connection:
998  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['connectToDB'])) {
999  $_params = array('pObj' => &$this);
1000  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['connectToDB'] as $_funcRef) {
1001  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1002  }
1003  }
1004  }
1005 
1014  public function sendRedirect()
1015  {
1016  if ($this->RDCT) {
1017  $db = $this->getDatabaseConnection();
1018  $row = $db->exec_SELECTgetSingleRow('params', 'cache_md5params', 'md5hash=' . $db->fullQuoteStr($this->RDCT, 'cache_md5params'));
1019  if ($row) {
1020  $this->updateMD5paramsRecord($this->RDCT);
1021  header('Location: ' . $row['params']);
1022  die;
1023  }
1024  }
1025  }
1026 
1033  public function getPageRenderer()
1034  {
1036  $this->initPageRenderer();
1037 
1038  return $this->pageRenderer;
1039  }
1040 
1041  /********************************************
1042  *
1043  * Initializing, resolving page id
1044  *
1045  ********************************************/
1051  protected function initCaches()
1052  {
1053  $this->pageCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_pages');
1054  }
1055 
1061  public function initFEuser()
1062  {
1063  $this->fe_user = GeneralUtility::makeInstance(FrontendUserAuthentication::class);
1064  $this->fe_user->lockIP = $this->TYPO3_CONF_VARS['FE']['lockIP'];
1065  $this->fe_user->checkPid = $this->TYPO3_CONF_VARS['FE']['checkFeUserPid'];
1066  $this->fe_user->lifetime = (int)$this->TYPO3_CONF_VARS['FE']['lifetime'];
1067  // List of pid's acceptable
1068  $pid = GeneralUtility::_GP('pid');
1069  $this->fe_user->checkPid_value = $pid ? $this->getDatabaseConnection()->cleanIntList($pid) : 0;
1070  // Check if a session is transferred:
1071  if (GeneralUtility::_GP('FE_SESSION_KEY')) {
1072  $fe_sParts = explode('-', GeneralUtility::_GP('FE_SESSION_KEY'));
1073  // If the session key hash check is OK:
1074  if (md5(($fe_sParts[0] . '/' . $this->TYPO3_CONF_VARS['SYS']['encryptionKey'])) === (string)$fe_sParts[1]) {
1076  $_COOKIE[$cookieName] = $fe_sParts[0];
1077  if (isset($_SERVER['HTTP_COOKIE'])) {
1078  // See http://forge.typo3.org/issues/27740
1079  $_SERVER['HTTP_COOKIE'] .= ';' . $cookieName . '=' . $fe_sParts[0];
1080  }
1081  $this->fe_user->forceSetCookie = 1;
1082  $this->fe_user->dontSetCookie = false;
1083  unset($cookieName);
1084  }
1085  }
1086  $this->fe_user->start();
1087  $this->fe_user->unpack_uc('');
1088  // Gets session data
1089  $this->fe_user->fetchSessionData();
1090  $recs = GeneralUtility::_GP('recs');
1091  // If any record registration is submitted, register the record.
1092  if (is_array($recs)) {
1093  $this->fe_user->record_registration($recs, $this->TYPO3_CONF_VARS['FE']['maxSessionDataSize']);
1094  }
1095  // Call hook for possible manipulation of frontend user object
1096  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['initFEuser'])) {
1097  $_params = array('pObj' => &$this);
1098  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['initFEuser'] as $_funcRef) {
1099  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1100  }
1101  }
1102  // For every 60 seconds the is_online timestamp is updated.
1103  if (is_array($this->fe_user->user) && $this->fe_user->user['uid'] && $this->fe_user->user['is_online'] < $GLOBALS['EXEC_TIME'] - 60) {
1104  $this->getDatabaseConnection()->exec_UPDATEquery('fe_users', 'uid=' . (int)$this->fe_user->user['uid'], array('is_online' => $GLOBALS['EXEC_TIME']));
1105  }
1106  }
1107 
1114  public function initUserGroups()
1115  {
1116  // This affects the hidden-flag selecting the fe_groups for the user!
1117  $this->fe_user->showHiddenRecords = $this->showHiddenRecords;
1118  // no matter if we have an active user we try to fetch matching groups which can be set without an user (simulation for instance!)
1119  $this->fe_user->fetchGroupData();
1120  if (is_array($this->fe_user->user) && !empty($this->fe_user->groupData['uid'])) {
1121  // global flag!
1122  $this->loginUser = true;
1123  // 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!
1124  $this->gr_list = '0,-2';
1125  $gr_array = $this->fe_user->groupData['uid'];
1126  } else {
1127  $this->loginUser = false;
1128  // 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!
1129  $this->gr_list = '0,-1';
1130  if ($this->loginAllowedInBranch) {
1131  // 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.
1132  $gr_array = $this->fe_user->groupData['uid'];
1133  } else {
1134  // Set to blank since we will NOT risk any groups being set when no logins are allowed!
1135  $gr_array = array();
1136  }
1137  }
1138  // Clean up.
1139  // Make unique...
1140  $gr_array = array_unique($gr_array);
1141  // sort
1142  sort($gr_array);
1143  if (!empty($gr_array) && !$this->loginAllowedInBranch_mode) {
1144  $this->gr_list .= ',' . implode(',', $gr_array);
1145  }
1146  if ($this->fe_user->writeDevLog) {
1147  GeneralUtility::devLog('Valid usergroups for TSFE: ' . $this->gr_list, __CLASS__);
1148  }
1149  }
1150 
1156  public function isUserOrGroupSet()
1157  {
1158  return is_array($this->fe_user->user) || $this->gr_list !== '0,-1';
1159  }
1160 
1170  public function checkAlternativeIdMethods()
1171  {
1172  $this->siteScript = GeneralUtility::getIndpEnv('TYPO3_SITE_SCRIPT');
1173  // Call post processing function for custom URL methods.
1174  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'])) {
1175  $_params = array('pObj' => &$this);
1176  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'] as $_funcRef) {
1177  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1178  }
1179  }
1180  }
1181 
1189  public function clear_preview()
1190  {
1191  $this->showHiddenPage = false;
1192  $this->showHiddenRecords = false;
1193  $GLOBALS['SIM_EXEC_TIME'] = $GLOBALS['EXEC_TIME'];
1194  $GLOBALS['SIM_ACCESS_TIME'] = $GLOBALS['ACCESS_TIME'];
1195  $this->fePreview = 0;
1196  }
1197 
1203  public function isBackendUserLoggedIn()
1204  {
1205  return (bool)$this->beUserLogin;
1206  }
1207 
1213  public function initializeBackendUser()
1214  {
1215  // PRE BE_USER HOOK
1216  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/index_ts.php']['preBeUser'])) {
1217  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/index_ts.php']['preBeUser'] as $_funcRef) {
1218  $_params = array();
1219  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1220  }
1221  }
1223  $BE_USER = null;
1224  // If the backend cookie is set,
1225  // we proceed and check if a backend user is logged in.
1226  if ($_COOKIE[BackendUserAuthentication::getCookieName()]) {
1227  $GLOBALS['TYPO3_MISC']['microtime_BE_USER_start'] = microtime(true);
1228  $this->getTimeTracker()->push('Back End user initialized', '');
1229  // @todo validate the comment below: is this necessary? if so,
1230  // formfield_status should be set to "" in \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
1231  // which is a subclass of \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1232  // ----
1233  // the value this->formfield_status is set to empty in order to
1234  // disable login-attempts to the backend account through this script
1235  // New backend user object
1236  $BE_USER = GeneralUtility::makeInstance(FrontendBackendUserAuthentication::class);
1237  $BE_USER->lockIP = $this->TYPO3_CONF_VARS['BE']['lockIP'];
1238  // Object is initialized
1239  $BE_USER->start();
1240  $BE_USER->unpack_uc('');
1241  if (!empty($BE_USER->user['uid'])) {
1242  $BE_USER->fetchGroupData();
1243  $this->beUserLogin = true;
1244  }
1245  // Unset the user initialization.
1246  if (!$BE_USER->checkLockToIP() || !$BE_USER->checkBackendAccessSettingsFromInitPhp() || empty($BE_USER->user['uid'])) {
1247  $BE_USER = null;
1248  $this->beUserLogin = false;
1249  }
1250  $this->getTimeTracker()->pull();
1251  $GLOBALS['TYPO3_MISC']['microtime_BE_USER_end'] = microtime(true);
1252  }
1253  // POST BE_USER HOOK
1254  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/index_ts.php']['postBeUser'])) {
1255  $_params = array(
1256  'BE_USER' => &$BE_USER
1257  );
1258  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/index_ts.php']['postBeUser'] as $_funcRef) {
1259  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1260  }
1261  }
1262  return $BE_USER;
1263  }
1264 
1273  public function determineId()
1274  {
1275  // Call pre processing function for id determination
1276  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PreProcessing'])) {
1277  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PreProcessing'] as $functionReference) {
1278  $parameters = array('parentObject' => $this);
1279  GeneralUtility::callUserFunction($functionReference, $parameters, $this);
1280  }
1281  }
1282  // Getting ARG-v values if some
1283  $this->setIDfromArgV();
1284  // If there is a Backend login we are going to check for any preview settings:
1285  $this->getTimeTracker()->push('beUserLogin', '');
1286  $originalFrontendUser = null;
1287  $backendUser = $this->getBackendUser();
1288  if ($this->beUserLogin || $this->doWorkspacePreview()) {
1289  // Backend user preview features:
1290  if ($this->beUserLogin && $backendUser->adminPanel instanceof AdminPanelView) {
1291  $this->fePreview = (bool)$backendUser->adminPanel->extGetFeAdminValue('preview');
1292  // If admin panel preview is enabled...
1293  if ($this->fePreview) {
1294  if ($this->fe_user->user) {
1295  $originalFrontendUser = $this->fe_user->user;
1296  }
1297  $this->showHiddenPage = (bool)$backendUser->adminPanel->extGetFeAdminValue('preview', 'showHiddenPages');
1298  $this->showHiddenRecords = (bool)$backendUser->adminPanel->extGetFeAdminValue('preview', 'showHiddenRecords');
1299  // Simulate date
1300  $simTime = $backendUser->adminPanel->extGetFeAdminValue('preview', 'simulateDate');
1301  if ($simTime) {
1302  $GLOBALS['SIM_EXEC_TIME'] = $simTime;
1303  $GLOBALS['SIM_ACCESS_TIME'] = $simTime - $simTime % 60;
1304  }
1305  // simulate user
1306  $simUserGroup = $backendUser->adminPanel->extGetFeAdminValue('preview', 'simulateUserGroup');
1307  $this->simUserGroup = $simUserGroup;
1308  if ($simUserGroup) {
1309  if ($this->fe_user->user) {
1310  $this->fe_user->user[$this->fe_user->usergroup_column] = $simUserGroup;
1311  } else {
1312  $this->fe_user->user = array(
1313  $this->fe_user->usergroup_column => $simUserGroup
1314  );
1315  }
1316  }
1317  if (!$simUserGroup && !$simTime && !$this->showHiddenPage && !$this->showHiddenRecords) {
1318  $this->fePreview = 0;
1319  }
1320  }
1321  }
1322  if ($this->id) {
1323  if ($this->determineIdIsHiddenPage()) {
1324  // The preview flag is set only if the current page turns out to actually be hidden!
1325  $this->fePreview = 1;
1326  $this->showHiddenPage = true;
1327  }
1328  // For Live workspace: Check root line for proper connection to tree root (done because of possible preview of page / branch versions)
1329  if (!$this->fePreview && $this->whichWorkspace() === 0) {
1330  // Initialize the page-select functions to check rootline:
1331  $temp_sys_page = GeneralUtility::makeInstance(PageRepository::class);
1332  $temp_sys_page->init($this->showHiddenPage);
1333  // If root line contained NO records and ->error_getRootLine_failPid tells us that it was because of a pid=-1 (indicating a "version" record)...:
1334  if (empty($temp_sys_page->getRootLine($this->id, $this->MP)) && $temp_sys_page->error_getRootLine_failPid == -1) {
1335  // Setting versioningPreview flag and try again:
1336  $temp_sys_page->versioningPreview = true;
1337  if (!empty($temp_sys_page->getRootLine($this->id, $this->MP))) {
1338  // 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.
1339  $this->fePreview = 1;
1340  }
1341  }
1342  }
1343  }
1344  // The preview flag will be set if a backend user is in an offline workspace
1345  if (
1346  (
1347  $backendUser->user['workspace_preview']
1348  || GeneralUtility::_GP('ADMCMD_view')
1349  || $this->doWorkspacePreview()
1350  )
1351  && (
1352  $this->whichWorkspace() === -1
1353  || $this->whichWorkspace() > 0
1354  )
1355  && !GeneralUtility::_GP('ADMCMD_noBeUser')
1356  ) {
1357  // Will show special preview message.
1358  $this->fePreview = 2;
1359  }
1360  // If the front-end is showing a preview, caching MUST be disabled.
1361  if ($this->fePreview) {
1362  $this->disableCache();
1363  }
1364  }
1365  $this->getTimeTracker()->pull();
1366  // Now, get the id, validate access etc:
1367  $this->fetch_the_id();
1368  // Check if backend user has read access to this page. If not, recalculate the id.
1369  if ($this->beUserLogin && $this->fePreview) {
1370  if (!$backendUser->doesUserHaveAccess($this->page, 1)) {
1371  // Resetting
1372  $this->clear_preview();
1373  $this->fe_user->user = $originalFrontendUser;
1374  // Fetching the id again, now with the preview settings reset.
1375  $this->fetch_the_id();
1376  }
1377  }
1378  // Checks if user logins are blocked for a certain branch and if so, will unset user login and re-fetch ID.
1379  $this->loginAllowedInBranch = $this->checkIfLoginAllowedInBranch();
1380  // Logins are not allowed:
1381  if (!$this->loginAllowedInBranch) {
1382  // Only if there is a login will we run this...
1383  if ($this->isUserOrGroupSet()) {
1384  if ($this->loginAllowedInBranch_mode == 'all') {
1385  // Clear out user and group:
1386  $this->fe_user->hideActiveLogin();
1387  $this->gr_list = '0,-1';
1388  } else {
1389  $this->gr_list = '0,-2';
1390  }
1391  // Fetching the id again, now with the preview settings reset.
1392  $this->fetch_the_id();
1393  }
1394  }
1395  // Final cleaning.
1396  // Make sure it's an integer
1397  $this->id = ($this->contentPid = (int)$this->id);
1398  // Make sure it's an integer
1399  $this->type = (int)$this->type;
1400  // Call post processing function for id determination:
1401  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PostProc'])) {
1402  $_params = array('pObj' => &$this);
1403  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PostProc'] as $_funcRef) {
1404  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1405  }
1406  }
1407  }
1408 
1415  protected function determineIdIsHiddenPage()
1416  {
1417  $field = MathUtility::canBeInterpretedAsInteger($this->id) ? 'uid' : 'alias';
1418  $pageSelectCondition = $field . '=' . $this->getDatabaseConnection()->fullQuoteStr($this->id, 'pages');
1419  $page = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid,hidden,starttime,endtime', 'pages', $pageSelectCondition . ' AND pid>=0 AND deleted=0');
1420  $workspace = $this->whichWorkspace();
1421  if ($workspace !== 0 && $workspace !== false) {
1422  // Fetch overlay of page if in workspace and check if it is hidden
1423  $pageSelectObject = GeneralUtility::makeInstance(PageRepository::class);
1424  $pageSelectObject->versioningPreview = true;
1425  $pageSelectObject->init(false);
1426  $targetPage = $pageSelectObject->getWorkspaceVersionOfRecord($this->whichWorkspace(), 'pages', $page['uid']);
1427  $result = $targetPage === -1 || $targetPage === -2;
1428  } else {
1429  $result = is_array($page) && ($page['hidden'] || $page['starttime'] > $GLOBALS['SIM_EXEC_TIME'] || $page['endtime'] != 0 && $page['endtime'] <= $GLOBALS['SIM_EXEC_TIME']);
1430  }
1431  return $result;
1432  }
1433 
1443  public function fetch_the_id()
1444  {
1445  $timeTracker = $this->getTimeTracker();
1446  $timeTracker->push('fetch_the_id initialize/', '');
1447  // Initialize the page-select functions.
1448  $this->sys_page = GeneralUtility::makeInstance(PageRepository::class);
1449  $this->sys_page->versioningPreview = $this->fePreview === 2 || (int)$this->workspacePreview || (bool)GeneralUtility::_GP('ADMCMD_view');
1450  $this->sys_page->versioningWorkspaceId = $this->whichWorkspace();
1451  $this->sys_page->init($this->showHiddenPage);
1452  // Set the valid usergroups for FE
1453  $this->initUserGroups();
1454  // Sets sys_page where-clause
1455  $this->setSysPageWhereClause();
1456  // Splitting $this->id by a period (.).
1457  // First part is 'id' and second part (if exists) will overrule the &type param
1458  $idParts = explode('.', $this->id, 2);
1459  $this->id = $idParts[0];
1460  if (isset($idParts[1])) {
1461  $this->type = $idParts[1];
1462  }
1463 
1464  // If $this->id is a string, it's an alias
1465  $this->checkAndSetAlias();
1466  // The id and type is set to the integer-value - just to be sure...
1467  $this->id = (int)$this->id;
1468  $this->type = (int)$this->type;
1469  $timeTracker->pull();
1470  // We find the first page belonging to the current domain
1471  $timeTracker->push('fetch_the_id domain/', '');
1472  // The page_id of the current domain
1473  $this->domainStartPage = $this->findDomainRecord($this->TYPO3_CONF_VARS['SYS']['recursiveDomainSearch']);
1474  if (!$this->id) {
1475  if ($this->domainStartPage) {
1476  // If the id was not previously set, set it to the id of the domain.
1477  $this->id = $this->domainStartPage;
1478  } else {
1479  // Find the first 'visible' page in that domain
1480  $theFirstPage = $this->sys_page->getFirstWebPage($this->id);
1481  if ($theFirstPage) {
1482  $this->id = $theFirstPage['uid'];
1483  } else {
1484  $message = 'No pages are found on the rootlevel!';
1485  if ($this->checkPageUnavailableHandler()) {
1486  $this->pageUnavailableAndExit($message);
1487  } else {
1488  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1489  throw new ServiceUnavailableException($message, 1301648975);
1490  }
1491  }
1492  }
1493  }
1494  $timeTracker->pull();
1495  $timeTracker->push('fetch_the_id rootLine/', '');
1496  // We store the originally requested id
1497  $this->requestedId = $this->id;
1498  $this->getPageAndRootlineWithDomain($this->domainStartPage);
1499  $timeTracker->pull();
1500  if ($this->pageNotFound && $this->TYPO3_CONF_VARS['FE']['pageNotFound_handling']) {
1501  $pNotFoundMsg = array(
1502  1 => 'ID was not an accessible page',
1503  2 => 'Subsection was found and not accessible',
1504  3 => 'ID was outside the domain',
1505  4 => 'The requested page alias does not exist'
1506  );
1507  $this->pageNotFoundAndExit($pNotFoundMsg[$this->pageNotFound]);
1508  }
1509  if ($this->page['url_scheme'] > 0) {
1510  $newUrl = '';
1511  $requestUrlScheme = parse_url(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'), PHP_URL_SCHEME);
1512  if ((int)$this->page['url_scheme'] === HttpUtility::SCHEME_HTTP && $requestUrlScheme == 'https') {
1513  $newUrl = 'http://' . substr(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'), 8);
1514  } elseif ((int)$this->page['url_scheme'] === HttpUtility::SCHEME_HTTPS && $requestUrlScheme == 'http') {
1515  $newUrl = 'https://' . substr(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'), 7);
1516  }
1517  if ($newUrl !== '') {
1518  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
1519  $headerCode = HttpUtility::HTTP_STATUS_303;
1520  } else {
1521  $headerCode = HttpUtility::HTTP_STATUS_301;
1522  }
1523  HttpUtility::redirect($newUrl, $headerCode);
1524  }
1525  }
1526  // Set no_cache if set
1527  if ($this->page['no_cache']) {
1528  $this->set_no_cache('no_cache is set in page properties');
1529  }
1530  // Init SYS_LASTCHANGED
1531  $this->register['SYS_LASTCHANGED'] = (int)$this->page['tstamp'];
1532  if ($this->register['SYS_LASTCHANGED'] < (int)$this->page['SYS_LASTCHANGED']) {
1533  $this->register['SYS_LASTCHANGED'] = (int)$this->page['SYS_LASTCHANGED'];
1534  }
1535  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['fetchPageId-PostProcessing'])) {
1536  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['fetchPageId-PostProcessing'] as $functionReference) {
1537  $parameters = array('parentObject' => $this);
1538  GeneralUtility::callUserFunction($functionReference, $parameters, $this);
1539  }
1540  }
1541  }
1542 
1558  public function getPageAndRootline()
1559  {
1560  $this->page = $this->sys_page->getPage($this->id);
1561  if (empty($this->page)) {
1562  // If no page, we try to find the page before in the rootLine.
1563  // Page is 'not found' in case the id itself was not an accessible page. code 1
1564  $this->pageNotFound = 1;
1565  $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1566  if (!empty($this->rootLine)) {
1567  $c = count($this->rootLine) - 1;
1568  while ($c > 0) {
1569  // Add to page access failure history:
1570  $this->pageAccessFailureHistory['direct_access'][] = $this->rootLine[$c];
1571  // Decrease to next page in rootline and check the access to that, if OK, set as page record and ID value.
1572  $c--;
1573  $this->id = $this->rootLine[$c]['uid'];
1574  $this->page = $this->sys_page->getPage($this->id);
1575  if (!empty($this->page)) {
1576  break;
1577  }
1578  }
1579  }
1580  // If still no page...
1581  if (empty($this->page)) {
1582  $message = 'The requested page does not exist!';
1583  if ($this->TYPO3_CONF_VARS['FE']['pageNotFound_handling']) {
1584  $this->pageNotFoundAndExit($message);
1585  } else {
1586  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1587  throw new PageNotFoundException($message, 1301648780);
1588  }
1589  }
1590  }
1591  // Spacer is not accessible in frontend
1592  if ($this->page['doktype'] == PageRepository::DOKTYPE_SPACER) {
1593  $message = 'The requested page does not exist!';
1594  if ($this->TYPO3_CONF_VARS['FE']['pageNotFound_handling']) {
1595  $this->pageNotFoundAndExit($message);
1596  } else {
1597  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1598  throw new PageNotFoundException($message, 1301648781);
1599  }
1600  }
1601  // Is the ID a link to another page??
1602  if ($this->page['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
1603  // 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.
1604  $this->MP = '';
1605  // saving the page so that we can check later - when we know
1606  // about languages - whether we took the correct shortcut or
1607  // whether a translation of the page overwrites the shortcut
1608  // target and we need to follow the new target
1609  $this->originalShortcutPage = $this->page;
1610  $this->page = $this->getPageShortcut($this->page['shortcut'], $this->page['shortcut_mode'], $this->page['uid']);
1611  $this->id = $this->page['uid'];
1612  }
1613  // If the page is a mountpoint which should be overlaid with the contents of the mounted page,
1614  // it must never be accessible directly, but only in the mountpoint context. Therefore we change
1615  // the current ID and the user is redirected by checkPageForMountpointRedirect().
1616  if ($this->page['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT && $this->page['mount_pid_ol']) {
1617  $this->originalMountPointPage = $this->page;
1618  $this->page = $this->sys_page->getPage($this->page['mount_pid']);
1619  if (empty($this->page)) {
1620  $message = 'This page (ID ' . $this->originalMountPointPage['uid'] . ') is of type "Mount point" and '
1621  . 'mounts a page which is not accessible (ID ' . $this->originalMountPointPage['mount_pid'] . ').';
1622  throw new PageNotFoundException($message, 1402043263);
1623  }
1624  $this->MP = $this->page['uid'] . '-' . $this->originalMountPointPage['uid'];
1625  $this->id = $this->page['uid'];
1626  }
1627  // Gets the rootLine
1628  $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1629  // If not rootline we're off...
1630  if (empty($this->rootLine)) {
1631  $ws = $this->whichWorkspace();
1632  if ($this->sys_page->error_getRootLine_failPid == -1 && $ws) {
1633  $this->sys_page->versioningPreview = true;
1634  $this->sys_page->versioningWorkspaceId = $ws;
1635  $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1636  }
1637  if (empty($this->rootLine)) {
1638  $message = 'The requested page didn\'t have a proper connection to the tree-root!';
1639  if ($this->checkPageUnavailableHandler()) {
1640  $this->pageUnavailableAndExit($message);
1641  } else {
1642  $rootline = '(' . $this->sys_page->error_getRootLine . ')';
1643  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1644  throw new ServiceUnavailableException($message . '<br /><br />' . $rootline, 1301648167);
1645  }
1646  }
1647  $this->fePreview = 1;
1648  }
1649  // Checking for include section regarding the hidden/starttime/endtime/fe_user (that is access control of a whole subbranch!)
1650  if ($this->checkRootlineForIncludeSection()) {
1651  if (empty($this->rootLine)) {
1652  $message = 'The requested page was not accessible!';
1653  if ($this->checkPageUnavailableHandler()) {
1654  $this->pageUnavailableAndExit($message);
1655  } else {
1656  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1657  throw new ServiceUnavailableException($message, 1301648234);
1658  }
1659  } else {
1660  $el = reset($this->rootLine);
1661  $this->id = $el['uid'];
1662  $this->page = $this->sys_page->getPage($this->id);
1663  $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1664  }
1665  }
1666  }
1667 
1683  public function getPageShortcut($SC, $mode, $thisUid, $itera = 20, $pageLog = array(), $disableGroupCheck = false)
1684  {
1685  $idArray = GeneralUtility::intExplode(',', $SC);
1686  // Find $page record depending on shortcut mode:
1687  switch ($mode) {
1689 
1691  $pageArray = $this->sys_page->getMenu($idArray[0] ? $idArray[0] : $thisUid, '*', 'sorting', 'AND pages.doktype<199 AND pages.doktype!=' . PageRepository::DOKTYPE_BE_USER_SECTION);
1692  $pO = 0;
1693  if ($mode == PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE && !empty($pageArray)) {
1694  $randval = (int)rand(0, count($pageArray) - 1);
1695  $pO = $randval;
1696  }
1697  $c = 0;
1698  $page = array();
1699  foreach ($pageArray as $pV) {
1700  if ($c == $pO) {
1701  $page = $pV;
1702  break;
1703  }
1704  $c++;
1705  }
1706  if (empty($page)) {
1707  $message = 'This page (ID ' . $thisUid . ') is of type "Shortcut" and configured to redirect to a subpage. ' . 'However, this page has no accessible subpages.';
1708  throw new PageNotFoundException($message, 1301648328);
1709  }
1710  break;
1712  $parent = $this->sys_page->getPage($idArray[0] ? $idArray[0] : $thisUid, $disableGroupCheck);
1713  $page = $this->sys_page->getPage($parent['pid'], $disableGroupCheck);
1714  if (empty($page)) {
1715  $message = 'This page (ID ' . $thisUid . ') is of type "Shortcut" and configured to redirect to its parent page. ' . 'However, the parent page is not accessible.';
1716  throw new PageNotFoundException($message, 1301648358);
1717  }
1718  break;
1719  default:
1720  $page = $this->sys_page->getPage($idArray[0], $disableGroupCheck);
1721  if (empty($page)) {
1722  $message = 'This page (ID ' . $thisUid . ') is of type "Shortcut" and configured to redirect to a page, which is not accessible (ID ' . $idArray[0] . ').';
1723  throw new PageNotFoundException($message, 1301648404);
1724  }
1725  }
1726  // Check if short cut page was a shortcut itself, if so look up recursively:
1727  if ($page['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
1728  if (!in_array($page['uid'], $pageLog) && $itera > 0) {
1729  $pageLog[] = $page['uid'];
1730  $page = $this->getPageShortcut($page['shortcut'], $page['shortcut_mode'], $page['uid'], $itera - 1, $pageLog, $disableGroupCheck);
1731  } else {
1732  $pageLog[] = $page['uid'];
1733  $message = 'Page shortcuts were looping in uids ' . implode(',', $pageLog) . '...!';
1734  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1735  throw new \RuntimeException($message, 1294587212);
1736  }
1737  }
1738  // Return resulting page:
1739  return $page;
1740  }
1741 
1749  {
1750  $c = count($this->rootLine);
1751  $removeTheRestFlag = 0;
1752  for ($a = 0; $a < $c; $a++) {
1753  if (!$this->checkPagerecordForIncludeSection($this->rootLine[$a])) {
1754  // Add to page access failure history:
1755  $this->pageAccessFailureHistory['sub_section'][] = $this->rootLine[$a];
1756  $removeTheRestFlag = 1;
1757  }
1758  if ($this->rootLine[$a]['doktype'] == PageRepository::DOKTYPE_BE_USER_SECTION) {
1759  // If there is a backend user logged in, check if he has read access to the page:
1760  if ($this->beUserLogin) {
1761  $row = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid', 'pages', 'uid=' . (int)$this->id . ' AND ' . $this->getBackendUser()->getPagePermsClause(1));
1762  // versionOL()?
1763  if (!$row) {
1764  // 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...
1765  $removeTheRestFlag = 1;
1766  }
1767  } else {
1768  // Dont go here, if there is no backend user logged in.
1769  $removeTheRestFlag = 1;
1770  }
1771  }
1772  if ($removeTheRestFlag) {
1773  // Page is 'not found' in case a subsection was found and not accessible, code 2
1774  $this->pageNotFound = 2;
1775  unset($this->rootLine[$a]);
1776  }
1777  }
1778  return $removeTheRestFlag;
1779  }
1780 
1791  public function checkEnableFields($row, $bypassGroupCheck = false)
1792  {
1793  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'])) {
1794  $_params = array('pObj' => $this, 'row' => &$row, 'bypassGroupCheck' => &$bypassGroupCheck);
1795  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_checkEnableFields'] as $_funcRef) {
1796  // Call hooks: If one returns FALSE, method execution is aborted with result "This record is not available"
1797  $return = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1798  if ($return === false) {
1799  return false;
1800  }
1801  }
1802  }
1803  if ((!$row['hidden'] || $this->showHiddenPage) && $row['starttime'] <= $GLOBALS['SIM_ACCESS_TIME'] && ($row['endtime'] == 0 || $row['endtime'] > $GLOBALS['SIM_ACCESS_TIME']) && ($bypassGroupCheck || $this->checkPageGroupAccess($row))) {
1804  return true;
1805  }
1806  return false;
1807  }
1808 
1817  public function checkPageGroupAccess($row, $groupList = null)
1818  {
1819  if (is_null($groupList)) {
1820  $groupList = $this->gr_list;
1821  }
1822  if (!is_array($groupList)) {
1823  $groupList = explode(',', $groupList);
1824  }
1825  $pageGroupList = explode(',', $row['fe_group'] ?: 0);
1826  return count(array_intersect($groupList, $pageGroupList)) > 0;
1827  }
1828 
1837  public function checkPagerecordForIncludeSection($row)
1838  {
1839  return !$row['extendToSubpages'] || $this->checkEnableFields($row) ? 1 : 0;
1840  }
1841 
1848  {
1849  // Initialize:
1850  $c = count($this->rootLine);
1851  $disable = false;
1852  // Traverse root line from root and outwards:
1853  for ($a = 0; $a < $c; $a++) {
1854  // If a value is set for login state:
1855  if ($this->rootLine[$a]['fe_login_mode'] > 0) {
1856  // Determine state from value:
1857  if ((int)$this->rootLine[$a]['fe_login_mode'] === 1) {
1858  $disable = true;
1859  $this->loginAllowedInBranch_mode = 'all';
1860  } elseif ((int)$this->rootLine[$a]['fe_login_mode'] === 3) {
1861  $disable = true;
1862  $this->loginAllowedInBranch_mode = 'groups';
1863  } else {
1864  $disable = false;
1865  }
1866  }
1867  }
1868  return !$disable;
1869  }
1870 
1877  {
1878  $output = array();
1879  $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());
1880  if (!empty($combinedRecords)) {
1881  foreach ($combinedRecords as $k => $pagerec) {
1882  // 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
1883  // 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!
1884  if (!$k || $pagerec['extendToSubpages']) {
1885  if ($pagerec['hidden']) {
1886  $output['hidden'][$pagerec['uid']] = true;
1887  }
1888  if ($pagerec['starttime'] > $GLOBALS['SIM_ACCESS_TIME']) {
1889  $output['starttime'][$pagerec['uid']] = $pagerec['starttime'];
1890  }
1891  if ($pagerec['endtime'] != 0 && $pagerec['endtime'] <= $GLOBALS['SIM_ACCESS_TIME']) {
1892  $output['endtime'][$pagerec['uid']] = $pagerec['endtime'];
1893  }
1894  if (!$this->checkPageGroupAccess($pagerec)) {
1895  $output['fe_group'][$pagerec['uid']] = $pagerec['fe_group'];
1896  }
1897  }
1898  }
1899  }
1900  return $output;
1901  }
1902 
1911  public function setIDfromArgV()
1912  {
1913  if (!$this->id) {
1914  list($theAlias) = explode('&', GeneralUtility::getIndpEnv('QUERY_STRING'));
1915  $theAlias = trim($theAlias);
1916  $this->id = $theAlias != '' && strpos($theAlias, '=') === false ? $theAlias : 0;
1917  }
1918  }
1919 
1929  {
1930  $this->getPageAndRootline();
1931  // 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.
1932  if ($domainStartPage && is_array($this->rootLine)) {
1933  $idFound = 0;
1934  foreach ($this->rootLine as $key => $val) {
1935  if ($val['uid'] == $domainStartPage) {
1936  $idFound = 1;
1937  break;
1938  }
1939  }
1940  if (!$idFound) {
1941  // Page is 'not found' in case the id was outside the domain, code 3
1942  $this->pageNotFound = 3;
1943  $this->id = $domainStartPage;
1944  // re-get the page and rootline if the id was not found.
1945  $this->getPageAndRootline();
1946  }
1947  }
1948  }
1949 
1956  public function setSysPageWhereClause()
1957  {
1958  $this->sys_page->where_hid_del .= ' AND pages.doktype<200';
1959  $this->sys_page->where_groupAccess = $this->sys_page->getMultipleGroupsWhereClause('pages.fe_group', 'pages');
1960  }
1961 
1969  public function findDomainRecord($recursive = false)
1970  {
1971  if ($recursive) {
1972  $pageUid = 0;
1973  $host = explode('.', GeneralUtility::getIndpEnv('HTTP_HOST'));
1974  while (count($host)) {
1975  $pageUid = $this->sys_page->getDomainStartPage(implode('.', $host), GeneralUtility::getIndpEnv('SCRIPT_NAME'), GeneralUtility::getIndpEnv('REQUEST_URI'));
1976  if ($pageUid) {
1977  return $pageUid;
1978  } else {
1979  array_shift($host);
1980  }
1981  }
1982  return $pageUid;
1983  } else {
1984  return $this->sys_page->getDomainStartPage(GeneralUtility::getIndpEnv('HTTP_HOST'), GeneralUtility::getIndpEnv('SCRIPT_NAME'), GeneralUtility::getIndpEnv('REQUEST_URI'));
1985  }
1986  }
1987 
1995  public function pageUnavailableAndExit($reason = '', $header = '')
1996  {
1997  $header = $header ?: $this->TYPO3_CONF_VARS['FE']['pageUnavailable_handling_statheader'];
1998  $this->pageUnavailableHandler($this->TYPO3_CONF_VARS['FE']['pageUnavailable_handling'], $header, $reason);
1999  die;
2000  }
2001 
2009  public function pageNotFoundAndExit($reason = '', $header = '')
2010  {
2011  $header = $header ?: $this->TYPO3_CONF_VARS['FE']['pageNotFound_handling_statheader'];
2012  $this->pageNotFoundHandler($this->TYPO3_CONF_VARS['FE']['pageNotFound_handling'], $header, $reason);
2013  die;
2014  }
2015 
2023  {
2024  if (
2025  $this->TYPO3_CONF_VARS['FE']['pageUnavailable_handling']
2027  GeneralUtility::getIndpEnv('REMOTE_ADDR'),
2028  $this->TYPO3_CONF_VARS['SYS']['devIPmask']
2029  )
2030  ) {
2031  $checkPageUnavailableHandler = true;
2032  } else {
2033  $checkPageUnavailableHandler = false;
2034  }
2035  return $checkPageUnavailableHandler;
2036  }
2037 
2046  public function pageUnavailableHandler($code, $header, $reason)
2047  {
2048  $this->pageErrorHandler($code, $header, $reason);
2049  }
2050 
2059  public function pageNotFoundHandler($code, $header = '', $reason = '')
2060  {
2061  $this->pageErrorHandler($code, $header, $reason);
2062  }
2063 
2074  public function pageErrorHandler($code, $header = '', $reason = '')
2075  {
2076  // Issue header in any case:
2077  if ($header) {
2078  $headerArr = preg_split('/\\r|\\n/', $header, -1, PREG_SPLIT_NO_EMPTY);
2079  foreach ($headerArr as $header) {
2080  header($header);
2081  }
2082  }
2083  // Create response:
2084  // Simply boolean; Just shows TYPO3 error page with reason:
2085  if (gettype($code) == 'boolean' || (string)$code === '1') {
2086  $title = 'Page Not Found';
2087  $message = 'The page did not exist or was inaccessible.' . ($reason ? ' Reason: ' . htmlspecialchars($reason) : '');
2088  $messagePage = GeneralUtility::makeInstance(ErrorpageMessage::class, $message, $title);
2089  $messagePage->output();
2090  die;
2091  } elseif (GeneralUtility::isFirstPartOfStr($code, 'USER_FUNCTION:')) {
2092  $funcRef = trim(substr($code, 14));
2093  $params = array(
2094  'currentUrl' => GeneralUtility::getIndpEnv('REQUEST_URI'),
2095  'reasonText' => $reason,
2096  'pageAccessFailureReasons' => $this->getPageAccessFailureReasons()
2097  );
2098  echo GeneralUtility::callUserFunction($funcRef, $params, $this);
2099  } elseif (GeneralUtility::isFirstPartOfStr($code, 'READFILE:')) {
2100  $readFile = GeneralUtility::getFileAbsFileName(trim(substr($code, 9)));
2101  if (@is_file($readFile)) {
2102  echo str_replace(
2103  array(
2104  '###CURRENT_URL###',
2105  '###REASON###'
2106  ),
2107  array(
2108  GeneralUtility::getIndpEnv('REQUEST_URI'),
2109  htmlspecialchars($reason)
2110  ),
2111  GeneralUtility::getUrl($readFile)
2112  );
2113  } else {
2114  throw new \RuntimeException('Configuration Error: 404 page "' . $readFile . '" could not be found.', 1294587214);
2115  }
2116  } elseif (GeneralUtility::isFirstPartOfStr($code, 'REDIRECT:')) {
2117  HttpUtility::redirect(substr($code, 9));
2118  } elseif ($code !== '') {
2119  // Check if URL is relative
2120  $url_parts = parse_url($code);
2121  if ($url_parts['host'] == '') {
2122  $url_parts['host'] = GeneralUtility::getIndpEnv('HTTP_HOST');
2123  if ($code[0] === '/') {
2124  $code = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST') . $code;
2125  } else {
2126  $code = GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR') . $code;
2127  }
2128  $checkBaseTag = false;
2129  } else {
2130  $checkBaseTag = true;
2131  }
2132  // Check recursion
2133  if ($code == GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')) {
2134  if ($reason == '') {
2135  $reason = 'Page cannot be found.';
2136  }
2137  $reason .= LF . LF . 'Additionally, ' . $code . ' was not found while trying to retrieve the error document.';
2138  throw new \RuntimeException(nl2br(htmlspecialchars($reason)), 1294587215);
2139  }
2140  // Prepare headers
2141  $headerArr = array(
2142  'User-agent: ' . GeneralUtility::getIndpEnv('HTTP_USER_AGENT'),
2143  'Referer: ' . GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')
2144  );
2145  $res = GeneralUtility::getUrl($code, 1, $headerArr);
2146  // Header and content are separated by an empty line
2147  list($header, $content) = explode(CRLF . CRLF, $res, 2);
2148  $content .= CRLF;
2149  if (false === $res) {
2150  // Last chance -- redirect
2151  HttpUtility::redirect($code);
2152  } else {
2153  // Forward these response headers to the client
2154  $forwardHeaders = array(
2155  'Content-Type:'
2156  );
2157  $headerArr = preg_split('/\\r|\\n/', $header, -1, PREG_SPLIT_NO_EMPTY);
2158  foreach ($headerArr as $header) {
2159  foreach ($forwardHeaders as $h) {
2160  if (preg_match('/^' . $h . '/', $header)) {
2161  header($header);
2162  }
2163  }
2164  }
2165  // Put <base> if necesary
2166  if ($checkBaseTag) {
2167  // If content already has <base> tag, we do not need to do anything
2168  if (false === stristr($content, '<base ')) {
2169  // Generate href for base tag
2170  $base = $url_parts['scheme'] . '://';
2171  if ($url_parts['user'] != '') {
2172  $base .= $url_parts['user'];
2173  if ($url_parts['pass'] != '') {
2174  $base .= ':' . $url_parts['pass'];
2175  }
2176  $base .= '@';
2177  }
2178  $base .= $url_parts['host'];
2179  // Add path portion skipping possible file name
2180  $base .= preg_replace('/(.*\\/)[^\\/]*/', '${1}', $url_parts['path']);
2181  // Put it into content (generate also <head> if necessary)
2182  $replacement = LF . '<base href="' . htmlentities($base) . '" />' . LF;
2183  if (stristr($content, '<head>')) {
2184  $content = preg_replace('/(<head>)/i', '\\1' . $replacement, $content);
2185  } else {
2186  $content = preg_replace('/(<html[^>]*>)/i', '\\1<head>' . $replacement . '</head>', $content);
2187  }
2188  }
2189  }
2190  // Output the content
2191  echo $content;
2192  }
2193  } else {
2194  $title = 'Page Not Found';
2195  $message = $reason ? 'Reason: ' . htmlspecialchars($reason) : 'Page cannot be found.';
2196  $messagePage = GeneralUtility::makeInstance(ErrorpageMessage::class, $message, $title);
2197  $messagePage->output();
2198  }
2199  die;
2200  }
2201 
2209  public function checkAndSetAlias()
2210  {
2211  if ($this->id && !MathUtility::canBeInterpretedAsInteger($this->id)) {
2212  $aid = $this->sys_page->getPageIdFromAlias($this->id);
2213  if ($aid) {
2214  $this->id = $aid;
2215  } else {
2216  $this->pageNotFound = 4;
2217  }
2218  }
2219  }
2220 
2227  public function mergingWithGetVars($GET_VARS)
2228  {
2229  if (is_array($GET_VARS)) {
2230  // Getting $_GET var, unescaped.
2231  $realGet = GeneralUtility::_GET();
2232  if (!is_array($realGet)) {
2233  $realGet = array();
2234  }
2235  // Merge new values on top:
2236  ArrayUtility::mergeRecursiveWithOverrule($realGet, $GET_VARS);
2237  // Write values back to $_GET:
2238  GeneralUtility::_GETset($realGet);
2239  // Setting these specifically (like in the init-function):
2240  if (isset($GET_VARS['type'])) {
2241  $this->type = (int)$GET_VARS['type'];
2242  }
2243  if (isset($GET_VARS['cHash'])) {
2244  $this->cHash = $GET_VARS['cHash'];
2245  }
2246  // @deprecated since TYPO3 7, remove in TYPO3 8 together with jumpurl property.
2247  if (isset($GET_VARS['jumpurl'])) {
2248  $this->jumpurl = $GET_VARS['jumpurl'];
2249  }
2250  if (isset($GET_VARS['MP'])) {
2251  $this->MP = $this->TYPO3_CONF_VARS['FE']['enable_mount_pids'] ? $GET_VARS['MP'] : '';
2252  }
2253  if (isset($GET_VARS['no_cache']) && $GET_VARS['no_cache']) {
2254  $this->set_no_cache('no_cache is requested via GET parameter');
2255  }
2256  }
2257  }
2258 
2259  /********************************************
2260  *
2261  * Template and caching related functions.
2262  *
2263  *******************************************/
2273  public function makeCacheHash()
2274  {
2275  // No need to test anything if caching was already disabled.
2276  if ($this->no_cache && !$this->TYPO3_CONF_VARS['FE']['pageNotFoundOnCHashError']) {
2277  return;
2278  }
2279  $GET = GeneralUtility::_GET();
2280  if ($this->cHash && is_array($GET)) {
2281  $this->cHash_array = $this->cacheHash->getRelevantParameters(GeneralUtility::implodeArrayForUrl('', $GET));
2282  $cHash_calc = $this->cacheHash->calculateCacheHash($this->cHash_array);
2283  if ($cHash_calc != $this->cHash) {
2284  if ($this->TYPO3_CONF_VARS['FE']['pageNotFoundOnCHashError']) {
2285  $this->pageNotFoundAndExit('Request parameters could not be validated (&cHash comparison failed)');
2286  } else {
2287  $this->disableCache();
2288  $this->getTimeTracker()->setTSlogMessage('The incoming cHash "' . $this->cHash . '" and calculated cHash "' . $cHash_calc . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($this->cHash_array)) . '"', 2);
2289  }
2290  }
2291  } elseif (is_array($GET)) {
2292  // No cHash is set, check if that is correct
2293  if ($this->cacheHash->doParametersRequireCacheHash(GeneralUtility::implodeArrayForUrl('', $GET))) {
2294  $this->reqCHash();
2295  }
2296  }
2297  }
2298 
2306  public function reqCHash()
2307  {
2308  if (!$this->cHash) {
2309  if ($this->TYPO3_CONF_VARS['FE']['pageNotFoundOnCHashError']) {
2310  if ($this->tempContent) {
2311  $this->clearPageCacheContent();
2312  }
2313  $this->pageNotFoundAndExit('Request parameters could not be validated (&cHash empty)');
2314  } else {
2315  $this->disableCache();
2316  $this->getTimeTracker()->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', 2);
2317  }
2318  }
2319  }
2320 
2326  public function initTemplate()
2327  {
2328  $this->tmpl = GeneralUtility::makeInstance(TemplateService::class);
2329  $this->tmpl->setVerbose((bool)$this->beUserLogin);
2330  $this->tmpl->init();
2331  $this->tmpl->tt_track = (bool)$this->beUserLogin;
2332  }
2333 
2341  public function getFromCache()
2342  {
2343  // clearing the content-variable, which will hold the pagecontent
2344  $this->content = '';
2345  // Unsetting the lowlevel config
2346  unset($this->config);
2347  $this->cacheContentFlag = false;
2348 
2349  if ($this->no_cache) {
2350  return;
2351  }
2352 
2353  $pageSectionCacheContent = $this->tmpl->getCurrentPageData();
2354  if (!is_array($pageSectionCacheContent)) {
2355  // Nothing in the cache, we acquire an "exclusive lock" for the key now.
2356  // We use the Registry to store this lock centrally,
2357  // but we protect the access again with a global exclusive lock to avoid race conditions
2358 
2359  $this->acquireLock('pagesection', $this->id . '::' . $this->MP);
2360  //
2361  // from this point on we're the only one working on that page ($key)
2362  //
2363 
2364  // query the cache again to see if the page data are there meanwhile
2365  $pageSectionCacheContent = $this->tmpl->getCurrentPageData();
2366  if (is_array($pageSectionCacheContent)) {
2367  // we have the content, nice that some other process did the work for us already
2368  $this->releaseLock('pagesection');
2369  } else {
2370  // We keep the lock set, because we are the ones generating the page now
2371  // and filling the cache.
2372  // This indicates that we have to release the lock in the Registry later in releaseLocks()
2373  }
2374  }
2375 
2376  if (is_array($pageSectionCacheContent)) {
2377  // 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.
2378  // If this hash is not the same in here in this section and after page-generation, then the page will not be properly cached!
2379  // 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.
2380  $pageSectionCacheContent = $this->tmpl->matching($pageSectionCacheContent);
2381  ksort($pageSectionCacheContent);
2382  $this->all = $pageSectionCacheContent;
2383  }
2384  unset($pageSectionCacheContent);
2385 
2386  // Look for page in cache only if a shift-reload is not sent to the server.
2387  $lockHash = $this->getLockHash();
2388  if (!$this->headerNoCache()) {
2389  if ($this->all) {
2390  // we got page section information
2391  $this->newHash = $this->getHash();
2392  $this->getTimeTracker()->push('Cache Row', '');
2393  $row = $this->getFromCache_queryRow();
2394  if (!is_array($row)) {
2395  // nothing in the cache, we acquire an exclusive lock now
2396 
2397  $this->acquireLock('pages', $lockHash);
2398  //
2399  // from this point on we're the only one working on that page ($lockHash)
2400  //
2401 
2402  // query the cache again to see if the data are there meanwhile
2403  $row = $this->getFromCache_queryRow();
2404  if (is_array($row)) {
2405  // we have the content, nice that some other process did the work for us
2406  $this->releaseLock('pages');
2407  } else {
2408  // We keep the lock set, because we are the ones generating the page now
2409  // and filling the cache.
2410  // This indicates that we have to release the lock in the Registry later in releaseLocks()
2411  }
2412  }
2413  if (is_array($row)) {
2414  // we have data from cache
2415 
2416  // Call hook when a page is retrieved from cache:
2417  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageLoadedFromCache'])) {
2418  $_params = array('pObj' => &$this, 'cache_pages_row' => &$row);
2419  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageLoadedFromCache'] as $_funcRef) {
2420  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2421  }
2422  }
2423  // Fetches the lowlevel config stored with the cached data
2424  $this->config = $row['cache_data'];
2425  // Getting the content
2426  $this->content = $row['content'];
2427  // Flag for temp content
2428  $this->tempContent = $row['temp_content'];
2429  // Setting flag, so we know, that some cached content has been loaded
2430  $this->cacheContentFlag = true;
2431  $this->cacheExpires = $row['expires'];
2432 
2433  // Restore page title information, this is needed to generate the page title for
2434  // partially cached pages.
2435  $this->page['title'] = $row['pageTitleInfo']['title'];
2436  $this->altPageTitle = $row['pageTitleInfo']['altPageTitle'];
2437  $this->indexedDocTitle = $row['pageTitleInfo']['indexedDocTitle'];
2438 
2439  if (isset($this->config['config']['debug'])) {
2440  $debugCacheTime = (bool)$this->config['config']['debug'];
2441  } else {
2442  $debugCacheTime = !empty($this->TYPO3_CONF_VARS['FE']['debug']);
2443  }
2444  if ($debugCacheTime) {
2445  $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
2446  $timeFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
2447  $this->content .= LF . '<!-- Cached page generated ' . date(($dateFormat . ' ' . $timeFormat), $row['tstamp']) . '. Expires ' . Date(($dateFormat . ' ' . $timeFormat), $row['expires']) . ' -->';
2448  }
2449  }
2450  $this->getTimeTracker()->pull();
2451 
2452  return;
2453  }
2454  }
2455  // the user forced rebuilding the page cache or there was no pagesection information
2456  // get a lock for the page content so other processes will not interrupt the regeneration
2457  $this->acquireLock('pages', $lockHash);
2458  }
2459 
2465  public function getFromCache_queryRow()
2466  {
2467  $this->getTimeTracker()->push('Cache Query', '');
2468  $row = $this->pageCache->get($this->newHash);
2469  $this->getTimeTracker()->pull();
2470  return $row;
2471  }
2472 
2480  public function headerNoCache()
2481  {
2482  $disableAcquireCacheData = false;
2483  if ($this->beUserLogin) {
2484  if (strtolower($_SERVER['HTTP_CACHE_CONTROL']) === 'no-cache' || strtolower($_SERVER['HTTP_PRAGMA']) === 'no-cache') {
2485  $disableAcquireCacheData = true;
2486  }
2487  }
2488  // Call hook for possible by-pass of requiring of page cache (for recaching purpose)
2489  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['headerNoCache'])) {
2490  $_params = array('pObj' => &$this, 'disableAcquireCacheData' => &$disableAcquireCacheData);
2491  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['headerNoCache'] as $_funcRef) {
2492  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2493  }
2494  }
2495  return $disableAcquireCacheData;
2496  }
2497 
2507  public function getHash()
2508  {
2509  return md5($this->createHashBase(false));
2510  }
2511 
2520  public function getLockHash()
2521  {
2522  $lockHash = $this->createHashBase(true);
2523  return md5($lockHash);
2524  }
2525 
2536  protected function createHashBase($createLockHashBase = false)
2537  {
2538  $hashParameters = array(
2539  'id' => (int)$this->id,
2540  'type' => (int)$this->type,
2541  'gr_list' => (string)$this->gr_list,
2542  'MP' => (string)$this->MP,
2543  'cHash' => $this->cHash_array,
2544  'domainStartPage' => $this->domainStartPage
2545  );
2546  // Include the template information if we shouldn't create a lock hash
2547  if (!$createLockHashBase) {
2548  $hashParameters['all'] = $this->all;
2549  }
2550  // Call hook to influence the hash calculation
2551  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['createHashBase'])) {
2552  $_params = array(
2553  'hashParameters' => &$hashParameters,
2554  'createLockHashBase' => $createLockHashBase
2555  );
2556  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['createHashBase'] as $_funcRef) {
2557  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2558  }
2559  }
2560  return serialize($hashParameters);
2561  }
2562 
2569  public function getConfigArray()
2570  {
2571  // 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
2572  if (!is_array($this->config) || is_array($this->config['INTincScript']) || $this->forceTemplateParsing) {
2573  $timeTracker = $this->getTimeTracker();
2574  $timeTracker->push('Parse template', '');
2575  // Force parsing, if set?:
2576  $this->tmpl->forceTemplateParsing = $this->forceTemplateParsing;
2577  // Start parsing the TS template. Might return cached version.
2578  $this->tmpl->start($this->rootLine);
2579  $timeTracker->pull();
2580  if ($this->tmpl->loaded) {
2581  $timeTracker->push('Setting the config-array', '');
2582  // toplevel - objArrayName
2583  $this->sPre = $this->tmpl->setup['types.'][$this->type];
2584  $this->pSetup = $this->tmpl->setup[$this->sPre . '.'];
2585  if (!is_array($this->pSetup)) {
2586  $message = 'The page is not configured! [type=' . $this->type . '][' . $this->sPre . '].';
2587  if ($this->checkPageUnavailableHandler()) {
2588  $this->pageUnavailableAndExit($message);
2589  } else {
2590  $explanation = 'This means that there is no TypoScript object of type PAGE with typeNum=' . $this->type . ' configured.';
2591  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
2592  throw new ServiceUnavailableException($message . ' ' . $explanation, 1294587217);
2593  }
2594  } else {
2595  if (!isset($this->config['config'])) {
2596  $this->config['config'] = array();
2597  }
2598  // Filling the config-array, first with the main "config." part
2599  if (is_array($this->tmpl->setup['config.'])) {
2600  ArrayUtility::mergeRecursiveWithOverrule($this->tmpl->setup['config.'], $this->config['config']);
2601  $this->config['config'] = $this->tmpl->setup['config.'];
2602  }
2603  // override it with the page/type-specific "config."
2604  if (is_array($this->pSetup['config.'])) {
2605  ArrayUtility::mergeRecursiveWithOverrule($this->config['config'], $this->pSetup['config.']);
2606  }
2607  if ($this->config['config']['typolinkEnableLinksAcrossDomains']) {
2608  $this->config['config']['typolinkCheckRootline'] = true;
2609  }
2610  // Set default values for removeDefaultJS and inlineStyle2TempFile so CSS and JS are externalized if compatversion is higher than 4.0
2611  if (!isset($this->config['config']['removeDefaultJS'])) {
2612  $this->config['config']['removeDefaultJS'] = 'external';
2613  }
2614  if (!isset($this->config['config']['inlineStyle2TempFile'])) {
2615  $this->config['config']['inlineStyle2TempFile'] = 1;
2616  }
2617 
2618  if (!isset($this->config['config']['compressJs'])) {
2619  $this->config['config']['compressJs'] = 0;
2620  }
2621  // Processing for the config_array:
2622  $this->config['rootLine'] = $this->tmpl->rootLine;
2623  $this->config['mainScript'] = trim($this->config['config']['mainScript']) ?: 'index.php';
2624  // Class for render Header and Footer parts
2625  if ($this->pSetup['pageHeaderFooterTemplateFile']) {
2626  $file = $this->tmpl->getFileName($this->pSetup['pageHeaderFooterTemplateFile']);
2627  if ($file) {
2628  $this->pageRenderer->setTemplateFile($file);
2629  }
2630  }
2631  }
2632  $timeTracker->pull();
2633  } else {
2634  if ($this->checkPageUnavailableHandler()) {
2635  $this->pageUnavailableAndExit('No TypoScript template found!');
2636  } else {
2637  $message = 'No TypoScript template found!';
2638  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
2639  throw new ServiceUnavailableException($message, 1294587218);
2640  }
2641  }
2642  }
2643 
2644  // No cache
2645  // Set $this->no_cache TRUE if the config.no_cache value is set!
2646  if ($this->config['config']['no_cache']) {
2647  $this->set_no_cache('config.no_cache is set');
2648  }
2649  // Merge GET with defaultGetVars
2650  if (!empty($this->config['config']['defaultGetVars.'])) {
2651  $modifiedGetVars = GeneralUtility::removeDotsFromTS($this->config['config']['defaultGetVars.']);
2653  GeneralUtility::_GETset($modifiedGetVars);
2654  }
2655  // Hook for postProcessing the configuration array
2656  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['configArrayPostProc'])) {
2657  $params = array('config' => &$this->config['config']);
2658  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['configArrayPostProc'] as $funcRef) {
2659  GeneralUtility::callUserFunction($funcRef, $params, $this);
2660  }
2661  }
2662  }
2663 
2664  /********************************************
2665  *
2666  * Further initialization and data processing
2667  * (jumpurl/submission of forms)
2668  *
2669  *******************************************/
2670 
2678  public function settingLanguage()
2679  {
2680  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_preProcess'])) {
2681  $_params = array();
2682  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_preProcess'] as $_funcRef) {
2683  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2684  }
2685  }
2686 
2687  // Initialize charset settings etc.
2688  $this->initLLvars();
2689 
2690  // Get values from TypoScript:
2691  $this->sys_language_uid = ($this->sys_language_content = (int)$this->config['config']['sys_language_uid']);
2692  list($this->sys_language_mode, $sys_language_content) = GeneralUtility::trimExplode(';', $this->config['config']['sys_language_mode']);
2693  $this->sys_language_contentOL = $this->config['config']['sys_language_overlay'];
2694  // If sys_language_uid is set to another language than default:
2695  if ($this->sys_language_uid > 0) {
2696  // check whether a shortcut is overwritten by a translated page
2697  // we can only do this now, as this is the place where we get
2698  // to know about translations
2699  $this->checkTranslatedShortcut();
2700  // Request the overlay record for the sys_language_uid:
2701  $olRec = $this->sys_page->getPageOverlay($this->id, $this->sys_language_uid);
2702  if (empty($olRec)) {
2703  // If no OL record exists and a foreign language is asked for...
2704  if ($this->sys_language_uid) {
2705  // If requested translation is not available:
2706  if (GeneralUtility::hideIfNotTranslated($this->page['l18n_cfg'])) {
2707  $this->pageNotFoundAndExit('Page is not available in the requested language.');
2708  } else {
2709  switch ((string)$this->sys_language_mode) {
2710  case 'strict':
2711  $this->pageNotFoundAndExit('Page is not available in the requested language (strict).');
2712  break;
2713  case 'content_fallback':
2714  $fallBackOrder = GeneralUtility::intExplode(',', $sys_language_content);
2715  foreach ($fallBackOrder as $orderValue) {
2716  if ((string)$orderValue === '0' || !empty($this->sys_page->getPageOverlay($this->id, $orderValue))) {
2717  $this->sys_language_content = $orderValue;
2718  // Setting content uid (but leaving the sys_language_uid)
2719  break;
2720  }
2721  }
2722  break;
2723  case 'ignore':
2724  $this->sys_language_content = $this->sys_language_uid;
2725  break;
2726  default:
2727  // Default is that everything defaults to the default language...
2728  $this->sys_language_uid = ($this->sys_language_content = 0);
2729  }
2730  }
2731  }
2732  } else {
2733  // Setting sys_language if an overlay record was found (which it is only if a language is used)
2734  $this->page = $this->sys_page->getPageOverlay($this->page, $this->sys_language_uid);
2735  }
2736  }
2737  // Setting sys_language_uid inside sys-page:
2738  $this->sys_page->sys_language_uid = $this->sys_language_uid;
2739  // If default translation is not available:
2740  if ((!$this->sys_language_uid || !$this->sys_language_content) && $this->page['l18n_cfg'] & 1) {
2741  $message = 'Page is not available in default language.';
2742  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
2743  $this->pageNotFoundAndExit($message);
2744  }
2746 
2747  // Finding the ISO code for the currently selected language
2748  // fetched by the sys_language record when not fetching content from the default language
2749  if ($this->sys_language_content > 0) {
2750  // using sys_language_content because the ISO code only (currently) affect content selection from FlexForms - which should follow "sys_language_content"
2751  // Set the fourth parameter to TRUE in the next two getRawRecord() calls to
2752  // avoid versioning overlay to be applied as it generates an SQL error
2753  $sys_language_row = $this->sys_page->getRawRecord('sys_language', $this->sys_language_content, 'language_isocode,static_lang_isocode', true);
2754  if (is_array($sys_language_row)) {
2755  if (!empty($sys_language_row['language_isocode'])) {
2756  $this->sys_language_isocode = $sys_language_row['language_isocode'];
2757  } elseif ($sys_language_row['static_lang_isocode'] && ExtensionManagementUtility::isLoaded('static_info_tables')) {
2758  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.');
2759  $stLrow = $this->sys_page->getRawRecord('static_languages', $sys_language_row['static_lang_isocode'], 'lg_iso_2', true);
2760  $this->sys_language_isocode = $stLrow['lg_iso_2'];
2761  }
2762  }
2763  // the DB value is overriden by TypoScript
2764  if (!empty($this->config['config']['sys_language_isocode'])) {
2765  $this->sys_language_isocode = $this->config['config']['sys_language_isocode'];
2766  }
2767  } else {
2768  // fallback to the TypoScript option when rendering with sys_language_uid=0
2769  // also: use "en" by default
2770  if (!empty($this->config['config']['sys_language_isocode_default'])) {
2771  $this->sys_language_isocode = $this->config['config']['sys_language_isocode_default'];
2772  } else {
2773  $this->sys_language_isocode = $this->lang != 'default' ? $this->lang : 'en';
2774  }
2775  }
2776 
2777 
2778  // Setting softMergeIfNotBlank:
2779  $table_fields = GeneralUtility::trimExplode(',', $this->config['config']['sys_language_softMergeIfNotBlank'], true);
2780  foreach ($table_fields as $TF) {
2781  list($tN, $fN) = explode(':', $TF);
2782  $GLOBALS['TCA'][$tN]['columns'][$fN]['l10n_mode'] = 'mergeIfNotBlank';
2783  }
2784  // Setting softExclude:
2785  $table_fields = GeneralUtility::trimExplode(',', $this->config['config']['sys_language_softExclude'], true);
2786  foreach ($table_fields as $TF) {
2787  list($tN, $fN) = explode(':', $TF);
2788  $GLOBALS['TCA'][$tN]['columns'][$fN]['l10n_mode'] = 'exclude';
2789  }
2790  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_postProcess'])) {
2791  $_params = array();
2792  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_postProcess'] as $_funcRef) {
2793  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2794  }
2795  }
2796  }
2797 
2801  protected function updateRootLinesWithTranslations()
2802  {
2803  if ($this->sys_language_uid) {
2804  $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
2805  $this->tmpl->updateRootlineData($this->rootLine);
2806  }
2807  }
2808 
2814  public function settingLocale()
2815  {
2816  // Setting locale
2817  if ($this->config['config']['locale_all']) {
2818  // There's a problem that PHP parses float values in scripts wrong if the
2819  // locale LC_NUMERIC is set to something with a comma as decimal point
2820  // Do we set all except LC_NUMERIC
2821  $locale = setlocale(LC_COLLATE, $this->config['config']['locale_all']);
2822  if ($locale) {
2823  setlocale(LC_CTYPE, $this->config['config']['locale_all']);
2824  setlocale(LC_MONETARY, $this->config['config']['locale_all']);
2825  setlocale(LC_TIME, $this->config['config']['locale_all']);
2826  $this->localeCharset = $this->csConvObj->get_locale_charset($this->config['config']['locale_all']);
2827  } else {
2828  $this->getTimeTracker()->setTSlogMessage('Locale "' . htmlspecialchars($this->config['config']['locale_all']) . '" not found.', 3);
2829  }
2830  }
2831  }
2832 
2841  protected function checkTranslatedShortcut()
2842  {
2843  if (!is_null($this->originalShortcutPage)) {
2844  $originalShortcutPageOverlay = $this->sys_page->getPageOverlay($this->originalShortcutPage['uid'], $this->sys_language_uid);
2845  if (!empty($originalShortcutPageOverlay['shortcut']) && $originalShortcutPageOverlay['shortcut'] != $this->id) {
2846  // the translation of the original shortcut page has a different shortcut target!
2847  // set the correct page and id
2848  $shortcut = $this->getPageShortcut($originalShortcutPageOverlay['shortcut'], $originalShortcutPageOverlay['shortcut_mode'], $originalShortcutPageOverlay['uid']);
2849  $this->id = ($this->contentPid = $shortcut['uid']);
2850  $this->page = $this->sys_page->getPage($this->id);
2851  // Fix various effects on things like menus f.e.
2852  $this->fetch_the_id();
2853  $this->tmpl->rootLine = array_reverse($this->rootLine);
2854  }
2855  }
2856  }
2857 
2864  public function handleDataSubmission()
2865  {
2866  // Hook for processing data submission to extensions
2867  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkDataSubmission'])) {
2868  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkDataSubmission'] as $_classRef) {
2869  $_procObj = GeneralUtility::getUserObj($_classRef);
2870  $_procObj->checkDataSubmission($this);
2871  }
2872  }
2873  }
2874 
2880  public function initializeRedirectUrlHandlers()
2881  {
2882  if (
2883  empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlHandlers'])
2884  || !is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlHandlers'])
2885  ) {
2886  return;
2887  }
2888 
2889  $urlHandlers = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlHandlers'];
2890  foreach ($urlHandlers as $identifier => $configuration) {
2891  if (empty($configuration) || !is_array($configuration)) {
2892  throw new \RuntimeException('Missing configuration for URL handler "' . $identifier . '".', 1442052263);
2893  }
2894  if (!is_string($configuration['handler']) || empty($configuration['handler']) || !class_exists($configuration['handler']) || !is_subclass_of($configuration['handler'], UrlHandlerInterface::class)) {
2895  throw new \RuntimeException('The URL handler "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlHandlerInterface::class . '".', 1442052249);
2896  }
2897  }
2898 
2899  $orderedHandlers = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlHandlers);
2900 
2901  foreach ($orderedHandlers as $configuration) {
2903  $urlHandler = GeneralUtility::makeInstance($configuration['handler']);
2904  if ($urlHandler->canHandleCurrentUrl()) {
2905  $this->activeUrlHandlers[] = $urlHandler;
2906  }
2907  }
2908  }
2909 
2917  public function setExternalJumpUrl()
2918  {
2920  $this->initializeRedirectUrlHandlers();
2921  }
2922 
2933  public function jumpUrl()
2934  {
2936  $this->redirectToExternalUrl();
2937  }
2938 
2947  public function redirectToExternalUrl()
2948  {
2949  foreach ($this->activeUrlHandlers as $redirectHandler) {
2950  $redirectHandler->handle();
2951  }
2952 
2953  if (!empty($this->activeUrlHandlers)) {
2954  throw new \RuntimeException('A URL handler is active but did not process the URL.', 1442305505);
2955  }
2956  }
2957 
2965  public function setUrlIdToken()
2966  {
2967  if ($this->config['config']['ftu']) {
2968  $this->getMethodUrlIdToken = $this->TYPO3_CONF_VARS['FE']['get_url_id_token'];
2969  } else {
2970  $this->getMethodUrlIdToken = '';
2971  }
2972  }
2973 
2980  public function calculateLinkVars()
2981  {
2982  $this->linkVars = '';
2983  $linkVars = GeneralUtility::trimExplode(',', (string)$this->config['config']['linkVars']);
2984  if (empty($linkVars)) {
2985  return;
2986  }
2987  $getData = GeneralUtility::_GET();
2988  foreach ($linkVars as $linkVar) {
2989  $test = ($value = '');
2990  if (preg_match('/^(.*)\\((.+)\\)$/', $linkVar, $match)) {
2991  $linkVar = trim($match[1]);
2992  $test = trim($match[2]);
2993  }
2994  if ($linkVar === '' || !isset($getData[$linkVar])) {
2995  continue;
2996  }
2997  if (!is_array($getData[$linkVar])) {
2998  $temp = rawurlencode($getData[$linkVar]);
2999  if ($test !== '' && !PageGenerator::isAllowedLinkVarValue($temp, $test)) {
3000  // Error: This value was not allowed for this key
3001  continue;
3002  }
3003  $value = '&' . $linkVar . '=' . $temp;
3004  } else {
3005  if ($test !== '' && $test !== 'array') {
3006  // Error: This key must not be an array!
3007  continue;
3008  }
3009  $value = GeneralUtility::implodeArrayForUrl($linkVar, $getData[$linkVar]);
3010  }
3011  $this->linkVars .= $value;
3012  }
3013  }
3014 
3024  {
3025  if (!empty($this->originalMountPointPage) && $this->originalMountPointPage['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT) {
3026  $this->redirectToCurrentPage();
3027  }
3028  }
3029 
3039  {
3040  if (!empty($this->originalShortcutPage) && $this->originalShortcutPage['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
3041  $this->redirectToCurrentPage();
3042  }
3043  }
3044 
3051  protected function redirectToCurrentPage()
3052  {
3053  $this->calculateLinkVars();
3054  // Instantiate \TYPO3\CMS\Frontend\ContentObject to generate the correct target URL
3056  $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
3057  $parameter = $this->page['uid'];
3058  $type = GeneralUtility::_GET('type');
3060  $parameter .= ',' . $type;
3061  }
3062  $redirectUrl = $cObj->typoLink_URL(array('parameter' => $parameter));
3063 
3064  // Prevent redirection loop
3065  if (!empty($redirectUrl)) {
3066  // redirect and exit
3068  }
3069  }
3070 
3071  /********************************************
3072  *
3073  * Page generation; cache handling
3074  *
3075  *******************************************/
3082  public function isGeneratePage()
3083  {
3084  return !$this->cacheContentFlag && empty($this->activeUrlHandlers);
3085  }
3086 
3093  public function tempPageCacheContent()
3094  {
3095  $this->tempContent = false;
3096  if (!$this->no_cache) {
3097  $seconds = 30;
3098  $title = htmlspecialchars($this->tmpl->printTitle($this->page['title']));
3099  $request_uri = htmlspecialchars(GeneralUtility::getIndpEnv('REQUEST_URI'));
3100  $stdMsg = '
3101  <strong>Page is being generated.</strong><br />
3102  If this message does not disappear within ' . $seconds . ' seconds, please reload.';
3103  $message = $this->config['config']['message_page_is_being_generated'];
3104  if ((string)$message !== '') {
3105  // This page is always encoded as UTF-8
3106  $message = $this->csConvObj->utf8_encode($message, $this->renderCharset);
3107  $message = str_replace('###TITLE###', $title, $message);
3108  $message = str_replace('###REQUEST_URI###', $request_uri, $message);
3109  } else {
3110  $message = $stdMsg;
3111  }
3112  $temp_content = '<?xml version="1.0" encoding="UTF-8"?>
3113 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3114  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3115 <html xmlns="http://www.w3.org/1999/xhtml">
3116  <head>
3117  <title>' . $title . '</title>
3118  <meta http-equiv="refresh" content="10" />
3119  </head>
3120  <body style="background-color:white; font-family:Verdana,Arial,Helvetica,sans-serif; color:#cccccc; text-align:center;">' . $message . '
3121  </body>
3122 </html>';
3123  // Fix 'nice errors' feature in modern browsers
3124  $padSuffix = '<!--pad-->';
3125  // prevent any trims
3126  $padSize = 768 - strlen($padSuffix) - strlen($temp_content);
3127  if ($padSize > 0) {
3128  $temp_content = str_pad($temp_content, $padSize, LF) . $padSuffix;
3129  }
3130  if (!$this->headerNoCache() && ($cachedRow = $this->getFromCache_queryRow())) {
3131  // We are here because between checking for cached content earlier and now some other HTTP-process managed to store something in cache AND it was not due to a shift-reload by-pass.
3132  // This is either the "Page is being generated" screen or it can be the final result.
3133  // In any case we should not begin another rendering process also, so we silently disable caching and render the page ourselves and that's it.
3134  // Actually $cachedRow contains content that we could show instead of rendering. Maybe we should do that to gain more performance but then we should set all the stuff done in $this->getFromCache()... For now we stick to this...
3135  $this->set_no_cache('Another process wrote into the cache since the beginning of the render process', true);
3136 
3137  // Since the new Locking API this should never be the case
3138  } else {
3139  $this->tempContent = true;
3140  // This flag shows that temporary content is put in the cache
3141  $this->setPageCacheContent($temp_content, $this->config, $GLOBALS['EXEC_TIME'] + $seconds);
3142  }
3143  }
3144  }
3145 
3151  public function realPageCacheContent()
3152  {
3153  // seconds until a cached page is too old
3154  $cacheTimeout = $this->get_cache_timeout();
3155  $timeOutTime = $GLOBALS['EXEC_TIME'] + $cacheTimeout;
3156  $this->tempContent = false;
3157  $usePageCache = true;
3158  // Hook for deciding whether page cache should be written to the cache backend or not
3159  // NOTE: as hooks are called in a loop, the last hook will have the final word (however each
3160  // hook receives the current status of the $usePageCache flag)
3161  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache'])) {
3162  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache'] as $_classRef) {
3163  $_procObj = GeneralUtility::getUserObj($_classRef);
3164  $usePageCache = $_procObj->usePageCache($this, $usePageCache);
3165  }
3166  }
3167  // Write the page to cache, if necessary
3168  if ($usePageCache) {
3169  $this->setPageCacheContent($this->content, $this->config, $timeOutTime);
3170  }
3171  // Hook for cache post processing (eg. writing static files!)
3172  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['insertPageIncache'])) {
3173  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['insertPageIncache'] as $_classRef) {
3174  $_procObj = GeneralUtility::getUserObj($_classRef);
3175  $_procObj->insertPageIncache($this, $timeOutTime);
3176  }
3177  }
3178  }
3179 
3189  public function setPageCacheContent($content, $data, $expirationTstamp)
3190  {
3191  $cacheData = array(
3192  'identifier' => $this->newHash,
3193  'page_id' => $this->id,
3194  'content' => $content,
3195  'temp_content' => $this->tempContent,
3196  'cache_data' => $data,
3197  'expires' => $expirationTstamp,
3198  'tstamp' => $GLOBALS['EXEC_TIME'],
3199  'pageTitleInfo' => array(
3200  'title' => $this->page['title'],
3201  'altPageTitle' => $this->altPageTitle,
3202  'indexedDocTitle' => $this->indexedDocTitle
3203  )
3204  );
3205  $this->cacheExpires = $expirationTstamp;
3206  $this->pageCacheTags[] = 'pageId_' . $cacheData['page_id'];
3207  if ($this->page_cache_reg1) {
3208  $reg1 = (int)$this->page_cache_reg1;
3209  $cacheData['reg1'] = $reg1;
3210  $this->pageCacheTags[] = 'reg1_' . $reg1;
3211  }
3212  if (!empty($this->page['cache_tags'])) {
3213  $tags = GeneralUtility::trimExplode(',', $this->page['cache_tags'], true);
3214  $this->pageCacheTags = array_merge($this->pageCacheTags, $tags);
3215  }
3216  $this->pageCache->set($this->newHash, $cacheData, $this->pageCacheTags, $expirationTstamp - $GLOBALS['EXEC_TIME']);
3217  }
3218 
3224  public function clearPageCacheContent()
3225  {
3226  $this->pageCache->remove($this->newHash);
3227  }
3228 
3235  public function clearPageCacheContent_pidList($pidList)
3236  {
3237  $pageIds = GeneralUtility::trimExplode(',', $pidList);
3238  foreach ($pageIds as $pageId) {
3239  $this->pageCache->flushByTag('pageId_' . (int)$pageId);
3240  }
3241  }
3242 
3250  public function setSysLastChanged()
3251  {
3252  // Draft workspaces are always uid 1 or more. We do not update SYS_LASTCHANGED if we are browsing page from one of theses workspaces
3253  if ((int)$this->whichWorkspace() < 1 && $this->page['SYS_LASTCHANGED'] < (int)$this->register['SYS_LASTCHANGED']) {
3254  $this->getDatabaseConnection()->exec_UPDATEquery('pages', 'uid=' . (int)$this->id, array('SYS_LASTCHANGED' => (int)$this->register['SYS_LASTCHANGED']));
3255  }
3256  }
3257 
3268  public function acquirePageGenerationLock(&$lockObj, $key)
3269  {
3271  if ($this->no_cache || $this->headerNoCache()) {
3272  GeneralUtility::sysLog('Locking: Page is not cached, no locking required', 'cms', GeneralUtility::SYSLOG_SEVERITY_INFO);
3273  // No locking is needed if caching is disabled
3274  return true;
3275  }
3276  try {
3277  if (!is_object($lockObj)) {
3278  $lockObj = GeneralUtility::makeInstance(Locker::class, $key, $this->TYPO3_CONF_VARS['SYS']['lockingMode']);
3279  }
3280  $success = false;
3281  if ($key !== '') {
3282  // TRUE = Page could get locked without blocking
3283  // FALSE = Page could get locked but process was blocked before
3284  $success = $lockObj->acquire();
3285  if ($lockObj->getLockStatus()) {
3286  $lockObj->sysLog('Acquired lock');
3287  }
3288  }
3289  } catch (\Exception $e) {
3290  GeneralUtility::sysLog('Locking: Failed to acquire lock: ' . $e->getMessage(), 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
3291  // If locking fails, return with FALSE and continue without locking
3292  $success = false;
3293  }
3294  return $success;
3295  }
3296 
3305  public function releasePageGenerationLock(&$lockObj)
3306  {
3308  $success = false;
3309  // If lock object is set and was acquired (may also happen if no_cache was enabled during runtime), release it:
3310  if (is_object($lockObj) && $lockObj instanceof Locker && $lockObj->getLockStatus()) {
3311  $success = $lockObj->release();
3312  $lockObj->sysLog('Released lock');
3313  $lockObj = null;
3314  } elseif ($this->no_cache || $this->headerNoCache()) {
3315  $success = true;
3316  }
3317  return $success;
3318  }
3319 
3326  public function releaseLocks()
3327  {
3328  $this->releaseLock('pagesection');
3329  $this->releaseLock('pages');
3330  }
3331 
3339  public function addCacheTags(array $tags)
3340  {
3341  $this->pageCacheTags = array_merge($this->pageCacheTags, $tags);
3342  }
3343 
3344  /********************************************
3345  *
3346  * Page generation; rendering and inclusion
3347  *
3348  *******************************************/
3354  public function generatePage_preProcessing()
3355  {
3356  // Same codeline as in getFromCache(). But $this->all has been changed by
3357  // \TYPO3\CMS\Core\TypoScript\TemplateService::start() in the meantime, so this must be called again!
3358  $this->newHash = $this->getHash();
3359 
3360  // If the pages_lock is set, we are in charge of generating the page.
3361  if (is_object($this->locks['pages']['accessLock'])) {
3362  // Here we put some temporary stuff in the cache in order to let the first hit generate the page.
3363  // The temporary cache will expire after a few seconds (typ. 30) or will be cleared by the rendered page,
3364  // which will also clear and rewrite the cache.
3365  $this->tempPageCacheContent();
3366  }
3367  // At this point we have a valid pagesection_cache and also some temporary page_cache content,
3368  // so let all other processes proceed now. (They are blocked at the pagessection_lock in getFromCache())
3369  $this->releaseLock('pagesection');
3370 
3371  // Setting cache_timeout_default. May be overridden by PHP include scripts.
3372  $this->cacheTimeOutDefault = (int)$this->config['config']['cache_period'];
3373  // Page is generated
3374  $this->no_cacheBeforePageGen = $this->no_cache;
3375  }
3376 
3383  public function generatePage_whichScript()
3384  {
3385  if (!$this->TYPO3_CONF_VARS['FE']['noPHPscriptInclude'] && $this->config['config']['pageGenScript']) {
3386  return $this->tmpl->getFileName($this->config['config']['pageGenScript']);
3387  }
3388  return null;
3389  }
3390 
3398  {
3399  // This is to ensure, that the page is NOT cached if the no_cache parameter was set before the page was generated. This is a safety precaution, as it could have been unset by some script.
3400  if ($this->no_cacheBeforePageGen) {
3401  $this->set_no_cache('no_cache has been set before the page was generated - safety check', true);
3402  }
3403  // Hook for post-processing of page content cached/non-cached:
3404  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'])) {
3405  $_params = array('pObj' => &$this);
3406  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'] as $_funcRef) {
3407  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3408  }
3409  }
3410  // Processing if caching is enabled:
3411  if (!$this->no_cache) {
3412  // Hook for post-processing of page content before being cached:
3413  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached'])) {
3414  $_params = array('pObj' => &$this);
3415  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached'] as $_funcRef) {
3416  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3417  }
3418  }
3419  }
3420  // Convert char-set for output: (should be BEFORE indexing of the content (changed 22/4 2005)),
3421  // because otherwise indexed search might convert from the wrong charset!
3422  // One thing is that the charset mentioned in the HTML header would be wrong since the output charset (metaCharset)
3423  // has not been converted to from renderCharset. And indexed search will internally convert from metaCharset
3424  // to renderCharset so the content MUST be in metaCharset already!
3425  $this->content = $this->convOutputCharset($this->content, 'mainpage');
3426  // Hook for indexing pages
3427  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageIndexing'])) {
3428  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageIndexing'] as $_classRef) {
3429  $_procObj = GeneralUtility::getUserObj($_classRef);
3430  $_procObj->hook_indexContent($this);
3431  }
3432  }
3433  // Storing for cache:
3434  if (!$this->no_cache) {
3435  $this->realPageCacheContent();
3436  } elseif ($this->tempContent) {
3437  // If there happens to be temporary content in the cache and the cache was not cleared due to new content, put it in... ($this->no_cache=0)
3438  $this->clearPageCacheContent();
3439  $this->tempContent = false;
3440  }
3441  // Sets sys-last-change:
3442  $this->setSysLastChanged();
3443  }
3444 
3450  protected function regeneratePageTitle()
3451  {
3452  PageGenerator::generatePageTitle();
3453  }
3454 
3460  public function INTincScript()
3461  {
3462  // Deprecated stuff:
3463  // @deprecated: annotation added TYPO3 4.6
3464  $this->additionalHeaderData = is_array($this->config['INTincScript_ext']['additionalHeaderData']) ? $this->config['INTincScript_ext']['additionalHeaderData'] : array();
3465  $this->additionalFooterData = is_array($this->config['INTincScript_ext']['additionalFooterData']) ? $this->config['INTincScript_ext']['additionalFooterData'] : array();
3466  $this->additionalJavaScript = $this->config['INTincScript_ext']['additionalJavaScript'];
3467  $this->additionalCSS = $this->config['INTincScript_ext']['additionalCSS'];
3468  $this->divSection = '';
3469  if (empty($this->config['INTincScript_ext']['pageRenderer'])) {
3470  $this->initPageRenderer();
3471  } else {
3473  $pageRenderer = unserialize($this->config['INTincScript_ext']['pageRenderer']);
3474  $this->pageRenderer = $pageRenderer;
3476  }
3477 
3479  $this->getTimeTracker()->push('Substitute header section');
3480  $this->INTincScript_loadJSCode();
3481  $this->regeneratePageTitle();
3482 
3483  $this->content = str_replace(
3484  array(
3485  '<!--HD_' . $this->config['INTincScript_ext']['divKey'] . '-->',
3486  '<!--FD_' . $this->config['INTincScript_ext']['divKey'] . '-->',
3487  '<!--TDS_' . $this->config['INTincScript_ext']['divKey'] . '-->'
3488  ),
3489  array(
3490  $this->convOutputCharset(implode(LF, $this->additionalHeaderData), 'HD'),
3491  $this->convOutputCharset(implode(LF, $this->additionalFooterData), 'FD'),
3492  $this->convOutputCharset($this->divSection, 'TDS'),
3493  ),
3494  $this->pageRenderer->renderJavaScriptAndCssForProcessingOfUncachedContentObjects($this->content, $this->config['INTincScript_ext']['divKey'])
3495  );
3496  // Replace again, because header and footer data and page renderer replacements may introduce additional placeholders (see #44825)
3498  $this->setAbsRefPrefix();
3499  $this->getTimeTracker()->pull();
3500  }
3501 
3508  {
3509  do {
3510  $INTiS_config = $this->config['INTincScript'];
3511  $this->INTincScript_includeLibs($INTiS_config);
3512  $this->INTincScript_process($INTiS_config);
3513  // Check if there were new items added to INTincScript during the previous execution:
3514  $INTiS_config = array_diff_assoc($this->config['INTincScript'], $INTiS_config);
3515  $reprocess = count($INTiS_config) > 0;
3516  } while ($reprocess);
3517  }
3518 
3526  protected function INTincScript_includeLibs($INTiS_config)
3527  {
3528  foreach ($INTiS_config as $INTiS_cPart) {
3529  if (isset($INTiS_cPart['conf']['includeLibs']) && $INTiS_cPart['conf']['includeLibs']) {
3530  $INTiS_resourceList = GeneralUtility::trimExplode(',', $INTiS_cPart['conf']['includeLibs'], true);
3531  $this->includeLibraries($INTiS_resourceList);
3532  }
3533  }
3534  }
3535 
3543  protected function INTincScript_process($INTiS_config)
3544  {
3545  $timeTracker = $this->getTimeTracker();
3546  $timeTracker->push('Split content');
3547  // Splits content with the key.
3548  $INTiS_splitC = explode('<!--INT_SCRIPT.', $this->content);
3549  $this->content = '';
3550  $timeTracker->setTSlogMessage('Parts: ' . count($INTiS_splitC));
3551  $timeTracker->pull();
3552  foreach ($INTiS_splitC as $INTiS_c => $INTiS_cPart) {
3553  // If the split had a comment-end after 32 characters it's probably a split-string
3554  if (substr($INTiS_cPart, 32, 3) === '-->') {
3555  $INTiS_key = 'INT_SCRIPT.' . substr($INTiS_cPart, 0, 32);
3556  if (is_array($INTiS_config[$INTiS_key])) {
3557  $timeTracker->push('Include ' . $INTiS_config[$INTiS_key]['file'], '');
3558  $incContent = '';
3559  $INTiS_cObj = unserialize($INTiS_config[$INTiS_key]['cObj']);
3560  /* @var $INTiS_cObj ContentObjectRenderer */
3561  $INTiS_cObj->INT_include = 1;
3562  switch ($INTiS_config[$INTiS_key]['type']) {
3563  case 'COA':
3564  $incContent = $INTiS_cObj->cObjGetSingle('COA', $INTiS_config[$INTiS_key]['conf']);
3565  break;
3566  case 'FUNC':
3567  $incContent = $INTiS_cObj->cObjGetSingle('USER', $INTiS_config[$INTiS_key]['conf']);
3568  break;
3569  case 'POSTUSERFUNC':
3570  $incContent = $INTiS_cObj->callUserFunction($INTiS_config[$INTiS_key]['postUserFunc'], $INTiS_config[$INTiS_key]['conf'], $INTiS_config[$INTiS_key]['content']);
3571  break;
3572  }
3573  $this->content .= $this->convOutputCharset($incContent, 'INC-' . $INTiS_c);
3574  $this->content .= substr($INTiS_cPart, 35);
3575  $timeTracker->pull($incContent);
3576  } else {
3577  $this->content .= substr($INTiS_cPart, 35);
3578  }
3579  } else {
3580  $this->content .= ($INTiS_c ? '<!--INT_SCRIPT.' : '') . $INTiS_cPart;
3581  }
3582  }
3583  }
3584 
3590  public function INTincScript_loadJSCode()
3591  {
3592  // Add javascript
3593  $jsCode = trim($this->JSCode);
3594  $additionalJavaScript = is_array($this->additionalJavaScript)
3595  ? implode(LF, $this->additionalJavaScript)
3598  if ($jsCode !== '' || $additionalJavaScript !== '') {
3599  $this->additionalHeaderData['JSCode'] = '
3600 <script type="text/javascript">
3601  /*<![CDATA[*/
3602 <!--
3603 ' . $additionalJavaScript . '
3604 ' . $jsCode . '
3605 // -->
3606  /*]]>*/
3607 </script>';
3608  }
3609  // Add CSS
3610  $additionalCss = is_array($this->additionalCSS) ? implode(LF, $this->additionalCSS) : $this->additionalCSS;
3611  $additionalCss = trim($additionalCss);
3612  if ($additionalCss !== '') {
3613  $this->additionalHeaderData['_CSS'] = '
3614 <style type="text/css">
3615 ' . $additionalCss . '
3616 </style>';
3617  }
3618  }
3619 
3625  public function isINTincScript()
3626  {
3627  return is_array($this->config['INTincScript']) && empty($this->activeUrlHandlers);
3628  }
3629 
3636  public function doXHTML_cleaning()
3637  {
3639  return $this->config['config']['xhtml_cleaning'];
3640  }
3641 
3648  public function doLocalAnchorFix()
3649  {
3651  return isset($this->config['config']['prefixLocalAnchors']) ? $this->config['config']['prefixLocalAnchors'] : null;
3652  }
3653 
3654  /********************************************
3655  *
3656  * Finished off; outputting, storing session data, statistics...
3657  *
3658  *******************************************/
3665  public function isOutputting()
3666  {
3667  // Initialize by status if there is a Redirect URL
3668  $enableOutput = empty($this->activeUrlHandlers);
3669  // Call hook for possible disabling of output:
3670  if (isset($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['isOutputting']) && is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['isOutputting'])) {
3671  $_params = array('pObj' => &$this, 'enableOutput' => &$enableOutput);
3672  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['isOutputting'] as $_funcRef) {
3673  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3674  }
3675  }
3676  return $enableOutput;
3677  }
3678 
3688  public function processOutput()
3689  {
3690  // Set header for charset-encoding unless disabled
3691  if (empty($this->config['config']['disableCharsetHeader'])) {
3692  $headLine = 'Content-Type: ' . $this->contentType . '; charset=' . trim($this->metaCharset);
3693  header($headLine);
3694  }
3695  // Set header for content language unless disabled
3696  if (empty($this->config['config']['disableLanguageHeader']) && !empty($this->sys_language_isocode)) {
3697  $headLine = 'Content-Language: ' . trim($this->sys_language_isocode);
3698  header($headLine);
3699  }
3700  // Set cache related headers to client (used to enable proxy / client caching!)
3701  if (!empty($this->config['config']['sendCacheHeaders'])) {
3702  $this->sendCacheHeaders();
3703  }
3704  // Set headers, if any
3705  if (!empty($this->config['config']['additionalHeaders'])) {
3706  $headerArray = explode('|', $this->config['config']['additionalHeaders']);
3707  GeneralUtility::deprecationLog('The TypoScript option "config.additionalHeaders" has been deprecated with TYPO3 CMS 7, and will be removed with CMS 8, please use the more flexible syntax config.additionalHeaders.10... to separate each header value.');
3708  foreach ($headerArray as $headLine) {
3709  $headLine = trim($headLine);
3710  header($headLine);
3711  }
3712  }
3713  if (is_array($this->config['config']['additionalHeaders.'])) {
3714  ksort($this->config['config']['additionalHeaders.']);
3715  foreach ($this->config['config']['additionalHeaders.'] as $options) {
3716  header(
3717  trim($options['header']),
3718  // "replace existing headers" is turned on by default, unless turned off
3719  ($options['replace'] !== '0'),
3720  ((int)$options['httpResponseCode'] ?: null)
3721  );
3722  }
3723  }
3724  // Send appropriate status code in case of temporary content
3725  if ($this->tempContent) {
3726  $this->addTempContentHttpHeaders();
3727  }
3728  // Make substitution of eg. username/uid in content only if cache-headers for client/proxy caching is NOT sent!
3729  if (!$this->isClientCachable) {
3730  $this->contentStrReplace();
3731  }
3732  // Hook for post-processing of page content before output:
3733  if (isset($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-output']) && is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-output'])) {
3734  $_params = array('pObj' => &$this);
3735  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-output'] as $_funcRef) {
3736  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3737  }
3738  }
3739  // Send content-length header.
3740  // Notice that all HTML content outside the length of the content-length header will be cut off!
3741  // Therefore content of unknown length from included PHP-scripts and if admin users are logged
3742  // in (admin panel might show...) or if debug mode is turned on, we disable it!
3743  if (
3744  (!isset($this->config['config']['enableContentLengthHeader']) || $this->config['config']['enableContentLengthHeader'])
3745  && !$this->beUserLogin && !$this->TYPO3_CONF_VARS['FE']['debug']
3746  && !$this->config['config']['debug'] && !$this->doWorkspacePreview()
3747  ) {
3748  header('Content-Length: ' . strlen($this->content));
3749  }
3750  }
3751 
3759  public function sendCacheHeaders()
3760  {
3761  // Getting status whether we can send cache control headers for proxy caching:
3762  $doCache = $this->isStaticCacheble();
3763  // This variable will be TRUE unless cache headers are configured to be sent ONLY if a branch does not allow logins and logins turns out to be allowed anyway...
3764  $loginsDeniedCfg = empty($this->config['config']['sendCacheHeaders_onlyWhenLoginDeniedInBranch']) || empty($this->loginAllowedInBranch);
3765  // Finally, when backend users are logged in, do not send cache headers at all (Admin Panel might be displayed for instance).
3766  if ($doCache && !$this->beUserLogin && !$this->doWorkspacePreview() && $loginsDeniedCfg) {
3767  // Build headers:
3768  $headers = array(
3769  'Expires: ' . gmdate('D, d M Y H:i:s T', $this->cacheExpires),
3770  'ETag: "' . md5($this->content) . '"',
3771  'Cache-Control: max-age=' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']),
3772  // no-cache
3773  'Pragma: public'
3774  );
3775  $this->isClientCachable = true;
3776  } else {
3777  // Build headers:
3778  $headers = array(
3779  'Cache-Control: private'
3780  );
3781  $this->isClientCachable = false;
3782  // Now, if a backend user is logged in, tell him in the Admin Panel log what the caching status would have been:
3783  if ($this->beUserLogin) {
3784  if ($doCache) {
3785  $this->getTimeTracker()->setTSlogMessage('Cache-headers with max-age "' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']) . '" would have been sent');
3786  } else {
3787  $reasonMsg = '';
3788  $reasonMsg .= !$this->no_cache ? '' : 'Caching disabled (no_cache). ';
3789  $reasonMsg .= !$this->isINTincScript() ? '' : '*_INT object(s) on page. ';
3790  $reasonMsg .= !is_array($this->fe_user->user) ? '' : 'Frontend user logged in. ';
3791  $this->getTimeTracker()->setTSlogMessage('Cache-headers would disable proxy caching! Reason(s): "' . $reasonMsg . '"', 1);
3792  }
3793  }
3794  }
3795  // Send headers:
3796  foreach ($headers as $hL) {
3797  header($hL);
3798  }
3799  }
3800 
3811  public function isStaticCacheble()
3812  {
3813  $doCache = !$this->no_cache && !$this->isINTincScript() && !$this->isUserOrGroupSet();
3814  return $doCache;
3815  }
3816 
3822  public function contentStrReplace()
3823  {
3824  $search = array();
3825  $replace = array();
3826  // Substitutes username mark with the username
3827  if (!empty($this->fe_user->user['uid'])) {
3828  // User name:
3829  $token = isset($this->config['config']['USERNAME_substToken']) ? trim($this->config['config']['USERNAME_substToken']) : '';
3830  $search[] = $token ? $token : '<!--###USERNAME###-->';
3831  $replace[] = $this->fe_user->user['username'];
3832  // User uid (if configured):
3833  $token = isset($this->config['config']['USERUID_substToken']) ? trim($this->config['config']['USERUID_substToken']) : '';
3834  if ($token) {
3835  $search[] = $token;
3836  $replace[] = $this->fe_user->user['uid'];
3837  }
3838  }
3839  // Substitutes get_URL_ID in case of GET-fallback
3840  if ($this->getMethodUrlIdToken) {
3841  $search[] = $this->getMethodUrlIdToken;
3842  $replace[] = $this->fe_user->get_URL_ID;
3843  }
3844  // Hook for supplying custom search/replace data
3845  if (isset($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-contentStrReplace'])) {
3846  $contentStrReplaceHooks = &$this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-contentStrReplace'];
3847  if (is_array($contentStrReplaceHooks)) {
3848  $_params = array(
3849  'search' => &$search,
3850  'replace' => &$replace
3851  );
3852  foreach ($contentStrReplaceHooks as $_funcRef) {
3853  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3854  }
3855  }
3856  }
3857  if (!empty($search)) {
3858  $this->content = str_replace($search, $replace, $this->content);
3859  }
3860  }
3861 
3867  public function storeSessionData()
3868  {
3869  $this->fe_user->storeSessionData();
3870  }
3871 
3878  public function setParseTime()
3879  {
3880  // Compensates for the time consumed with Back end user initialization.
3881  $microtime_start = isset($GLOBALS['TYPO3_MISC']['microtime_start']) ? $GLOBALS['TYPO3_MISC']['microtime_start'] : null;
3882  $microtime_end = isset($GLOBALS['TYPO3_MISC']['microtime_end']) ? $GLOBALS['TYPO3_MISC']['microtime_end'] : null;
3883  $microtime_BE_USER_start = isset($GLOBALS['TYPO3_MISC']['microtime_BE_USER_start']) ? $GLOBALS['TYPO3_MISC']['microtime_BE_USER_start'] : null;
3884  $microtime_BE_USER_end = isset($GLOBALS['TYPO3_MISC']['microtime_BE_USER_end']) ? $GLOBALS['TYPO3_MISC']['microtime_BE_USER_end'] : null;
3885  $timeTracker = $this->getTimeTracker();
3886  $this->scriptParseTime = $timeTracker->getMilliseconds($microtime_end) - $timeTracker->getMilliseconds($microtime_start) - ($timeTracker->getMilliseconds($microtime_BE_USER_end) - $timeTracker->getMilliseconds($microtime_BE_USER_start));
3887  }
3888 
3894  public function previewInfo()
3895  {
3896  if ($this->fePreview !== 0) {
3897  $previewInfo = '';
3898  if (isset($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_previewInfo']) && is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_previewInfo'])) {
3899  $_params = array('pObj' => &$this);
3900  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_previewInfo'] as $_funcRef) {
3901  $previewInfo .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3902  }
3903  }
3904  $this->content = str_ireplace('</body>', $previewInfo . '</body>', $this->content);
3905  }
3906  }
3907 
3913  public function hook_eofe()
3914  {
3915  // Call hook for end-of-frontend processing:
3916  if (isset($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe']) && is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe'])) {
3917  $_params = array('pObj' => &$this);
3918  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe'] as $_funcRef) {
3919  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3920  }
3921  }
3922  }
3923 
3929  public function beLoginLinkIPList()
3930  {
3931  if (!empty($this->config['config']['beLoginLinkIPList'])) {
3932  if (GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $this->config['config']['beLoginLinkIPList'])) {
3933  $label = !$this->beUserLogin ? $this->config['config']['beLoginLinkIPList_login'] : $this->config['config']['beLoginLinkIPList_logout'];
3934  if ($label) {
3935  if (!$this->beUserLogin) {
3936  $link = '<a href="' . htmlspecialchars((TYPO3_mainDir . 'index.php?redirect_url=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI')))) . '">' . $label . '</a>';
3937  } else {
3938  $link = '<a href="' . htmlspecialchars((TYPO3_mainDir . 'index.php?L=OUT&redirect_url=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI')))) . '">' . $label . '</a>';
3939  }
3940  return $link;
3941  }
3942  }
3943  }
3944  return '';
3945  }
3946 
3952  public function addTempContentHttpHeaders()
3953  {
3954  header('HTTP/1.0 503 Service unavailable');
3955  header('Retry-after: 3600');
3956  header('Pragma: no-cache');
3957  header('Cache-control: no-cache');
3958  header('Expire: 0');
3959  }
3960 
3961  /********************************************
3962  *
3963  * Various internal API functions
3964  *
3965  *******************************************/
3976  public function encryptCharcode($n, $start, $end, $offset)
3977  {
3978  $n = $n + $offset;
3979  if ($offset > 0 && $n > $end) {
3980  $n = $start + ($n - $end - 1);
3981  } elseif ($offset < 0 && $n < $start) {
3982  $n = $end - ($start - $n - 1);
3983  }
3984  return chr($n);
3985  }
3986 
3994  public function encryptEmail($string, $back = false)
3995  {
3996  $out = '';
3997  if ($this->spamProtectEmailAddresses === 'ascii') {
3998  $stringLength = strlen($string);
3999  for ($a = 0; $a < $stringLength; $a++) {
4000  $out .= '&#' . ord(substr($string, $a, 1)) . ';';
4001  }
4002  } else {
4003  // like str_rot13() but with a variable offset and a wider character range
4004  $len = strlen($string);
4005  $offset = (int)$this->spamProtectEmailAddresses * ($back ? -1 : 1);
4006  for ($i = 0; $i < $len; $i++) {
4007  $charValue = ord($string[$i]);
4008  // 0-9 . , - + / :
4009  if ($charValue >= 43 && $charValue <= 58) {
4010  $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
4011  } elseif ($charValue >= 64 && $charValue <= 90) {
4012  // A-Z @
4013  $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
4014  } elseif ($charValue >= 97 && $charValue <= 122) {
4015  // a-z
4016  $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
4017  } else {
4018  $out .= $string[$i];
4019  }
4020  }
4021  }
4022  return $out;
4023  }
4024 
4032  public function checkFileInclude($incFile)
4033  {
4035  return !$this->TYPO3_CONF_VARS['FE']['noPHPscriptInclude'] || substr($incFile, 0, 4 + strlen(TYPO3_mainDir)) == TYPO3_mainDir . 'ext/' || substr($incFile, 0, 7 + strlen(TYPO3_mainDir)) == TYPO3_mainDir . 'sysext/' || substr($incFile, 0, 14) == 'typo3conf/ext/';
4036  }
4037 
4045  public function newCObj()
4046  {
4047  $this->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
4048  $this->cObj->start($this->page, 'pages');
4049  }
4050 
4058  public function setAbsRefPrefix()
4059  {
4060  if (!$this->absRefPrefix) {
4061  return;
4062  }
4063  $search = array(
4064  '"typo3temp/',
4065  '"typo3conf/ext/',
4066  '"' . TYPO3_mainDir . 'ext/',
4067  '"' . TYPO3_mainDir . 'sysext/'
4068  );
4069  $replace = array(
4070  '"' . $this->absRefPrefix . 'typo3temp/',
4071  '"' . $this->absRefPrefix . 'typo3conf/ext/',
4072  '"' . $this->absRefPrefix . TYPO3_mainDir . 'ext/',
4073  '"' . $this->absRefPrefix . TYPO3_mainDir . 'sysext/'
4074  );
4076  $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
4077  $storages = $storageRepository->findAll();
4078  foreach ($storages as $storage) {
4079  if ($storage->getDriverType() === 'Local' && $storage->isPublic() && $storage->isOnline()) {
4080  $folder = $storage->getPublicUrl($storage->getRootLevelFolder(), true);
4081  $search[] = '"' . $folder;
4082  $replace[] = '"' . $this->absRefPrefix . $folder;
4083  }
4084  }
4085  // Process additional directories
4086  $directories = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['additionalAbsRefPrefixDirectories'], true);
4087  foreach ($directories as $directory) {
4088  $search[] = '"' . $directory;
4089  $replace[] = '"' . $this->absRefPrefix . $directory;
4090  }
4091  $this->content = str_replace(
4092  $search,
4093  $replace,
4094  $this->content
4095  );
4096  }
4097 
4105  public function baseUrlWrap($url)
4106  {
4107  if ($this->baseUrl) {
4108  $urlParts = parse_url($url);
4109  if ($urlParts['scheme'] === '' && $url[0] !== '/') {
4110  $url = $this->baseUrl . $url;
4111  }
4112  }
4113  return $url;
4114  }
4115 
4125  public function logDeprecatedTyposcript($typoScriptProperty, $explanation = '')
4126  {
4127  $explanationText = $explanation !== '' ? ' - ' . $explanation : '';
4128  $this->getTimeTracker()->setTSlogMessage($typoScriptProperty . ' is deprecated.' . $explanationText, 2);
4129  GeneralUtility::deprecationLog('TypoScript ' . $typoScriptProperty . ' is deprecated' . $explanationText);
4130  }
4131 
4139  public function updateMD5paramsRecord($hash)
4140  {
4141  $this->getDatabaseConnection()->exec_UPDATEquery('cache_md5params', 'md5hash=' . $this->getDatabaseConnection()->fullQuoteStr($hash, 'cache_md5params'), array('tstamp' => $GLOBALS['EXEC_TIME']));
4142  }
4143 
4151  {
4153  if (!$this->beUserLogin) {
4154  if (!is_object($this->cObj)) {
4155  $this->newCObj();
4156  }
4157  $scriptPath = $this->cObj->getUrlToCurrentLocation();
4158  } else {
4159  // To break less existing sites, we allow the REQUEST_URI to be used for the prefix
4160  $scriptPath = GeneralUtility::getIndpEnv('REQUEST_URI');
4161  // Disable the cache so that these URI will not be the ones to be cached
4162  $this->disableCache();
4163  }
4164  $originalContent = $this->content;
4165  $this->content = preg_replace('/(<(?:a|area).*?href=")(#[^"]*")/i', '${1}' . htmlspecialchars($scriptPath) . '${2}', $originalContent);
4166  // There was an error in the call to preg_replace, so keep the original content (behavior prior to PHP 5.2)
4167  if (preg_last_error() > 0) {
4168  GeneralUtility::sysLog('preg_replace returned error-code: ' . preg_last_error() . ' in function prefixLocalAnchorsWithScript. Replacement not done!', 'cms', GeneralUtility::SYSLOG_SEVERITY_FATAL);
4169  $this->content = $originalContent;
4170  }
4171  }
4172 
4173  /********************************************
4174  * PUBLIC ACCESSIBLE WORKSPACES FUNCTIONS
4175  *******************************************/
4176 
4182  public function doWorkspacePreview()
4183  {
4184  return $this->workspacePreview !== 0;
4185  }
4186 
4193  public function whichWorkspace($returnTitle = false)
4194  {
4195  $ws = null;
4196  if ($this->doWorkspacePreview()) {
4197  $ws = (int)$this->workspacePreview;
4198  } elseif ($this->beUserLogin) {
4199  $ws = $this->getBackendUser()->workspace;
4200  }
4201  if ($ws && $returnTitle) {
4202  if (ExtensionManagementUtility::isLoaded('workspaces')) {
4203  $row = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('title', 'sys_workspace', 'uid=' . (int)$ws);
4204  if ($row) {
4205  return $row['title'];
4206  }
4207  }
4208  }
4209  return $ws;
4210  }
4211 
4219  public function includeLibraries(array $libraries)
4220  {
4221  $timeTracker = $this->getTimeTracker();
4222  $timeTracker->push('Include libraries');
4223  $timeTracker->setTSlogMessage('Files for inclusion: "' . implode(', ', $libraries) . '"');
4224  foreach ($libraries as $library) {
4225  $file = $this->tmpl->getFileName($library);
4226  if ($file) {
4227  include_once './' . $file;
4228  } else {
4229  $timeTracker->setTSlogMessage('Include file "' . $file . '" did not exist!', 2);
4230  }
4231  }
4232  $timeTracker->pull();
4233  }
4234 
4235  /********************************************
4236  *
4237  * Various external API functions - for use in plugins etc.
4238  *
4239  *******************************************/
4246  public function getStorageSiterootPids()
4247  {
4249  $res = array();
4250  if (!is_array($this->rootLine)) {
4251  return array();
4252  }
4253  foreach ($this->rootLine as $rC) {
4254  if (!$res['_STORAGE_PID']) {
4255  $res['_STORAGE_PID'] = (int)$rC['storage_pid'];
4256  }
4257  if (!$res['_SITEROOT']) {
4258  $res['_SITEROOT'] = $rC['is_siteroot'] ? (int)$rC['uid'] : 0;
4259  }
4260  }
4261  return $res;
4262  }
4263 
4269  public function getPagesTSconfig()
4270  {
4271  if (!is_array($this->pagesTSconfig)) {
4272  $TSdataArray = array();
4273  foreach ($this->rootLine as $k => $v) {
4274  $TSdataArray[] = $v['TSconfig'];
4275  }
4276  // Adding the default configuration:
4277  $TSdataArray[] = $this->TYPO3_CONF_VARS['BE']['defaultPageTSconfig'];
4278  // Bring everything in the right order. Default first, then the Rootline down to the current page
4279  $TSdataArray = array_reverse($TSdataArray);
4280  // Parsing the user TS (or getting from cache)
4281  $TSdataArray = TypoScriptParser::checkIncludeLines_array($TSdataArray);
4282  $userTS = implode(LF . '[GLOBAL]' . LF, $TSdataArray);
4283  $hash = md5('pageTS:' . $userTS);
4284  $cachedContent = $this->sys_page->getHash($hash);
4285  if (is_array($cachedContent)) {
4286  $this->pagesTSconfig = $cachedContent;
4287  } else {
4288  $parseObj = GeneralUtility::makeInstance(TypoScriptParser::class);
4289  $parseObj->parse($userTS);
4290  $this->pagesTSconfig = $parseObj->setup;
4291  $this->sys_page->storeHash($hash, $this->pagesTSconfig, 'PAGES_TSconfig');
4292  }
4293  }
4294  return $this->pagesTSconfig;
4295  }
4296 
4305  public function setJS($key, $content = '')
4306  {
4307  if ($key) {
4308  switch ($key) {
4309  case 'mouseOver':
4310  $this->additionalJavaScript[$key] = ' // JS function for mouse-over
4311  function over(name, imgObj) { //
4312  if (version == "n3" && document[name]) {document[name].src = eval(name+"_h.src");}
4313  else if (document.getElementById && document.getElementById(name)) {document.getElementById(name).src = eval(name+"_h.src");}
4314  else if (imgObj) {imgObj.src = eval(name+"_h.src");}
4315  }
4316  // JS function for mouse-out
4317  function out(name, imgObj) { //
4318  if (version == "n3" && document[name]) {document[name].src = eval(name+"_n.src");}
4319  else if (document.getElementById && document.getElementById(name)) {document.getElementById(name).src = eval(name+"_n.src");}
4320  else if (imgObj) {imgObj.src = eval(name+"_n.src");}
4321  }';
4322  break;
4323  case 'openPic':
4324  $this->additionalJavaScript[$key] = ' function openPic(url, winName, winParams) { //
4325  var theWindow = window.open(url, winName, winParams);
4326  if (theWindow) {theWindow.focus();}
4327  }';
4328  break;
4329  default:
4330  $this->additionalJavaScript[$key] = $content;
4331  }
4332  }
4333  }
4334 
4343  public function setCSS($key, $content)
4344  {
4345  if ($key) {
4346  $this->additionalCSS[$key] = $content;
4347  }
4348  }
4349 
4357  public function uniqueHash($str = '')
4358  {
4359  return md5($this->uniqueString . '_' . $str . $this->uniqueCounter++);
4360  }
4361 
4369  public function set_no_cache($reason = '', $internal = false)
4370  {
4371  if ($internal && isset($GLOBALS['BE_USER'])) {
4373  } else {
4375  }
4376 
4377  if ($reason !== '') {
4378  $warning = '$TSFE->set_no_cache() was triggered. Reason: ' . $reason . '.';
4379  } else {
4380  $trace = debug_backtrace();
4381  // This is a hack to work around ___FILE___ resolving symbolic links
4382  $PATH_site_real = dirname(realpath(PATH_site . 'typo3')) . '/';
4383  $file = $trace[0]['file'];
4384  if (StringUtility::beginsWith($file, $PATH_site_real)) {
4385  $file = str_replace($PATH_site_real, '', $file);
4386  } else {
4387  $file = str_replace(PATH_site, '', $file);
4388  }
4389  $line = $trace[0]['line'];
4390  $trigger = $file . ' on line ' . $line;
4391  $warning = '$GLOBALS[\'TSFE\']->set_no_cache() was triggered by ' . $trigger . '.';
4392  }
4393  if ($this->TYPO3_CONF_VARS['FE']['disableNoCacheParameter']) {
4394  $warning .= ' However, $TYPO3_CONF_VARS[\'FE\'][\'disableNoCacheParameter\'] is set, so it will be ignored!';
4395  $this->getTimeTracker()->setTSlogMessage($warning, 2);
4396  } else {
4397  $warning .= ' Caching is disabled!';
4398  $this->disableCache();
4399  }
4400  GeneralUtility::sysLog($warning, 'cms', $severity);
4401  }
4402 
4409  protected function disableCache()
4410  {
4411  $this->no_cache = true;
4412  }
4413 
4420  public function set_cache_timeout_default($seconds)
4421  {
4422  $this->cacheTimeOutDefault = (int)$seconds;
4423  }
4424 
4430  public function get_cache_timeout()
4431  {
4433  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
4434  $cachedCacheLifetimeIdentifier = 'core-tslib_fe-get_cache_timeout';
4435  $cachedCacheLifetime = $runtimeCache->get($cachedCacheLifetimeIdentifier);
4436  if ($cachedCacheLifetime === false) {
4437  if ($this->page['cache_timeout']) {
4438  // Cache period was set for the page:
4439  $cacheTimeout = $this->page['cache_timeout'];
4440  } elseif ($this->cacheTimeOutDefault) {
4441  // Cache period was set for the whole site:
4442  $cacheTimeout = $this->cacheTimeOutDefault;
4443  } else {
4444  // No cache period set at all, so we take one day (60*60*24 seconds = 86400 seconds):
4445  $cacheTimeout = 86400;
4446  }
4447  if ($this->config['config']['cache_clearAtMidnight']) {
4448  $timeOutTime = $GLOBALS['EXEC_TIME'] + $cacheTimeout;
4449  $midnightTime = mktime(0, 0, 0, date('m', $timeOutTime), date('d', $timeOutTime), date('Y', $timeOutTime));
4450  // If the midnight time of the expire-day is greater than the current time,
4451  // we may set the timeOutTime to the new midnighttime.
4452  if ($midnightTime > $GLOBALS['EXEC_TIME']) {
4453  $cacheTimeout = $midnightTime - $GLOBALS['EXEC_TIME'];
4454  }
4455  }
4456 
4457  // Calculate the timeout time for records on the page and adjust cache timeout if necessary
4458  $cacheTimeout = min($this->calculatePageCacheTimeout(), $cacheTimeout);
4459 
4460  if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['get_cache_timeout'])) {
4461  foreach ($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['get_cache_timeout'] as $_funcRef) {
4462  $params = array('cacheTimeout' => $cacheTimeout);
4463  $cacheTimeout = GeneralUtility::callUserFunction($_funcRef, $params, $this);
4464  }
4465  }
4466  $runtimeCache->set($cachedCacheLifetimeIdentifier, $cacheTimeout);
4467  $cachedCacheLifetime = $cacheTimeout;
4468  }
4469  return $cachedCacheLifetime;
4470  }
4471 
4478  public function getUniqueId($desired = '')
4479  {
4480  if ($desired === '') {
4481  // id has to start with a letter to reach XHTML compliance
4482  $uniqueId = 'a' . $this->uniqueHash();
4483  } else {
4484  $uniqueId = $desired;
4485  for ($i = 1; isset($this->usedUniqueIds[$uniqueId]); $i++) {
4486  $uniqueId = $desired . '_' . $i;
4487  }
4488  }
4489  $this->usedUniqueIds[$uniqueId] = true;
4490  return $uniqueId;
4491  }
4492 
4493  /*********************************************
4494  *
4495  * Localization and character set conversion
4496  *
4497  *********************************************/
4504  public function sL($input)
4505  {
4506  if (substr($input, 0, 4) !== 'LLL:') {
4507  // Not a label, return the key as this
4508  return $input;
4509  }
4510  // If cached label
4511  if (!isset($this->LL_labels_cache[$this->lang][$input])) {
4512  $restStr = trim(substr($input, 4));
4513  $extPrfx = '';
4514  if (substr($restStr, 0, 4) === 'EXT:') {
4515  $restStr = trim(substr($restStr, 4));
4516  $extPrfx = 'EXT:';
4517  }
4518  $parts = explode(':', $restStr);
4519  $parts[0] = $extPrfx . $parts[0];
4520  // Getting data if not cached
4521  if (!isset($this->LL_files_cache[$parts[0]])) {
4522  $this->LL_files_cache[$parts[0]] = $this->readLLfile($parts[0]);
4523  }
4524  $this->LL_labels_cache[$this->lang][$input] = $this->getLLL($parts[1], $this->LL_files_cache[$parts[0]]);
4525  }
4526  return $this->LL_labels_cache[$this->lang][$input];
4527  }
4528 
4535  public function readLLfile($fileRef)
4536  {
4538  $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
4539 
4540  if ($this->lang !== 'default') {
4541  $languages = array_reverse($this->languageDependencies);
4542  // At least we need to have English
4543  if (empty($languages)) {
4544  $languages[] = 'default';
4545  }
4546  } else {
4547  $languages = array('default');
4548  }
4549 
4550  $localLanguage = array();
4551  foreach ($languages as $language) {
4552  $tempLL = $languageFactory->getParsedData($fileRef, $language, $this->renderCharset);
4553  $localLanguage['default'] = $tempLL['default'];
4554  if (!isset($localLanguage[$this->lang])) {
4555  $localLanguage[$this->lang] = $localLanguage['default'];
4556  }
4557  if ($this->lang !== 'default' && isset($tempLL[$language])) {
4558  // Merge current language labels onto labels from previous language
4559  // This way we have a label with fall back applied
4560  ArrayUtility::mergeRecursiveWithOverrule($localLanguage[$this->lang], $tempLL[$language], true, false);
4561  }
4562  }
4563 
4564  return $localLanguage;
4565  }
4566 
4574  public function getLLL($index, $LOCAL_LANG)
4575  {
4576  if (isset($LOCAL_LANG[$this->lang][$index][0]['target'])) {
4577  return $LOCAL_LANG[$this->lang][$index][0]['target'];
4578  } elseif (isset($LOCAL_LANG['default'][$index][0]['target'])) {
4579  return $LOCAL_LANG['default'][$index][0]['target'];
4580  }
4581  return false;
4582  }
4583 
4589  public function initLLvars()
4590  {
4591  // Init languageDependencies list
4592  $this->languageDependencies = array();
4593  // Setting language key and split index:
4594  $this->lang = $this->config['config']['language'] ?: 'default';
4595  $this->pageRenderer->setLanguage($this->lang);
4596 
4597  // Finding the requested language in this list based
4598  // on the $lang key being inputted to this function.
4600  $locales = GeneralUtility::makeInstance(Locales::class);
4601  $locales->initialize();
4602 
4603  // Language is found. Configure it:
4604  if (in_array($this->lang, $locales->getLocales())) {
4605  $this->languageDependencies[] = $this->lang;
4606  foreach ($locales->getLocaleDependencies($this->lang) as $language) {
4607  $this->languageDependencies[] = $language;
4608  }
4609  }
4610 
4611  // Setting charsets:
4612  $this->renderCharset = $this->csConvObj->parse_charset($this->config['config']['renderCharset'] ? $this->config['config']['renderCharset'] : 'utf-8');
4613  // Rendering charset of HTML page.
4614  $this->metaCharset = $this->csConvObj->parse_charset($this->config['config']['metaCharset'] ? $this->config['config']['metaCharset'] : $this->renderCharset);
4615  }
4616 
4628  public function csConv($str, $from = '')
4629  {
4630  if ($from) {
4631  $output = $this->csConvObj->conv($str, $this->csConvObj->parse_charset($from), $this->renderCharset, 1);
4632  return $output ?: $str;
4633  } else {
4634  return $str;
4635  }
4636  }
4637 
4644  public function convOutputCharset($content)
4645  {
4646  if ($this->renderCharset != $this->metaCharset) {
4647  $content = $this->csConvObj->conv($content, $this->renderCharset, $this->metaCharset, true);
4648  }
4649  return $content;
4650  }
4651 
4657  public function convPOSTCharset()
4658  {
4659  if ($this->renderCharset != $this->metaCharset && is_array($_POST) && !empty($_POST)) {
4660  $this->csConvObj->convArray($_POST, $this->metaCharset, $this->renderCharset);
4661  $GLOBALS['HTTP_POST_VARS'] = $_POST;
4662  }
4663  }
4664 
4670  protected function calculatePageCacheTimeout()
4671  {
4672  $result = PHP_INT_MAX;
4673  // Get the configuration
4674  $tablesToConsider = $this->getCurrentPageCacheConfiguration();
4675  // Get the time, rounded to the minute (do not polute MySQL cache!)
4676  // It is ok that we do not take seconds into account here because this
4677  // value will be substracted later. So we never get the time "before"
4678  // the cache change.
4679  $now = $GLOBALS['ACCESS_TIME'];
4680  // Find timeout by checking every table
4681  foreach ($tablesToConsider as $tableDef) {
4682  $result = min($result, $this->getFirstTimeValueForRecord($tableDef, $now));
4683  }
4684  // We return + 1 second just to ensure that cache is definitely regenerated
4685  return $result == PHP_INT_MAX ? PHP_INT_MAX : $result - $now + 1;
4686  }
4687 
4703  {
4704  $result = array('tt_content:' . $this->id);
4705  if (isset($this->config['config']['cache.'][$this->id])) {
4706  $result = array_merge($result, GeneralUtility::trimExplode(',', $this->config['config']['cache.'][$this->id]));
4707  }
4708  if (isset($this->config['config']['cache.']['all'])) {
4709  $result = array_merge($result, GeneralUtility::trimExplode(',', $this->config['config']['cache.']['all']));
4710  }
4711  return array_unique($result);
4712  }
4713 
4723  protected function getFirstTimeValueForRecord($tableDef, $now)
4724  {
4725  $result = PHP_INT_MAX;
4726  list($tableName, $pid) = GeneralUtility::trimExplode(':', $tableDef);
4727  if (empty($tableName) || empty($pid)) {
4728  throw new \InvalidArgumentException('Unexpected value for parameter $tableDef. Expected <tablename>:<pid>, got \'' . htmlspecialchars($tableDef) . '\'.', 1307190365);
4729  }
4730  // Additional fields
4731  $showHidden = $tableName === 'pages' ? $this->showHiddenPage : $this->showHiddenRecords;
4732  $enableFields = $this->sys_page->enableFields(
4733  $tableName,
4734  $showHidden,
4735  array('starttime' => true, 'endtime' => true)
4736  );
4737 
4738  $timeFields = array();
4739  $selectFields = array();
4740  $whereConditions = array();
4741  foreach (array('starttime', 'endtime') as $field) {
4742  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field])) {
4743  $timeFields[$field] = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field];
4744  $selectFields[$field]
4745  = 'MIN('
4746  . 'CASE WHEN ' . $timeFields[$field] . ' <= ' . $now
4747  . ' THEN NULL ELSE ' . $timeFields[$field] . ' END'
4748  . ') AS ' . $field;
4749  $whereConditions[$field] = $timeFields[$field] . '>' . $now;
4750  }
4751  }
4752 
4753  // if starttime or endtime are defined, evaluate them
4754  if (!empty($timeFields)) {
4755  // find the timestamp, when the current page's content changes the next time
4756  $row = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
4757  implode(', ', $selectFields),
4758  $tableName,
4759  'pid=' . (int)$pid
4760  . ' AND (' . implode(' OR ', $whereConditions) . ')'
4761  . $enableFields
4762  );
4763  if ($row) {
4764  foreach ($timeFields as $timeField => $_) {
4765  // if a MIN value is found, take it into account for the
4766  // cache lifetime we have to filter out start/endtimes < $now,
4767  // as the SQL query also returns rows with starttime < $now
4768  // and endtime > $now (and using a starttime from the past
4769  // would be wrong)
4770  if ($row[$timeField] !== null && (int)$row[$timeField] > $now) {
4771  $result = min($result, (int)$row[$timeField]);
4772  }
4773  }
4774  }
4775  }
4776 
4777  return $result;
4778  }
4779 
4780 
4786  protected function getSysDomainCache()
4787  {
4788  $entryIdentifier = 'core-database-sys_domain-complete';
4790  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
4791 
4792  $sysDomainData = array();
4793  if ($runtimeCache->has($entryIdentifier)) {
4794  $sysDomainData = $runtimeCache->get($entryIdentifier);
4795  } else {
4796  $domainRecords = $this->getDatabaseConnection()->exec_SELECTgetRows(
4797  'uid, pid, domainName, forced',
4798  'sys_domain',
4799  'redirectTo=\'\' ' . $this->sys_page->enableFields('sys_domain', 0),
4800  '',
4801  'sorting ASC'
4802  );
4803 
4804  foreach ($domainRecords as $row) {
4805  // if there is already an entry for this pid, check if we should overwrite it
4806  if (isset($sysDomainData[$row['pid']])) {
4807  // There is already a "forced" entry, which must not be overwritten
4808  if ($sysDomainData[$row['pid']]['forced']) {
4809  continue;
4810  }
4811 
4812  // The current domain record is also NOT-forced, keep the old unless the new one matches the current request
4813  if (!$row['forced'] && !$this->domainNameMatchesCurrentRequest($row['domainName'])) {
4814  continue;
4815  }
4816  }
4817 
4818  // as we passed all previous checks, we save this domain for the current pid
4819  $sysDomainData[$row['pid']] = array(
4820  'uid' => $row['uid'],
4821  'pid' => $row['pid'],
4822  'domainName' => rtrim($row['domainName'], '/'),
4823  'forced' => $row['forced'],
4824  );
4825  }
4826  $runtimeCache->set($entryIdentifier, $sysDomainData);
4827  }
4828  return $sysDomainData;
4829  }
4830 
4838  public function domainNameMatchesCurrentRequest($domainName)
4839  {
4840  $currentDomain = GeneralUtility::getIndpEnv('HTTP_HOST');
4841  $currentPathSegment = trim(preg_replace('|/[^/]*$|', '', GeneralUtility::getIndpEnv('SCRIPT_NAME')));
4842  return $currentDomain === $domainName || $currentDomain . $currentPathSegment === $domainName;
4843  }
4844 
4853  public function getDomainDataForPid($targetPid)
4854  {
4855  // Using array_key_exists() here, nice $result can be NULL
4856  // (happens, if there's no domain records defined)
4857  if (!array_key_exists($targetPid, $this->domainDataCache)) {
4858  $result = null;
4859  $sysDomainData = $this->getSysDomainCache();
4860  $rootline = $this->sys_page->getRootLine($targetPid);
4861  // walk the rootline downwards from the target page
4862  // to the root page, until a domain record is found
4863  foreach ($rootline as $pageInRootline) {
4864  $pidInRootline = $pageInRootline['uid'];
4865  if (isset($sysDomainData[$pidInRootline])) {
4866  $result = $sysDomainData[$pidInRootline];
4867  break;
4868  }
4869  }
4870  $this->domainDataCache[$targetPid] = $result;
4871  }
4872 
4873  return $this->domainDataCache[$targetPid];
4874  }
4875 
4883  public function getDomainNameForPid($targetPid)
4884  {
4885  $domainData = $this->getDomainDataForPid($targetPid);
4886  return $domainData ? $domainData['domainName'] : null;
4887  }
4888 
4895  public function getRequestedId()
4896  {
4897  return $this->requestedId ?: $this->id;
4898  }
4899 
4909  protected function acquireLock($type, $key)
4910  {
4911  $lockFactory = GeneralUtility::makeInstance(LockFactory::class);
4912  $this->locks[$type]['accessLock'] = $lockFactory->createLocker($type);
4913 
4914  $this->locks[$type]['pageLock'] = $lockFactory->createLocker(
4915  $key,
4917  );
4918 
4919  do {
4920  if (!$this->locks[$type]['accessLock']->acquire()) {
4921  throw new \RuntimeException('Could not acquire access lock for "' . $type . '"".', 1294586098);
4922  }
4923 
4924  try {
4925  $locked = $this->locks[$type]['pageLock']->acquire(
4927  );
4928  } catch (LockAcquireWouldBlockException $e) {
4929  // somebody else has the lock, we keep waiting
4930 
4931  // first release the access lock
4932  $this->locks[$type]['accessLock']->release();
4933  // now lets make a short break (100ms) until we try again, since
4934  // the page generation by the lock owner will take a while anyways
4935  usleep(100000);
4936  continue;
4937  }
4938  $this->locks[$type]['accessLock']->release();
4939  if ($locked) {
4940  break;
4941  } else {
4942  throw new \RuntimeException('Could not acquire page lock for ' . $key . '.', 1294586098);
4943  }
4944  } while (true);
4945  }
4946 
4955  protected function releaseLock($type)
4956  {
4957  if ($this->locks[$type]['accessLock']) {
4958  if (!$this->locks[$type]['accessLock']->acquire()) {
4959  throw new \RuntimeException('Could not acquire access lock for "' . $type . '"".', 1294586098);
4960  }
4961 
4962  $this->locks[$type]['pageLock']->release();
4963  $this->locks[$type]['pageLock']->destroy();
4964  $this->locks[$type]['pageLock'] = null;
4965 
4966  $this->locks[$type]['accessLock']->release();
4967  $this->locks[$type]['accessLock'] = null;
4968  }
4969  }
4970 
4976  protected function getBackendUser()
4977  {
4978  return $GLOBALS['BE_USER'];
4979  }
4980 
4986  protected function getDatabaseConnection()
4987  {
4988  return $GLOBALS['TYPO3_DB'];
4989  }
4990 
4994  protected function getTimeTracker()
4995  {
4996  return $GLOBALS['TT'];
4997  }
4998 
5004  protected function getDocumentTemplate()
5005  {
5006  return $GLOBALS['TBE_TEMPLATE'];
5007  }
5008 }