TYPO3 CMS  TYPO3_8-7
TypoScriptFrontendController.php
Go to the documentation of this file.
1 <?php
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 
55 
72 {
77  public $id = '';
78 
83  public $type = '';
84 
89  public $cHash = '';
90 
96  public $no_cache = false;
97 
102  public $rootLine = '';
103 
108  public $page = '';
109 
115  public $contentPid = 0;
116 
124  protected $originalMountPointPage = null;
125 
134  protected $originalShortcutPage = null;
135 
141  public $sys_page = '';
142 
151  protected $activeUrlHandlers = [];
152 
157  public $pageNotFound = 0;
158 
163  public $domainStartPage = 0;
164 
170 
174  public $MP = '';
175 
179  public $RDCT = '';
180 
187  public $page_cache_reg1 = 0;
188 
195  public $siteScript = '';
196 
202  public $fe_user = '';
203 
210  public $loginUser = false;
211 
218  public $gr_list = '';
219 
224  public $beUserLogin = false;
225 
230  public $workspacePreview = 0;
231 
236  public $loginAllowedInBranch = true;
237 
243 
249 
257  public $fePreview = 0;
258 
264  public $showHiddenPage = false;
265 
272  public $showHiddenRecords = false;
273 
278  public $simUserGroup = 0;
279 
285  public $config = [];
286 
292  public $tmpl = null;
293 
299  public $cacheTimeOutDefault = false;
300 
306  public $cacheContentFlag = false;
307 
312  public $cacheExpires = 0;
313 
318  public $isClientCachable = false;
319 
326  public $all = [];
327 
332  public $sPre = '';
333 
339  public $pSetup = '';
340 
346  public $newHash = '';
347 
355  public $getMethodUrlIdToken = '';
356 
364  public $no_cacheBeforePageGen = false;
365 
372  public $tempContent = false;
373 
378  public $forceTemplateParsing = false;
379 
384  public $cHash_array = [];
385 
390  public $pagesTSconfig = '';
391 
410 
416 
424 
430  public $additionalCSS = [];
431 
435  public $JSCode;
436 
440  public $inlineJS;
441 
446  public $divSection = '';
447 
452  public $debug = false;
453 
458  public $intTarget = '';
459 
464  public $extTarget = '';
465 
470  public $fileTarget = '';
471 
477  public $MP_defaults = [];
478 
484 
489  public $absRefPrefix = '';
490 
497 
502  public $lockFilePath = '';
503 
508  public $ATagParams = '';
509 
516  public $sWordRegEx = '';
517 
523  public $sWordList = '';
524 
531  public $linkVars = '';
532 
540  public $excludeCHashVars = '';
541 
547  public $displayEditIcons = '';
548 
556 
564  public $sys_language_uid = 0;
565 
570  public $sys_language_mode = '';
571 
578 
589 
596 
602  public $applicationData = [];
603 
607  public $register = [];
608 
614  public $registerStack = [];
615 
621  public $cObjectDepthCounter = 50;
622 
628  public $recordRegister = [];
629 
637  public $currentRecord = '';
638 
644  public $accessKey = [];
645 
651  public $imagesOnPage = [];
652 
658  public $lastImageInfo = [];
659 
665  public $uniqueCounter = 0;
666 
670  public $uniqueString = '';
671 
677  public $indexedDocTitle = '';
678 
684  public $altPageTitle = '';
685 
690  public $baseUrl = '';
691 
696  private $usedUniqueIds = [];
697 
703  public $cObj = '';
704 
709  public $content = '';
710 
715  public $scriptParseTime = 0;
716 
724  public $csConvObj;
725 
731  public $defaultCharSet = 'utf-8';
732 
738  public $renderCharset = 'utf-8';
739 
746  public $metaCharset = 'utf-8';
747 
752  public $lang = '';
753 
757  public $LL_labels_cache = [];
758 
762  public $LL_files_cache = [];
763 
771  protected $languageDependencies = [];
772 
776  protected $locks = [];
777 
781  protected $pageRenderer = null;
782 
789  protected $pageCache;
790 
794  protected $pageCacheTags = [];
795 
801  protected $cacheHash;
802 
808  protected $domainDataCache = [];
809 
817  protected $contentType = 'text/html';
818 
824  public $xhtmlDoctype = '';
825 
830 
836  protected $requestedId;
837 
842 
859  public function __construct($_ = null, $id, $type, $no_cache = '', $cHash = '', $_2 = null, $MP = '', $RDCT = '')
860  {
861  // Setting some variables:
862  $this->id = $id;
863  $this->type = $type;
864  if ($no_cache) {
865  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter']) {
866  $warning = '&no_cache=1 has been ignored because $TYPO3_CONF_VARS[\'FE\'][\'disableNoCacheParameter\'] is set!';
867  $this->getTimeTracker()->setTSlogMessage($warning, 2);
868  } else {
869  $warning = '&no_cache=1 has been supplied, so caching is disabled! URL: "' . GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL') . '"';
870  $this->disableCache();
871  }
872  GeneralUtility::sysLog($warning, 'cms', GeneralUtility::SYSLOG_SEVERITY_WARNING);
873  }
874  $this->cHash = $cHash;
875  $this->MP = $GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids'] ? (string)$MP : '';
876  $this->RDCT = $RDCT;
877  $this->uniqueString = md5(microtime());
878  $this->csConvObj = GeneralUtility::makeInstance(CharsetConverter::class);
879  $this->initPageRenderer();
880  // Call post processing function for constructor:
881  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-PostProc'])) {
882  $_params = ['pObj' => &$this];
883  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-PostProc'] as $_funcRef) {
884  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
885  }
886  }
887  $this->cacheHash = GeneralUtility::makeInstance(CacheHashCalculator::class);
888  $this->initCaches();
889  }
890 
894  protected function initPageRenderer()
895  {
896  if ($this->pageRenderer !== null) {
897  return;
898  }
899  $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
900  $this->pageRenderer->setTemplateFile('EXT:frontend/Resources/Private/Templates/MainPage.html');
901  }
902 
907  public function setContentType($contentType)
908  {
909  $this->contentType = $contentType;
910  }
911 
919  public function connectToDB()
920  {
921  try {
922  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');
923  $connection->connect();
924  } catch (DBALException $exception) {
925  // Cannot connect to current database
926  $message = sprintf(
927  'Cannot connect to the configured database. Connection failed with: "%s"',
928  $exception->getMessage()
929  );
930  if ($this->checkPageUnavailableHandler()) {
931  $this->pageUnavailableAndExit($message);
932  } else {
933  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
934  throw new ServiceUnavailableException($message, 1301648782);
935  }
936  }
937  // Call post processing function for DB connection:
938  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['connectToDB'])) {
939  $_params = ['pObj' => &$this];
940  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['connectToDB'] as $_funcRef) {
941  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
942  }
943  }
944  }
945 
952  public function sendRedirect()
953  {
954  if ($this->RDCT) {
955  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
956  ->getQueryBuilderForTable('cache_md5params');
957 
958  $row = $queryBuilder
959  ->select('params')
960  ->from('cache_md5params')
961  ->where(
962  $queryBuilder->expr()->eq(
963  'md5hash',
964  $queryBuilder->createNamedParameter($this->RDCT, \PDO::PARAM_STR)
965  )
966  )
967  ->execute()
968  ->fetch();
969 
970  if ($row) {
971  $this->updateMD5paramsRecord($this->RDCT);
972  header('Location: ' . $row['params']);
973  die;
974  }
975  }
976  }
977 
978  /********************************************
979  *
980  * Initializing, resolving page id
981  *
982  ********************************************/
986  protected function initCaches()
987  {
988  $this->pageCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_pages');
989  }
990 
994  public function initFEuser()
995  {
996  $this->fe_user = GeneralUtility::makeInstance(FrontendUserAuthentication::class);
997  // List of pid's acceptable
998  $pid = GeneralUtility::_GP('pid');
999  $this->fe_user->checkPid_value = $pid ? implode(',', GeneralUtility::intExplode(',', $pid)) : 0;
1000  // Check if a session is transferred:
1001  if (GeneralUtility::_GP('FE_SESSION_KEY')) {
1002  $fe_sParts = explode('-', GeneralUtility::_GP('FE_SESSION_KEY'));
1003  // If the session key hash check is OK:
1004  if (md5(($fe_sParts[0] . '/' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) === (string)$fe_sParts[1]) {
1006  $_COOKIE[$cookieName] = $fe_sParts[0];
1007  if (isset($_SERVER['HTTP_COOKIE'])) {
1008  // See http://forge.typo3.org/issues/27740
1009  $_SERVER['HTTP_COOKIE'] .= ';' . $cookieName . '=' . $fe_sParts[0];
1010  }
1011  $this->fe_user->forceSetCookie = 1;
1012  $this->fe_user->dontSetCookie = false;
1013  unset($cookieName);
1014  }
1015  }
1016  $this->fe_user->start();
1017  $this->fe_user->unpack_uc();
1018 
1019  // @deprecated since TYPO3 v8, will be removed in TYPO3 v9
1020  // @todo: With the removal of that in v9, TYPO3_CONF_VARS maxSessionDataSize can be removed as well,
1021  // @todo: and a silent ugrade wizard to remove the setting from LocalConfiguration should be added.
1022  if (!empty($GLOBALS['TYPO3_CONF_VARS']['FE']['enableRecordRegistration'])) {
1023  $recs = GeneralUtility::_GP('recs');
1024  if (is_array($recs)) {
1025  // If any record registration is submitted, register the record.
1026  $this->fe_user->record_registration($recs, $GLOBALS['TYPO3_CONF_VARS']['FE']['maxSessionDataSize']);
1027  }
1028  }
1029 
1030  // Call hook for possible manipulation of frontend user object
1031  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['initFEuser'])) {
1032  $_params = ['pObj' => &$this];
1033  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['initFEuser'] as $_funcRef) {
1034  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1035  }
1036  }
1037  // For every 60 seconds the is_online timestamp is updated.
1038  if (is_array($this->fe_user->user) && $this->fe_user->user['uid'] && $this->fe_user->user['is_online'] < $GLOBALS['EXEC_TIME'] - 60) {
1039  $dbConnection = GeneralUtility::makeInstance(ConnectionPool::class)
1040  ->getConnectionForTable('fe_users');
1041  $dbConnection->update(
1042  'fe_users',
1043  [
1044  'is_online' => $GLOBALS['EXEC_TIME']
1045  ],
1046  [
1047  'uid' => (int)$this->fe_user->user['uid']
1048  ]
1049  );
1050  }
1051  }
1052 
1057  public function initUserGroups()
1058  {
1059  // This affects the hidden-flag selecting the fe_groups for the user!
1060  $this->fe_user->showHiddenRecords = $this->showHiddenRecords;
1061  // no matter if we have an active user we try to fetch matching groups which can be set without an user (simulation for instance!)
1062  $this->fe_user->fetchGroupData();
1063  if (is_array($this->fe_user->user) && !empty($this->fe_user->groupData['uid'])) {
1064  // global flag!
1065  $this->loginUser = true;
1066  // 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!
1067  $this->gr_list = '0,-2';
1068  $gr_array = $this->fe_user->groupData['uid'];
1069  } else {
1070  $this->loginUser = false;
1071  // 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!
1072  $this->gr_list = '0,-1';
1073  if ($this->loginAllowedInBranch) {
1074  // 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.
1075  $gr_array = $this->fe_user->groupData['uid'];
1076  } else {
1077  // Set to blank since we will NOT risk any groups being set when no logins are allowed!
1078  $gr_array = [];
1079  }
1080  }
1081  // Clean up.
1082  // Make unique...
1083  $gr_array = array_unique($gr_array);
1084  // sort
1085  sort($gr_array);
1086  if (!empty($gr_array) && !$this->loginAllowedInBranch_mode) {
1087  $this->gr_list .= ',' . implode(',', $gr_array);
1088  }
1089  if ($this->fe_user->writeDevLog) {
1090  GeneralUtility::devLog('Valid usergroups for TSFE: ' . $this->gr_list, __CLASS__);
1091  }
1092  }
1093 
1099  public function isUserOrGroupSet()
1100  {
1101  return is_array($this->fe_user->user) || $this->gr_list !== '0,-1';
1102  }
1103 
1111  public function checkAlternativeIdMethods()
1112  {
1113  $this->siteScript = GeneralUtility::getIndpEnv('TYPO3_SITE_SCRIPT');
1114  // Call post processing function for custom URL methods.
1115  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'])) {
1116  $_params = ['pObj' => &$this];
1117  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'] as $_funcRef) {
1118  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1119  }
1120  }
1121  }
1122 
1128  public function clear_preview()
1129  {
1130  $this->showHiddenPage = false;
1131  $this->showHiddenRecords = false;
1132  $GLOBALS['SIM_EXEC_TIME'] = $GLOBALS['EXEC_TIME'];
1133  $GLOBALS['SIM_ACCESS_TIME'] = $GLOBALS['ACCESS_TIME'];
1134  $this->fePreview = 0;
1135  }
1136 
1142  public function isBackendUserLoggedIn()
1143  {
1144  return (bool)$this->beUserLogin;
1145  }
1146 
1152  public function initializeBackendUser()
1153  {
1154  // PRE BE_USER HOOK
1155  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preBeUser'])) {
1156  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preBeUser'] as $_funcRef) {
1157  $_params = [];
1158  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1159  }
1160  }
1161  $backendUserObject = null;
1162  // If the backend cookie is set,
1163  // we proceed and check if a backend user is logged in.
1164  if ($_COOKIE[BackendUserAuthentication::getCookieName()]) {
1165  $GLOBALS['TYPO3_MISC']['microtime_BE_USER_start'] = microtime(true);
1166  $this->getTimeTracker()->push('Back End user initialized', '');
1167  $this->beUserLogin = false;
1168  // New backend user object
1169  $backendUserObject = GeneralUtility::makeInstance(FrontendBackendUserAuthentication::class);
1170  $backendUserObject->start();
1171  $backendUserObject->unpack_uc();
1172  if (!empty($backendUserObject->user['uid'])) {
1173  $backendUserObject->fetchGroupData();
1174  }
1175  // Unset the user initialization if any setting / restriction applies
1176  if (!$backendUserObject->checkBackendAccessSettingsFromInitPhp()) {
1177  $backendUserObject = null;
1178  } elseif (!empty($backendUserObject->user['uid'])) {
1179  // If the user is active now, let the controller know
1180  $this->beUserLogin = true;
1181  } else {
1182  $backendUserObject = null;
1183  }
1184  $this->getTimeTracker()->pull();
1185  $GLOBALS['TYPO3_MISC']['microtime_BE_USER_end'] = microtime(true);
1186  }
1187  // POST BE_USER HOOK
1188  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['postBeUser'])) {
1189  $_params = [
1190  'BE_USER' => &$backendUserObject
1191  ];
1192  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['postBeUser'] as $_funcRef) {
1193  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1194  }
1195  }
1196  return $backendUserObject;
1197  }
1198 
1205  public function determineId()
1206  {
1207  // Call pre processing function for id determination
1208  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PreProcessing'])) {
1209  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PreProcessing'] as $functionReference) {
1210  $parameters = ['parentObject' => $this];
1211  GeneralUtility::callUserFunction($functionReference, $parameters, $this);
1212  }
1213  }
1214  // If there is a Backend login we are going to check for any preview settings:
1215  $this->getTimeTracker()->push('beUserLogin', '');
1216  $originalFrontendUser = null;
1217  $backendUser = $this->getBackendUser();
1218  if ($this->beUserLogin || $this->doWorkspacePreview()) {
1219  // Backend user preview features:
1220  if ($this->beUserLogin && $backendUser->adminPanel instanceof AdminPanelView) {
1221  $this->fePreview = (int)$backendUser->adminPanel->extGetFeAdminValue('preview');
1222  // If admin panel preview is enabled...
1223  if ($this->fePreview) {
1224  if ($this->fe_user->user) {
1225  $originalFrontendUser = $this->fe_user->user;
1226  }
1227  $this->showHiddenPage = (bool)$backendUser->adminPanel->extGetFeAdminValue('preview', 'showHiddenPages');
1228  $this->showHiddenRecords = (bool)$backendUser->adminPanel->extGetFeAdminValue('preview', 'showHiddenRecords');
1229  // Simulate date
1230  $simTime = $backendUser->adminPanel->extGetFeAdminValue('preview', 'simulateDate');
1231  if ($simTime) {
1232  $simTime -= date('Z', $simTime);
1233  $GLOBALS['SIM_EXEC_TIME'] = $simTime;
1234  $GLOBALS['SIM_ACCESS_TIME'] = $simTime - $simTime % 60;
1235  }
1236  // simulate user
1237  $simUserGroup = $backendUser->adminPanel->extGetFeAdminValue('preview', 'simulateUserGroup');
1238  $this->simUserGroup = $simUserGroup;
1239  if ($simUserGroup) {
1240  if ($this->fe_user->user) {
1241  $this->fe_user->user[$this->fe_user->usergroup_column] = $simUserGroup;
1242  } else {
1243  $this->fe_user->user = [
1244  $this->fe_user->usergroup_column => $simUserGroup
1245  ];
1246  }
1247  }
1248  if (!$simUserGroup && !$simTime && !$this->showHiddenPage && !$this->showHiddenRecords) {
1249  $this->fePreview = 0;
1250  }
1251  }
1252  }
1253  if ($this->id && $this->determineIdIsHiddenPage()) {
1254  // The preview flag is set only if the current page turns out to actually be hidden!
1255  $this->fePreview = 1;
1256  $this->showHiddenPage = true;
1257  }
1258  // The preview flag will be set if a backend user is in an offline workspace
1259  if (
1260  (
1261  $backendUser->user['workspace_preview']
1262  || GeneralUtility::_GP('ADMCMD_view')
1263  || $this->doWorkspacePreview()
1264  )
1265  && (
1266  $this->whichWorkspace() === -1
1267  || $this->whichWorkspace() > 0
1268  )
1269  && !GeneralUtility::_GP('ADMCMD_noBeUser')
1270  ) {
1271  // Will show special preview message.
1272  $this->fePreview = 2;
1273  }
1274  // If the front-end is showing a preview, caching MUST be disabled.
1275  if ($this->fePreview) {
1276  $this->disableCache();
1277  }
1278  }
1279  $this->getTimeTracker()->pull();
1280  // Now, get the id, validate access etc:
1281  $this->fetch_the_id();
1282  // Check if backend user has read access to this page. If not, recalculate the id.
1283  if ($this->beUserLogin && $this->fePreview) {
1284  if (!$backendUser->doesUserHaveAccess($this->page, 1)) {
1285  // Resetting
1286  $this->clear_preview();
1287  $this->fe_user->user = $originalFrontendUser;
1288  // Fetching the id again, now with the preview settings reset.
1289  $this->fetch_the_id();
1290  }
1291  }
1292  // Checks if user logins are blocked for a certain branch and if so, will unset user login and re-fetch ID.
1293  $this->loginAllowedInBranch = $this->checkIfLoginAllowedInBranch();
1294  // Logins are not allowed:
1295  if (!$this->loginAllowedInBranch) {
1296  // Only if there is a login will we run this...
1297  if ($this->isUserOrGroupSet()) {
1298  if ($this->loginAllowedInBranch_mode === 'all') {
1299  // Clear out user and group:
1300  $this->fe_user->hideActiveLogin();
1301  $this->gr_list = '0,-1';
1302  } else {
1303  $this->gr_list = '0,-2';
1304  }
1305  // Fetching the id again, now with the preview settings reset.
1306  $this->fetch_the_id();
1307  }
1308  }
1309  // Final cleaning.
1310  // Make sure it's an integer
1311  $this->id = ($this->contentPid = (int)$this->id);
1312  // Make sure it's an integer
1313  $this->type = (int)$this->type;
1314  // Call post processing function for id determination:
1315  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PostProc'])) {
1316  $_params = ['pObj' => &$this];
1317  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PostProc'] as $_funcRef) {
1318  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1319  }
1320  }
1321  }
1322 
1329  protected function determineIdIsHiddenPage()
1330  {
1331  $field = MathUtility::canBeInterpretedAsInteger($this->id) ? 'uid' : 'alias';
1332 
1333  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1334  ->getQueryBuilderForTable('pages');
1335  $queryBuilder
1336  ->getRestrictions()
1337  ->removeAll()
1338  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1339 
1340  $page = $queryBuilder
1341  ->select('uid', 'hidden', 'starttime', 'endtime')
1342  ->from('pages')
1343  ->where(
1344  $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($this->id)),
1345  $queryBuilder->expr()->gte('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
1346  )
1347  ->setMaxResults(1)
1348  ->execute()
1349  ->fetch();
1350 
1351  $workspace = $this->whichWorkspace();
1352  if ($workspace !== 0 && $workspace !== false) {
1353  // Fetch overlay of page if in workspace and check if it is hidden
1354  $pageSelectObject = GeneralUtility::makeInstance(PageRepository::class);
1355  $pageSelectObject->versioningPreview = true;
1356  $pageSelectObject->init(false);
1357  $targetPage = $pageSelectObject->getWorkspaceVersionOfRecord($this->whichWorkspace(), 'pages', $page['uid']);
1358  $result = $targetPage === -1 || $targetPage === -2;
1359  } else {
1360  $result = is_array($page) && ($page['hidden'] || $page['starttime'] > $GLOBALS['SIM_EXEC_TIME'] || $page['endtime'] != 0 && $page['endtime'] <= $GLOBALS['SIM_EXEC_TIME']);
1361  }
1362  return $result;
1363  }
1364 
1373  public function fetch_the_id()
1374  {
1375  $timeTracker = $this->getTimeTracker();
1376  $timeTracker->push('fetch_the_id initialize/', '');
1377  // Initialize the page-select functions.
1378  $this->sys_page = GeneralUtility::makeInstance(PageRepository::class);
1379  $this->sys_page->versioningPreview = $this->fePreview === 2 || (int)$this->workspacePreview || (bool)GeneralUtility::_GP('ADMCMD_view');
1380  $this->sys_page->versioningWorkspaceId = $this->whichWorkspace();
1381  $this->sys_page->init($this->showHiddenPage);
1382  // Set the valid usergroups for FE
1383  $this->initUserGroups();
1384  // Sets sys_page where-clause
1385  $this->setSysPageWhereClause();
1386  // Splitting $this->id by a period (.).
1387  // First part is 'id' and second part (if exists) will overrule the &type param
1388  $idParts = explode('.', $this->id, 2);
1389  $this->id = $idParts[0];
1390  if (isset($idParts[1])) {
1391  $this->type = $idParts[1];
1392  }
1393 
1394  // If $this->id is a string, it's an alias
1395  $this->checkAndSetAlias();
1396  // The id and type is set to the integer-value - just to be sure...
1397  $this->id = (int)$this->id;
1398  $this->type = (int)$this->type;
1399  $timeTracker->pull();
1400  // We find the first page belonging to the current domain
1401  $timeTracker->push('fetch_the_id domain/', '');
1402  // The page_id of the current domain
1403  $this->domainStartPage = $this->findDomainRecord($GLOBALS['TYPO3_CONF_VARS']['SYS']['recursiveDomainSearch']);
1404  if (!$this->id) {
1405  if ($this->domainStartPage) {
1406  // If the id was not previously set, set it to the id of the domain.
1407  $this->id = $this->domainStartPage;
1408  } else {
1409  // Find the first 'visible' page in that domain
1410  $theFirstPage = $this->sys_page->getFirstWebPage($this->id);
1411  if ($theFirstPage) {
1412  $this->id = $theFirstPage['uid'];
1413  } else {
1414  $message = 'No pages are found on the rootlevel!';
1415  if ($this->checkPageUnavailableHandler()) {
1416  $this->pageUnavailableAndExit($message);
1417  } else {
1418  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1419  throw new ServiceUnavailableException($message, 1301648975);
1420  }
1421  }
1422  }
1423  }
1424  $timeTracker->pull();
1425  $timeTracker->push('fetch_the_id rootLine/', '');
1426  // We store the originally requested id
1427  $this->requestedId = $this->id;
1428  $this->getPageAndRootlineWithDomain($this->domainStartPage);
1429  $timeTracker->pull();
1430  if ($this->pageNotFound && $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling']) {
1431  $pNotFoundMsg = [
1432  1 => 'ID was not an accessible page',
1433  2 => 'Subsection was found and not accessible',
1434  3 => 'ID was outside the domain',
1435  4 => 'The requested page alias does not exist'
1436  ];
1437  $header = '';
1438  if ($this->pageNotFound === 1 || $this->pageNotFound === 2) {
1439  $header = $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_accessdeniedheader'];
1440  }
1441  $this->pageNotFoundAndExit($pNotFoundMsg[$this->pageNotFound], $header);
1442  }
1443  // Set no_cache if set
1444  if ($this->page['no_cache']) {
1445  $this->set_no_cache('no_cache is set in page properties');
1446  }
1447  // Init SYS_LASTCHANGED
1448  $this->register['SYS_LASTCHANGED'] = (int)$this->page['tstamp'];
1449  if ($this->register['SYS_LASTCHANGED'] < (int)$this->page['SYS_LASTCHANGED']) {
1450  $this->register['SYS_LASTCHANGED'] = (int)$this->page['SYS_LASTCHANGED'];
1451  }
1452  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['fetchPageId-PostProcessing'])) {
1453  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['fetchPageId-PostProcessing'] as $functionReference) {
1454  $parameters = ['parentObject' => $this];
1455  GeneralUtility::callUserFunction($functionReference, $parameters, $this);
1456  }
1457  }
1458  }
1459 
1474  public function getPageAndRootline()
1475  {
1476  $this->page = $this->sys_page->getPage($this->id);
1477  if (empty($this->page)) {
1478  // If no page, we try to find the page before in the rootLine.
1479  // Page is 'not found' in case the id itself was not an accessible page. code 1
1480  $this->pageNotFound = 1;
1481  $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1482  if (!empty($this->rootLine)) {
1483  $c = count($this->rootLine) - 1;
1484  while ($c > 0) {
1485  // Add to page access failure history:
1486  $this->pageAccessFailureHistory['direct_access'][] = $this->rootLine[$c];
1487  // Decrease to next page in rootline and check the access to that, if OK, set as page record and ID value.
1488  $c--;
1489  $this->id = $this->rootLine[$c]['uid'];
1490  $this->page = $this->sys_page->getPage($this->id);
1491  if (!empty($this->page)) {
1492  break;
1493  }
1494  }
1495  }
1496  // If still no page...
1497  if (empty($this->page)) {
1498  $message = 'The requested page does not exist!';
1499  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling']) {
1500  $this->pageNotFoundAndExit($message);
1501  } else {
1502  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1503  throw new PageNotFoundException($message, 1301648780);
1504  }
1505  }
1506  }
1507  // Spacer is not accessible in frontend
1508  if ($this->page['doktype'] == PageRepository::DOKTYPE_SPACER) {
1509  $message = 'The requested page does not exist!';
1510  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling']) {
1511  $this->pageNotFoundAndExit($message);
1512  } else {
1513  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1514  throw new PageNotFoundException($message, 1301648781);
1515  }
1516  }
1517  // Is the ID a link to another page??
1518  if ($this->page['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
1519  // 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.
1520  $this->MP = '';
1521  // saving the page so that we can check later - when we know
1522  // about languages - whether we took the correct shortcut or
1523  // whether a translation of the page overwrites the shortcut
1524  // target and we need to follow the new target
1525  $this->originalShortcutPage = $this->page;
1526  $this->page = $this->getPageShortcut($this->page['shortcut'], $this->page['shortcut_mode'], $this->page['uid']);
1527  $this->id = $this->page['uid'];
1528  }
1529  // If the page is a mountpoint which should be overlaid with the contents of the mounted page,
1530  // it must never be accessible directly, but only in the mountpoint context. Therefore we change
1531  // the current ID and the user is redirected by checkPageForMountpointRedirect().
1532  if ($this->page['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT && $this->page['mount_pid_ol']) {
1533  $this->originalMountPointPage = $this->page;
1534  $this->page = $this->sys_page->getPage($this->page['mount_pid']);
1535  if (empty($this->page)) {
1536  $message = 'This page (ID ' . $this->originalMountPointPage['uid'] . ') is of type "Mount point" and '
1537  . 'mounts a page which is not accessible (ID ' . $this->originalMountPointPage['mount_pid'] . ').';
1538  throw new PageNotFoundException($message, 1402043263);
1539  }
1540  $this->MP = $this->page['uid'] . '-' . $this->originalMountPointPage['uid'];
1541  $this->id = $this->page['uid'];
1542  }
1543  // Gets the rootLine
1544  $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1545  // If not rootline we're off...
1546  if (empty($this->rootLine)) {
1547  $message = 'The requested page didn\'t have a proper connection to the tree-root!';
1548  if ($this->checkPageUnavailableHandler()) {
1549  $this->pageUnavailableAndExit($message);
1550  } else {
1551  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1552  throw new ServiceUnavailableException($message, 1301648167);
1553  }
1554  }
1555  // Checking for include section regarding the hidden/starttime/endtime/fe_user (that is access control of a whole subbranch!)
1556  if ($this->checkRootlineForIncludeSection()) {
1557  if (empty($this->rootLine)) {
1558  $message = 'The requested page was not accessible!';
1559  if ($this->checkPageUnavailableHandler()) {
1560  $this->pageUnavailableAndExit($message);
1561  } else {
1562  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1563  throw new ServiceUnavailableException($message, 1301648234);
1564  }
1565  } else {
1566  $el = reset($this->rootLine);
1567  $this->id = $el['uid'];
1568  $this->page = $this->sys_page->getPage($this->id);
1569  $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
1570  }
1571  }
1572  }
1573 
1589  public function getPageShortcut($SC, $mode, $thisUid, $itera = 20, $pageLog = [], $disableGroupCheck = false)
1590  {
1591  $idArray = GeneralUtility::intExplode(',', $SC);
1592  // Find $page record depending on shortcut mode:
1593  switch ($mode) {
1595 
1597  $pageArray = $this->sys_page->getMenu($idArray[0] ? $idArray[0] : $thisUid, '*', 'sorting', 'AND pages.doktype<199 AND pages.doktype!=' . PageRepository::DOKTYPE_BE_USER_SECTION);
1598  $pO = 0;
1599  if ($mode == PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE && !empty($pageArray)) {
1600  $randval = (int)rand(0, count($pageArray) - 1);
1601  $pO = $randval;
1602  }
1603  $c = 0;
1604  $page = [];
1605  foreach ($pageArray as $pV) {
1606  if ($c === $pO) {
1607  $page = $pV;
1608  break;
1609  }
1610  $c++;
1611  }
1612  if (empty($page)) {
1613  $message = 'This page (ID ' . $thisUid . ') is of type "Shortcut" and configured to redirect to a subpage. ' . 'However, this page has no accessible subpages.';
1614  throw new PageNotFoundException($message, 1301648328);
1615  }
1616  break;
1618  $parent = $this->sys_page->getPage($idArray[0] ? $idArray[0] : $thisUid, $disableGroupCheck);
1619  $page = $this->sys_page->getPage($parent['pid'], $disableGroupCheck);
1620  if (empty($page)) {
1621  $message = 'This page (ID ' . $thisUid . ') is of type "Shortcut" and configured to redirect to its parent page. ' . 'However, the parent page is not accessible.';
1622  throw new PageNotFoundException($message, 1301648358);
1623  }
1624  break;
1625  default:
1626  $page = $this->sys_page->getPage($idArray[0], $disableGroupCheck);
1627  if (empty($page)) {
1628  $message = 'This page (ID ' . $thisUid . ') is of type "Shortcut" and configured to redirect to a page, which is not accessible (ID ' . $idArray[0] . ').';
1629  throw new PageNotFoundException($message, 1301648404);
1630  }
1631  }
1632  // Check if short cut page was a shortcut itself, if so look up recursively:
1633  if ($page['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
1634  if (!in_array($page['uid'], $pageLog) && $itera > 0) {
1635  $pageLog[] = $page['uid'];
1636  $page = $this->getPageShortcut($page['shortcut'], $page['shortcut_mode'], $page['uid'], $itera - 1, $pageLog, $disableGroupCheck);
1637  } else {
1638  $pageLog[] = $page['uid'];
1639  $message = 'Page shortcuts were looping in uids ' . implode(',', $pageLog) . '...!';
1640  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1641  throw new \RuntimeException($message, 1294587212);
1642  }
1643  }
1644  // Return resulting page:
1645  return $page;
1646  }
1647 
1655  {
1656  $c = count($this->rootLine);
1657  $removeTheRestFlag = 0;
1658  for ($a = 0; $a < $c; $a++) {
1659  if (!$this->checkPagerecordForIncludeSection($this->rootLine[$a])) {
1660  // Add to page access failure history:
1661  $this->pageAccessFailureHistory['sub_section'][] = $this->rootLine[$a];
1662  $removeTheRestFlag = 1;
1663  }
1664 
1665  if ($this->rootLine[$a]['doktype'] == PageRepository::DOKTYPE_BE_USER_SECTION) {
1666  // If there is a backend user logged in, check if he has read access to the page:
1667  if ($this->beUserLogin) {
1668  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1669  ->getQueryBuilderForTable('pages');
1670 
1671  $queryBuilder
1672  ->getRestrictions()
1673  ->removeAll();
1674 
1675  $row = $queryBuilder
1676  ->select('uid')
1677  ->from('pages')
1678  ->where(
1679  $queryBuilder->expr()->eq(
1680  'uid',
1681  $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1682  ),
1683  $this->getBackendUser()->getPagePermsClause(1)
1684  )
1685  ->execute()
1686  ->fetch();
1687 
1688  // versionOL()?
1689  if (!$row) {
1690  // 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...
1691  $removeTheRestFlag = 1;
1692  }
1693  } else {
1694  // Don't go here, if there is no backend user logged in.
1695  $removeTheRestFlag = 1;
1696  }
1697  }
1698  if ($removeTheRestFlag) {
1699  // Page is 'not found' in case a subsection was found and not accessible, code 2
1700  $this->pageNotFound = 2;
1701  unset($this->rootLine[$a]);
1702  }
1703  }
1704  return $removeTheRestFlag;
1705  }
1706 
1717  public function checkEnableFields($row, $bypassGroupCheck = false)
1718  {
1719  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_checkEnableFields']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_checkEnableFields'])) {
1720  $_params = ['pObj' => $this, 'row' => &$row, 'bypassGroupCheck' => &$bypassGroupCheck];
1721  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_checkEnableFields'] as $_funcRef) {
1722  // Call hooks: If one returns FALSE, method execution is aborted with result "This record is not available"
1723  $return = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1724  if ($return === false) {
1725  return false;
1726  }
1727  }
1728  }
1729  if ((!$row['hidden'] || $this->showHiddenPage) && $row['starttime'] <= $GLOBALS['SIM_ACCESS_TIME'] && ($row['endtime'] == 0 || $row['endtime'] > $GLOBALS['SIM_ACCESS_TIME']) && ($bypassGroupCheck || $this->checkPageGroupAccess($row))) {
1730  return true;
1731  }
1732  return false;
1733  }
1734 
1743  public function checkPageGroupAccess($row, $groupList = null)
1744  {
1745  if (is_null($groupList)) {
1746  $groupList = $this->gr_list;
1747  }
1748  if (!is_array($groupList)) {
1749  $groupList = explode(',', $groupList);
1750  }
1751  $pageGroupList = explode(',', $row['fe_group'] ?: 0);
1752  return count(array_intersect($groupList, $pageGroupList)) > 0;
1753  }
1754 
1763  public function checkPagerecordForIncludeSection($row)
1764  {
1765  return !$row['extendToSubpages'] || $this->checkEnableFields($row) ? 1 : 0;
1766  }
1767 
1774  {
1775  // Initialize:
1776  $c = count($this->rootLine);
1777  $loginAllowed = true;
1778  // Traverse root line from root and outwards:
1779  for ($a = 0; $a < $c; $a++) {
1780  // If a value is set for login state:
1781  if ($this->rootLine[$a]['fe_login_mode'] > 0) {
1782  // Determine state from value:
1783  if ((int)$this->rootLine[$a]['fe_login_mode'] === 1) {
1784  $loginAllowed = false;
1785  $this->loginAllowedInBranch_mode = 'all';
1786  } elseif ((int)$this->rootLine[$a]['fe_login_mode'] === 3) {
1787  $loginAllowed = false;
1788  $this->loginAllowedInBranch_mode = 'groups';
1789  } else {
1790  $loginAllowed = true;
1791  }
1792  }
1793  }
1794  return $loginAllowed;
1795  }
1796 
1803  {
1804  $output = [];
1805  $combinedRecords = array_merge(is_array($this->pageAccessFailureHistory['direct_access']) ? $this->pageAccessFailureHistory['direct_access'] : [['fe_group' => 0]], is_array($this->pageAccessFailureHistory['sub_section']) ? $this->pageAccessFailureHistory['sub_section'] : []);
1806  if (!empty($combinedRecords)) {
1807  foreach ($combinedRecords as $k => $pagerec) {
1808  // 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
1809  // 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!
1810  if (!$k || $pagerec['extendToSubpages']) {
1811  if ($pagerec['hidden']) {
1812  $output['hidden'][$pagerec['uid']] = true;
1813  }
1814  if ($pagerec['starttime'] > $GLOBALS['SIM_ACCESS_TIME']) {
1815  $output['starttime'][$pagerec['uid']] = $pagerec['starttime'];
1816  }
1817  if ($pagerec['endtime'] != 0 && $pagerec['endtime'] <= $GLOBALS['SIM_ACCESS_TIME']) {
1818  $output['endtime'][$pagerec['uid']] = $pagerec['endtime'];
1819  }
1820  if (!$this->checkPageGroupAccess($pagerec)) {
1821  $output['fe_group'][$pagerec['uid']] = $pagerec['fe_group'];
1822  }
1823  }
1824  }
1825  }
1826  return $output;
1827  }
1828 
1837  {
1838  $this->getPageAndRootline();
1839  // 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.
1840  if ($domainStartPage && is_array($this->rootLine)) {
1841  $idFound = 0;
1842  foreach ($this->rootLine as $key => $val) {
1843  if ($val['uid'] == $domainStartPage) {
1844  $idFound = 1;
1845  break;
1846  }
1847  }
1848  if (!$idFound) {
1849  // Page is 'not found' in case the id was outside the domain, code 3
1850  $this->pageNotFound = 3;
1851  $this->id = $domainStartPage;
1852  // re-get the page and rootline if the id was not found.
1853  $this->getPageAndRootline();
1854  }
1855  }
1856  }
1857 
1863  public function setSysPageWhereClause()
1864  {
1865  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1866  ->getConnectionForTable('pages')
1867  ->getExpressionBuilder();
1868  $this->sys_page->where_hid_del = ' AND ' . (string)$expressionBuilder->andX(
1869  QueryHelper::stripLogicalOperatorPrefix($this->sys_page->where_hid_del),
1870  $expressionBuilder->lt('pages.doktype', 200)
1871  );
1872  $this->sys_page->where_groupAccess = $this->sys_page->getMultipleGroupsWhereClause('pages.fe_group', 'pages');
1873  }
1874 
1882  public function findDomainRecord($recursive = false)
1883  {
1884  if ($recursive) {
1885  $pageUid = 0;
1886  $host = explode('.', GeneralUtility::getIndpEnv('HTTP_HOST'));
1887  while (count($host)) {
1888  $pageUid = $this->sys_page->getDomainStartPage(implode('.', $host), GeneralUtility::getIndpEnv('SCRIPT_NAME'), GeneralUtility::getIndpEnv('REQUEST_URI'));
1889  if ($pageUid) {
1890  return $pageUid;
1891  }
1892  array_shift($host);
1893  }
1894  return $pageUid;
1895  }
1896  return $this->sys_page->getDomainStartPage(GeneralUtility::getIndpEnv('HTTP_HOST'), GeneralUtility::getIndpEnv('SCRIPT_NAME'), GeneralUtility::getIndpEnv('REQUEST_URI'));
1897  }
1898 
1905  public function pageUnavailableAndExit($reason = '', $header = '')
1906  {
1907  $header = $header ?: $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling_statheader'];
1908  $this->pageUnavailableHandler($GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'], $header, $reason);
1909  die;
1910  }
1911 
1918  public function pageNotFoundAndExit($reason = '', $header = '')
1919  {
1920  $header = $header ?: $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_statheader'];
1921  $this->pageNotFoundHandler($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'], $header, $reason);
1922  die;
1923  }
1924 
1932  {
1933  if (
1934  $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling']
1936  GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1937  $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
1938  )
1939  ) {
1940  $checkPageUnavailableHandler = true;
1941  } else {
1942  $checkPageUnavailableHandler = false;
1943  }
1944  return $checkPageUnavailableHandler;
1945  }
1946 
1954  public function pageUnavailableHandler($code, $header, $reason)
1955  {
1956  $this->pageErrorHandler($code, $header, $reason);
1957  }
1958 
1966  public function pageNotFoundHandler($code, $header = '', $reason = '')
1967  {
1968  $this->pageErrorHandler($code, $header, $reason);
1969  }
1970 
1980  public function pageErrorHandler($code, $header = '', $reason = '')
1981  {
1982  // Issue header in any case:
1983  if ($header) {
1984  $headerArr = preg_split('/\\r|\\n/', $header, -1, PREG_SPLIT_NO_EMPTY);
1985  foreach ($headerArr as $header) {
1986  header($header);
1987  }
1988  }
1989  // Create response:
1990  // Simply boolean; Just shows TYPO3 error page with reason:
1991  if (strtolower($code) === 'true' || (string)$code === '1' || gettype($code) === 'boolean') {
1992  echo GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
1993  'Page Not Found',
1994  'The page did not exist or was inaccessible.' . ($reason ? ' Reason: ' . $reason : '')
1995  );
1996  } elseif (GeneralUtility::isFirstPartOfStr($code, 'USER_FUNCTION:')) {
1997  $funcRef = trim(substr($code, 14));
1998  $params = [
1999  'currentUrl' => GeneralUtility::getIndpEnv('REQUEST_URI'),
2000  'reasonText' => $reason,
2001  'pageAccessFailureReasons' => $this->getPageAccessFailureReasons()
2002  ];
2003  try {
2004  echo GeneralUtility::callUserFunction($funcRef, $params, $this);
2005  } catch (\Exception $e) {
2006  throw new \RuntimeException('Error: 404 page by USER_FUNCTION "' . $funcRef . '" failed.', 1509296032, $e);
2007  }
2008  } elseif (GeneralUtility::isFirstPartOfStr($code, 'READFILE:')) {
2009  $readFile = GeneralUtility::getFileAbsFileName(trim(substr($code, 9)));
2010  if (@is_file($readFile)) {
2011  echo str_replace(
2012  [
2013  '###CURRENT_URL###',
2014  '###REASON###'
2015  ],
2016  [
2017  GeneralUtility::getIndpEnv('REQUEST_URI'),
2018  htmlspecialchars($reason)
2019  ],
2020  file_get_contents($readFile)
2021  );
2022  } else {
2023  throw new \RuntimeException('Configuration Error: 404 page "' . $readFile . '" could not be found.', 1294587214);
2024  }
2025  } elseif (GeneralUtility::isFirstPartOfStr($code, 'REDIRECT:')) {
2026  HttpUtility::redirect(substr($code, 9));
2027  } elseif ($code !== '') {
2028  // Check if URL is relative
2029  $url_parts = parse_url($code);
2030  // parse_url could return an array without the key "host", the empty check works better than strict check
2031  if (empty($url_parts['host'])) {
2032  $url_parts['host'] = GeneralUtility::getIndpEnv('HTTP_HOST');
2033  if ($code[0] === '/') {
2034  $code = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST') . $code;
2035  } else {
2036  $code = GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR') . $code;
2037  }
2038  $checkBaseTag = false;
2039  } else {
2040  $checkBaseTag = true;
2041  }
2042  // Check recursion
2043  if ($code == GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')) {
2044  if ($reason == '') {
2045  $reason = 'Page cannot be found.';
2046  }
2047  $reason .= LF . LF . 'Additionally, ' . $code . ' was not found while trying to retrieve the error document.';
2048  throw new \RuntimeException(nl2br(htmlspecialchars($reason)), 1294587215);
2049  }
2050  // Prepare headers
2051  $headerArr = [
2052  'User-agent: ' . GeneralUtility::getIndpEnv('HTTP_USER_AGENT'),
2053  'Referer: ' . GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')
2054  ];
2055  $report = [];
2056  $res = GeneralUtility::getUrl($code, 1, $headerArr, $report);
2057  if ((int)$report['error'] !== 0 && (int)$report['error'] !== 200) {
2058  throw new \RuntimeException('Failed to fetch error page "' . $code . '", reason: ' . $report['message'], 1509296606);
2059  }
2060  // Header and content are separated by an empty line
2061  list($header, $content) = explode(CRLF . CRLF, $res, 2);
2062  $content .= CRLF;
2063  if (false === $res) {
2064  // Last chance -- redirect
2065  HttpUtility::redirect($code);
2066  } else {
2067  // Forward these response headers to the client
2068  $forwardHeaders = [
2069  'Content-Type:'
2070  ];
2071  $headerArr = preg_split('/\\r|\\n/', $header, -1, PREG_SPLIT_NO_EMPTY);
2072  foreach ($headerArr as $header) {
2073  foreach ($forwardHeaders as $h) {
2074  if (preg_match('/^' . $h . '/', $header)) {
2075  header($header);
2076  }
2077  }
2078  }
2079  // Put <base> if necessary
2080  if ($checkBaseTag) {
2081  // If content already has <base> tag, we do not need to do anything
2082  if (false === stristr($content, '<base ')) {
2083  // Generate href for base tag
2084  $base = $url_parts['scheme'] . '://';
2085  if ($url_parts['user'] != '') {
2086  $base .= $url_parts['user'];
2087  if ($url_parts['pass'] != '') {
2088  $base .= ':' . $url_parts['pass'];
2089  }
2090  $base .= '@';
2091  }
2092  $base .= $url_parts['host'];
2093  // Add path portion skipping possible file name
2094  $base .= preg_replace('/(.*\\/)[^\\/]*/', '${1}', $url_parts['path']);
2095  // Put it into content (generate also <head> if necessary)
2096  $replacement = LF . '<base href="' . htmlentities($base) . '" />' . LF;
2097  if (stristr($content, '<head>')) {
2098  $content = preg_replace('/(<head>)/i', '\\1' . $replacement, $content);
2099  } else {
2100  $content = preg_replace('/(<html[^>]*>)/i', '\\1<head>' . $replacement . '</head>', $content);
2101  }
2102  }
2103  }
2104  // Output the content
2105  echo $content;
2106  }
2107  } else {
2108  echo GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
2109  'Page Not Found',
2110  $reason ? 'Reason: ' . $reason : 'Page cannot be found.'
2111  );
2112  }
2113  die;
2114  }
2115 
2122  public function checkAndSetAlias()
2123  {
2124  if ($this->id && !MathUtility::canBeInterpretedAsInteger($this->id)) {
2125  $aid = $this->sys_page->getPageIdFromAlias($this->id);
2126  if ($aid) {
2127  $this->id = $aid;
2128  } else {
2129  $this->pageNotFound = 4;
2130  }
2131  }
2132  }
2133 
2139  public function mergingWithGetVars($GET_VARS)
2140  {
2141  if (is_array($GET_VARS)) {
2142  // Getting $_GET var, unescaped.
2143  $realGet = GeneralUtility::_GET();
2144  if (!is_array($realGet)) {
2145  $realGet = [];
2146  }
2147  // Merge new values on top:
2148  ArrayUtility::mergeRecursiveWithOverrule($realGet, $GET_VARS);
2149  // Write values back to $_GET:
2150  GeneralUtility::_GETset($realGet);
2151  // Setting these specifically (like in the init-function):
2152  if (isset($GET_VARS['type'])) {
2153  $this->type = (int)$GET_VARS['type'];
2154  }
2155  if (isset($GET_VARS['cHash'])) {
2156  $this->cHash = $GET_VARS['cHash'];
2157  }
2158  if (isset($GET_VARS['MP'])) {
2159  $this->MP = $GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids'] ? $GET_VARS['MP'] : '';
2160  }
2161  if (isset($GET_VARS['no_cache']) && $GET_VARS['no_cache']) {
2162  $this->set_no_cache('no_cache is requested via GET parameter');
2163  }
2164  }
2165  }
2166 
2167  /********************************************
2168  *
2169  * Template and caching related functions.
2170  *
2171  *******************************************/
2180  public function makeCacheHash()
2181  {
2182  // No need to test anything if caching was already disabled.
2183  if ($this->no_cache && !$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']) {
2184  return;
2185  }
2186  $GET = GeneralUtility::_GET();
2187  if ($this->cHash && is_array($GET)) {
2188  // Make sure we use the page uid and not the page alias
2189  $GET['id'] = $this->id;
2190  $this->cHash_array = $this->cacheHash->getRelevantParameters(GeneralUtility::implodeArrayForUrl('', $GET));
2191  $cHash_calc = $this->cacheHash->calculateCacheHash($this->cHash_array);
2192  if (!hash_equals($cHash_calc, $this->cHash)) {
2193  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']) {
2194  $this->pageNotFoundAndExit('Request parameters could not be validated (&cHash comparison failed)');
2195  } else {
2196  $this->disableCache();
2197  $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);
2198  }
2199  }
2200  } elseif (is_array($GET)) {
2201  // No cHash is set, check if that is correct
2202  if ($this->cacheHash->doParametersRequireCacheHash(GeneralUtility::implodeArrayForUrl('', $GET))) {
2203  $this->reqCHash();
2204  }
2205  }
2206  }
2207 
2214  public function reqCHash()
2215  {
2216  if (!$this->cHash) {
2217  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']) {
2218  $this->pageNotFoundAndExit('Request parameters could not be validated (&cHash empty)');
2219  } else {
2220  $this->disableCache();
2221  $this->getTimeTracker()->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', 2);
2222  }
2223  }
2224  }
2225 
2229  public function initTemplate()
2230  {
2231  $this->tmpl = GeneralUtility::makeInstance(TemplateService::class);
2232  $this->tmpl->setVerbose((bool)$this->beUserLogin);
2233  $this->tmpl->init();
2234  $this->tmpl->tt_track = (bool)$this->beUserLogin;
2235  }
2236 
2244  public function getFromCache()
2245  {
2246  // clearing the content-variable, which will hold the pagecontent
2247  $this->content = '';
2248  // Unsetting the lowlevel config
2249  $this->config = [];
2250  $this->cacheContentFlag = false;
2251 
2252  if ($this->no_cache) {
2253  return;
2254  }
2255 
2256  $pageSectionCacheContent = $this->tmpl->getCurrentPageData();
2257  if (!is_array($pageSectionCacheContent)) {
2258  // Nothing in the cache, we acquire an "exclusive lock" for the key now.
2259  // We use the Registry to store this lock centrally,
2260  // but we protect the access again with a global exclusive lock to avoid race conditions
2261 
2262  $this->acquireLock('pagesection', $this->id . '::' . $this->MP);
2263  //
2264  // from this point on we're the only one working on that page ($key)
2265  //
2266 
2267  // query the cache again to see if the page data are there meanwhile
2268  $pageSectionCacheContent = $this->tmpl->getCurrentPageData();
2269  if (is_array($pageSectionCacheContent)) {
2270  // we have the content, nice that some other process did the work for us already
2271  $this->releaseLock('pagesection');
2272  }
2273  // We keep the lock set, because we are the ones generating the page now and filling the cache.
2274  // This indicates that we have to release the lock later in releaseLocks()
2275  }
2276 
2277  if (is_array($pageSectionCacheContent)) {
2278  // 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.
2279  // If this hash is not the same in here in this section and after page-generation, then the page will not be properly cached!
2280  // 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.
2281  $pageSectionCacheContent = $this->tmpl->matching($pageSectionCacheContent);
2282  ksort($pageSectionCacheContent);
2283  $this->all = $pageSectionCacheContent;
2284  }
2285  unset($pageSectionCacheContent);
2286 
2287  // Look for page in cache only if a shift-reload is not sent to the server.
2288  $lockHash = $this->getLockHash();
2289  if (!$this->headerNoCache()) {
2290  if ($this->all) {
2291  // we got page section information
2292  $this->newHash = $this->getHash();
2293  $this->getTimeTracker()->push('Cache Row', '');
2294  $row = $this->getFromCache_queryRow();
2295  if (!is_array($row)) {
2296  // nothing in the cache, we acquire an exclusive lock now
2297 
2298  $this->acquireLock('pages', $lockHash);
2299  //
2300  // from this point on we're the only one working on that page ($lockHash)
2301  //
2302 
2303  // query the cache again to see if the data are there meanwhile
2304  $row = $this->getFromCache_queryRow();
2305  if (is_array($row)) {
2306  // we have the content, nice that some other process did the work for us
2307  $this->releaseLock('pages');
2308  }
2309  // We keep the lock set, because we are the ones generating the page now and filling the cache.
2310  // This indicates that we have to release the lock later in releaseLocks()
2311  }
2312  if (is_array($row)) {
2313  // we have data from cache
2314 
2315  // Call hook when a page is retrieved from cache:
2316  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageLoadedFromCache'])) {
2317  $_params = ['pObj' => &$this, 'cache_pages_row' => &$row];
2318  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageLoadedFromCache'] as $_funcRef) {
2319  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2320  }
2321  }
2322  // Fetches the lowlevel config stored with the cached data
2323  $this->config = $row['cache_data'];
2324  // Getting the content
2325  $this->content = $row['content'];
2326  // Setting flag, so we know, that some cached content has been loaded
2327  $this->cacheContentFlag = true;
2328  $this->cacheExpires = $row['expires'];
2329 
2330  // Restore page title information, this is needed to generate the page title for
2331  // partially cached pages.
2332  $this->page['title'] = $row['pageTitleInfo']['title'];
2333  $this->altPageTitle = $row['pageTitleInfo']['altPageTitle'];
2334  $this->indexedDocTitle = $row['pageTitleInfo']['indexedDocTitle'];
2335 
2336  if (isset($this->config['config']['debug'])) {
2337  $debugCacheTime = (bool)$this->config['config']['debug'];
2338  } else {
2339  $debugCacheTime = !empty($GLOBALS['TYPO3_CONF_VARS']['FE']['debug']);
2340  }
2341  if ($debugCacheTime) {
2342  $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
2343  $timeFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
2344  $this->content .= LF . '<!-- Cached page generated ' . date(($dateFormat . ' ' . $timeFormat), $row['tstamp']) . '. Expires ' . date(($dateFormat . ' ' . $timeFormat), $row['expires']) . ' -->';
2345  }
2346  }
2347  $this->getTimeTracker()->pull();
2348 
2349  return;
2350  }
2351  }
2352  // the user forced rebuilding the page cache or there was no pagesection information
2353  // get a lock for the page content so other processes will not interrupt the regeneration
2354  $this->acquireLock('pages', $lockHash);
2355  }
2356 
2362  public function getFromCache_queryRow()
2363  {
2364  $this->getTimeTracker()->push('Cache Query', '');
2365  $row = $this->pageCache->get($this->newHash);
2366  $this->getTimeTracker()->pull();
2367  return $row;
2368  }
2369 
2377  public function headerNoCache()
2378  {
2379  $disableAcquireCacheData = false;
2380  if ($this->beUserLogin) {
2381  if (strtolower($_SERVER['HTTP_CACHE_CONTROL']) === 'no-cache' || strtolower($_SERVER['HTTP_PRAGMA']) === 'no-cache') {
2382  $disableAcquireCacheData = true;
2383  }
2384  }
2385  // Call hook for possible by-pass of requiring of page cache (for recaching purpose)
2386  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['headerNoCache'])) {
2387  $_params = ['pObj' => &$this, 'disableAcquireCacheData' => &$disableAcquireCacheData];
2388  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['headerNoCache'] as $_funcRef) {
2389  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2390  }
2391  }
2392  return $disableAcquireCacheData;
2393  }
2394 
2404  public function getHash()
2405  {
2406  return md5($this->createHashBase(false));
2407  }
2408 
2417  public function getLockHash()
2418  {
2419  $lockHash = $this->createHashBase(true);
2420  return md5($lockHash);
2421  }
2422 
2433  protected function createHashBase($createLockHashBase = false)
2434  {
2435  $hashParameters = [
2436  'id' => (int)$this->id,
2437  'type' => (int)$this->type,
2438  'gr_list' => (string)$this->gr_list,
2439  'MP' => (string)$this->MP,
2440  'cHash' => $this->cHash_array,
2441  'domainStartPage' => $this->domainStartPage
2442  ];
2443  // Include the template information if we shouldn't create a lock hash
2444  if (!$createLockHashBase) {
2445  $hashParameters['all'] = $this->all;
2446  }
2447  // Call hook to influence the hash calculation
2448  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['createHashBase'])) {
2449  $_params = [
2450  'hashParameters' => &$hashParameters,
2451  'createLockHashBase' => $createLockHashBase
2452  ];
2453  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['createHashBase'] as $_funcRef) {
2454  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2455  }
2456  }
2457  return serialize($hashParameters);
2458  }
2459 
2465  public function getConfigArray()
2466  {
2467  // 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
2468  if (empty($this->config) || is_array($this->config['INTincScript']) || $this->forceTemplateParsing) {
2469  $timeTracker = $this->getTimeTracker();
2470  $timeTracker->push('Parse template', '');
2471  // Force parsing, if set?:
2472  $this->tmpl->forceTemplateParsing = $this->forceTemplateParsing;
2473  // Start parsing the TS template. Might return cached version.
2474  $this->tmpl->start($this->rootLine);
2475  $timeTracker->pull();
2476  // At this point we have a valid pagesection_cache (generated in $this->tmpl->start()),
2477  // so let all other processes proceed now. (They are blocked at the pagessection_lock in getFromCache())
2478  $this->releaseLock('pagesection');
2479  if ($this->tmpl->loaded) {
2480  $timeTracker->push('Setting the config-array', '');
2481  // toplevel - objArrayName
2482  $this->sPre = $this->tmpl->setup['types.'][$this->type];
2483  $this->pSetup = $this->tmpl->setup[$this->sPre . '.'];
2484  if (!is_array($this->pSetup)) {
2485  $message = 'The page is not configured! [type=' . $this->type . '][' . $this->sPre . '].';
2486  if ($this->checkPageUnavailableHandler()) {
2487  $this->pageUnavailableAndExit($message);
2488  } else {
2489  $explanation = 'This means that there is no TypoScript object of type PAGE with typeNum=' . $this->type . ' configured.';
2490  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
2491  throw new ServiceUnavailableException($message . ' ' . $explanation, 1294587217);
2492  }
2493  } else {
2494  if (!isset($this->config['config'])) {
2495  $this->config['config'] = [];
2496  }
2497  // Filling the config-array, first with the main "config." part
2498  if (is_array($this->tmpl->setup['config.'])) {
2499  ArrayUtility::mergeRecursiveWithOverrule($this->tmpl->setup['config.'], $this->config['config']);
2500  $this->config['config'] = $this->tmpl->setup['config.'];
2501  }
2502  // override it with the page/type-specific "config."
2503  if (is_array($this->pSetup['config.'])) {
2504  ArrayUtility::mergeRecursiveWithOverrule($this->config['config'], $this->pSetup['config.']);
2505  }
2506  if ($this->config['config']['typolinkEnableLinksAcrossDomains']) {
2507  $this->config['config']['typolinkCheckRootline'] = true;
2508  }
2509  // Set default values for removeDefaultJS and inlineStyle2TempFile so CSS and JS are externalized if compatversion is higher than 4.0
2510  if (!isset($this->config['config']['removeDefaultJS'])) {
2511  $this->config['config']['removeDefaultJS'] = 'external';
2512  }
2513  if (!isset($this->config['config']['inlineStyle2TempFile'])) {
2514  $this->config['config']['inlineStyle2TempFile'] = 1;
2515  }
2516 
2517  if (!isset($this->config['config']['compressJs'])) {
2518  $this->config['config']['compressJs'] = 0;
2519  }
2520  // Processing for the config_array:
2521  $this->config['rootLine'] = $this->tmpl->rootLine;
2522  $this->config['mainScript'] = trim($this->config['config']['mainScript']) ?: 'index.php';
2523  if (isset($this->config['config']['mainScript']) || $this->config['mainScript'] !== 'index.php') {
2524  $this->logDeprecatedTyposcript('config.mainScript', 'Setting the frontend script to something else than index.php is deprecated as of TYPO3 v8, and will not be possible in TYPO3 v9 without a custom extension');
2525  }
2526  // Class for render Header and Footer parts
2527  if ($this->pSetup['pageHeaderFooterTemplateFile']) {
2528  $file = $this->tmpl->getFileName($this->pSetup['pageHeaderFooterTemplateFile']);
2529  if ($file) {
2530  $this->pageRenderer->setTemplateFile($file);
2531  }
2532  }
2533  }
2534  $timeTracker->pull();
2535  } else {
2536  if ($this->checkPageUnavailableHandler()) {
2537  $this->pageUnavailableAndExit('No TypoScript template found!');
2538  } else {
2539  $message = 'No TypoScript template found!';
2540  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
2541  throw new ServiceUnavailableException($message, 1294587218);
2542  }
2543  }
2544  }
2545 
2546  // No cache
2547  // Set $this->no_cache TRUE if the config.no_cache value is set!
2548  if ($this->config['config']['no_cache']) {
2549  $this->set_no_cache('config.no_cache is set');
2550  }
2551  // Merge GET with defaultGetVars
2552  if (!empty($this->config['config']['defaultGetVars.'])) {
2553  $modifiedGetVars = GeneralUtility::removeDotsFromTS($this->config['config']['defaultGetVars.']);
2555  GeneralUtility::_GETset($modifiedGetVars);
2556  }
2557  // Hook for postProcessing the configuration array
2558  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['configArrayPostProc'])) {
2559  $params = ['config' => &$this->config['config']];
2560  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['configArrayPostProc'] as $funcRef) {
2561  GeneralUtility::callUserFunction($funcRef, $params, $this);
2562  }
2563  }
2564  }
2565 
2566  /********************************************
2567  *
2568  * Further initialization and data processing
2569  *
2570  *******************************************/
2571 
2578  public function settingLanguage()
2579  {
2580  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_preProcess'])) {
2581  $_params = [];
2582  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_preProcess'] as $_funcRef) {
2583  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2584  }
2585  }
2586 
2587  // Initialize charset settings etc.
2588  $this->initLLvars();
2589 
2590  // Get values from TypoScript:
2591  $this->sys_language_uid = ($this->sys_language_content = (int)$this->config['config']['sys_language_uid']);
2592  list($this->sys_language_mode, $sys_language_content) = GeneralUtility::trimExplode(';', $this->config['config']['sys_language_mode']);
2593  $this->sys_language_contentOL = $this->config['config']['sys_language_overlay'];
2594  // If sys_language_uid is set to another language than default:
2595  if ($this->sys_language_uid > 0) {
2596  // check whether a shortcut is overwritten by a translated page
2597  // we can only do this now, as this is the place where we get
2598  // to know about translations
2599  $this->checkTranslatedShortcut();
2600  // Request the overlay record for the sys_language_uid:
2601  $olRec = $this->sys_page->getPageOverlay($this->id, $this->sys_language_uid);
2602  if (empty($olRec)) {
2603  // If no OL record exists and a foreign language is asked for...
2604  if ($this->sys_language_uid) {
2605  // If requested translation is not available:
2606  if (GeneralUtility::hideIfNotTranslated($this->page['l18n_cfg'])) {
2607  $this->pageNotFoundAndExit('Page is not available in the requested language.');
2608  } else {
2609  switch ((string)$this->sys_language_mode) {
2610  case 'strict':
2611  $this->pageNotFoundAndExit('Page is not available in the requested language (strict).');
2612  break;
2613  case 'content_fallback':
2614  // Setting content uid (but leaving the sys_language_uid) when a content_fallback
2615  // value was found.
2616  $fallBackOrder = GeneralUtility::trimExplode(',', $sys_language_content);
2617  foreach ($fallBackOrder as $orderValue) {
2618  if ($orderValue === '0' || $orderValue === '') {
2619  $this->sys_language_content = 0;
2620  break;
2621  }
2622  if (MathUtility::canBeInterpretedAsInteger($orderValue) && !empty($this->sys_page->getPageOverlay($this->id, (int)$orderValue))) {
2623  $this->sys_language_content = (int)$orderValue;
2624  break;
2625  }
2626  if ($orderValue === 'pageNotFound') {
2627  // The existing fallbacks have not been found, but instead of continuing
2628  // page rendering with default language, a "page not found" message should be shown
2629  // instead.
2630  $this->pageNotFoundAndExit('Page is not available in the requested language (fallbacks did not apply).');
2631  }
2632  }
2633  break;
2634  case 'ignore':
2635  $this->sys_language_content = $this->sys_language_uid;
2636  break;
2637  default:
2638  // Default is that everything defaults to the default language...
2639  $this->sys_language_uid = ($this->sys_language_content = 0);
2640  }
2641  }
2642  }
2643  } else {
2644  // Setting sys_language if an overlay record was found (which it is only if a language is used)
2645  $this->page = $this->sys_page->getPageOverlay($this->page, $this->sys_language_uid);
2646  }
2647  }
2648  // Setting sys_language_uid inside sys-page:
2649  $this->sys_page->sys_language_uid = $this->sys_language_uid;
2650  // If default translation is not available:
2651  if ((!$this->sys_language_uid || !$this->sys_language_content) && GeneralUtility::hideIfDefaultLanguage($this->page['l18n_cfg'])) {
2652  $message = 'Page is not available in default language.';
2653  GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR);
2654  $this->pageNotFoundAndExit($message);
2655  }
2657 
2658  // Finding the ISO code for the currently selected language
2659  // fetched by the sys_language record when not fetching content from the default language
2660  if ($this->sys_language_content > 0) {
2661  // using sys_language_content because the ISO code only (currently) affect content selection from FlexForms - which should follow "sys_language_content"
2662  // Set the fourth parameter to TRUE in the next two getRawRecord() calls to
2663  // avoid versioning overlay to be applied as it generates an SQL error
2664  $sys_language_row = $this->sys_page->getRawRecord('sys_language', $this->sys_language_content, 'language_isocode,static_lang_isocode', true);
2665  if (is_array($sys_language_row) && !empty($sys_language_row['language_isocode'])) {
2666  $this->sys_language_isocode = $sys_language_row['language_isocode'];
2667  }
2668  // the DB value is overridden by TypoScript
2669  if (!empty($this->config['config']['sys_language_isocode'])) {
2670  $this->sys_language_isocode = $this->config['config']['sys_language_isocode'];
2671  }
2672  } else {
2673  // fallback to the TypoScript option when rendering with sys_language_uid=0
2674  // also: use "en" by default
2675  if (!empty($this->config['config']['sys_language_isocode_default'])) {
2676  $this->sys_language_isocode = $this->config['config']['sys_language_isocode_default'];
2677  } else {
2678  $this->sys_language_isocode = $this->lang !== 'default' ? $this->lang : 'en';
2679  }
2680  }
2681 
2682  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_postProcess'])) {
2683  $_params = [];
2684  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_postProcess'] as $_funcRef) {
2685  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2686  }
2687  }
2688  }
2689 
2693  protected function updateRootLinesWithTranslations()
2694  {
2695  if ($this->sys_language_uid) {
2696  $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
2697  $this->tmpl->updateRootlineData($this->rootLine);
2698  }
2699  }
2700 
2704  public function settingLocale()
2705  {
2706  // Setting locale
2707  if ($this->config['config']['locale_all']) {
2708  $availableLocales = GeneralUtility::trimExplode(',', $this->config['config']['locale_all'], true);
2709  // If LC_NUMERIC is set e.g. to 'de_DE' PHP parses float values locale-aware resulting in strings with comma
2710  // as decimal point which causes problems with value conversions - so we set all locale types except LC_NUMERIC
2711  // @see https://bugs.php.net/bug.php?id=53711
2712  $locale = setlocale(LC_COLLATE, ...$availableLocales);
2713  if ($locale) {
2714  // As str_* methods are locale aware and turkish has no upper case I
2715  // Class autoloading and other checks depending on case changing break with turkish locale LC_CTYPE
2716  // @see http://bugs.php.net/bug.php?id=35050
2717  if (substr($this->config['config']['locale_all'], 0, 2) !== 'tr') {
2718  setlocale(LC_CTYPE, ...$availableLocales);
2719  }
2720  setlocale(LC_MONETARY, ...$availableLocales);
2721  setlocale(LC_TIME, ...$availableLocales);
2722  } else {
2723  $this->getTimeTracker()->setTSlogMessage('Locale "' . htmlspecialchars($this->config['config']['locale_all']) . '" not found.', 3);
2724  }
2725  }
2726  }
2727 
2734  protected function checkTranslatedShortcut()
2735  {
2736  if (!is_null($this->originalShortcutPage)) {
2737  $originalShortcutPageOverlay = $this->sys_page->getPageOverlay($this->originalShortcutPage['uid'], $this->sys_language_uid);
2738  if (!empty($originalShortcutPageOverlay['shortcut']) && $originalShortcutPageOverlay['shortcut'] != $this->id) {
2739  // the translation of the original shortcut page has a different shortcut target!
2740  // set the correct page and id
2741  $shortcut = $this->getPageShortcut($originalShortcutPageOverlay['shortcut'], $originalShortcutPageOverlay['shortcut_mode'], $originalShortcutPageOverlay['uid']);
2742  $this->id = ($this->contentPid = $shortcut['uid']);
2743  $this->page = $this->sys_page->getPage($this->id);
2744  // Fix various effects on things like menus f.e.
2745  $this->fetch_the_id();
2746  $this->tmpl->rootLine = array_reverse($this->rootLine);
2747  }
2748  }
2749  }
2750 
2755  public function handleDataSubmission()
2756  {
2757  // Hook for processing data submission to extensions
2758  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkDataSubmission'])) {
2759  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkDataSubmission'] as $_classRef) {
2760  $_procObj = GeneralUtility::getUserObj($_classRef);
2761  $_procObj->checkDataSubmission($this);
2762  }
2763  }
2764  }
2765 
2771  public function initializeRedirectUrlHandlers()
2772  {
2773  if (
2774  empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlHandlers'])
2775  || !is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlHandlers'])
2776  ) {
2777  return;
2778  }
2779 
2780  $urlHandlers = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlHandlers'];
2781  foreach ($urlHandlers as $identifier => $configuration) {
2782  if (empty($configuration) || !is_array($configuration)) {
2783  throw new \RuntimeException('Missing configuration for URL handler "' . $identifier . '".', 1442052263);
2784  }
2785  if (!is_string($configuration['handler']) || empty($configuration['handler']) || !class_exists($configuration['handler']) || !is_subclass_of($configuration['handler'], UrlHandlerInterface::class)) {
2786  throw new \RuntimeException('The URL handler "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlHandlerInterface::class . '".', 1442052249);
2787  }
2788  }
2789 
2790  $orderedHandlers = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlHandlers);
2791 
2792  foreach ($orderedHandlers as $configuration) {
2794  $urlHandler = GeneralUtility::makeInstance($configuration['handler']);
2795  if ($urlHandler->canHandleCurrentUrl()) {
2796  $this->activeUrlHandlers[] = $urlHandler;
2797  }
2798  }
2799  }
2800 
2809  public function redirectToExternalUrl()
2810  {
2811  foreach ($this->activeUrlHandlers as $redirectHandler) {
2812  $redirectHandler->handle();
2813  }
2814 
2815  if (!empty($this->activeUrlHandlers)) {
2816  throw new \RuntimeException('A URL handler is active but did not process the URL.', 1442305505);
2817  }
2818  }
2819 
2826  public function setUrlIdToken()
2827  {
2828  if ($this->config['config']['ftu']) {
2829  $this->getMethodUrlIdToken = $GLOBALS['TYPO3_CONF_VARS']['FE']['get_url_id_token'];
2830  } else {
2831  $this->getMethodUrlIdToken = '';
2832  }
2833  }
2834 
2839  public function calculateLinkVars()
2840  {
2841  $this->linkVars = '';
2842  if (empty($this->config['config']['linkVars'])) {
2843  return;
2844  }
2845 
2846  $linkVars = $this->splitLinkVarsString((string)$this->config['config']['linkVars']);
2847 
2848  if (empty($linkVars)) {
2849  return;
2850  }
2851  $getData = GeneralUtility::_GET();
2852  foreach ($linkVars as $linkVar) {
2853  $test = ($value = '');
2854  if (preg_match('/^(.*)\\((.+)\\)$/', $linkVar, $match)) {
2855  $linkVar = trim($match[1]);
2856  $test = trim($match[2]);
2857  }
2858  if ($linkVar === '' || !isset($getData[$linkVar])) {
2859  continue;
2860  }
2861  if (!is_array($getData[$linkVar])) {
2862  $temp = rawurlencode($getData[$linkVar]);
2863  if ($test !== '' && !PageGenerator::isAllowedLinkVarValue($temp, $test)) {
2864  // Error: This value was not allowed for this key
2865  continue;
2866  }
2867  $value = '&' . $linkVar . '=' . $temp;
2868  } else {
2869  if ($test !== '' && $test !== 'array') {
2870  // Error: This key must not be an array!
2871  continue;
2872  }
2873  $value = GeneralUtility::implodeArrayForUrl($linkVar, $getData[$linkVar]);
2874  }
2875  $this->linkVars .= $value;
2876  }
2877  }
2878 
2886  protected function splitLinkVarsString(string $string): array
2887  {
2888  $tempCommaReplacementString = '###KASPER###';
2889 
2890  // replace every "," wrapped in "()" by a "unique" string
2891  $string = preg_replace_callback('/\((?>[^()]|(?R))*\)/', function ($result) use ($tempCommaReplacementString) {
2892  return str_replace(',', $tempCommaReplacementString, $result[0]);
2893  }, $string);
2894 
2895  $string = GeneralUtility::trimExplode(',', $string);
2896 
2897  // replace all "unique" strings back to ","
2898  return str_replace($tempCommaReplacementString, ',', $string);
2899  }
2900 
2908  {
2909  if (!empty($this->originalMountPointPage) && $this->originalMountPointPage['doktype'] == PageRepository::DOKTYPE_MOUNTPOINT) {
2910  $this->redirectToCurrentPage();
2911  }
2912  }
2913 
2921  {
2922  if (!empty($this->originalShortcutPage) && $this->originalShortcutPage['doktype'] == PageRepository::DOKTYPE_SHORTCUT) {
2923  $this->redirectToCurrentPage();
2924  }
2925  }
2926 
2931  protected function redirectToCurrentPage()
2932  {
2933  $this->calculateLinkVars();
2934  // Instantiate \TYPO3\CMS\Frontend\ContentObject to generate the correct target URL
2936  $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
2937  $parameter = $this->page['uid'];
2938  $type = GeneralUtility::_GET('type');
2940  $parameter .= ',' . $type;
2941  }
2942  $redirectUrl = $cObj->typoLink_URL(['parameter' => $parameter, 'addQueryString' => true,
2943  'addQueryString.' => ['exclude' => 'id']]);
2944 
2945  // Prevent redirection loop
2946  if (!empty($redirectUrl) && GeneralUtility::getIndpEnv('REQUEST_URI') !== '/' . $redirectUrl) {
2947  // redirect and exit
2949  }
2950  }
2951 
2952  /********************************************
2953  *
2954  * Page generation; cache handling
2955  *
2956  *******************************************/
2963  public function isGeneratePage()
2964  {
2965  return !$this->cacheContentFlag && empty($this->activeUrlHandlers);
2966  }
2967 
2971  public function realPageCacheContent()
2972  {
2973  // seconds until a cached page is too old
2974  $cacheTimeout = $this->get_cache_timeout();
2975  $timeOutTime = $GLOBALS['EXEC_TIME'] + $cacheTimeout;
2976  $usePageCache = true;
2977  // Hook for deciding whether page cache should be written to the cache backend or not
2978  // NOTE: as hooks are called in a loop, the last hook will have the final word (however each
2979  // hook receives the current status of the $usePageCache flag)
2980  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache'])) {
2981  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['usePageCache'] as $_classRef) {
2982  $_procObj = GeneralUtility::getUserObj($_classRef);
2983  $usePageCache = $_procObj->usePageCache($this, $usePageCache);
2984  }
2985  }
2986  // Write the page to cache, if necessary
2987  if ($usePageCache) {
2988  $this->setPageCacheContent($this->content, $this->config, $timeOutTime);
2989  }
2990  // Hook for cache post processing (eg. writing static files!)
2991  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['insertPageIncache'])) {
2992  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['insertPageIncache'] as $_classRef) {
2993  $_procObj = GeneralUtility::getUserObj($_classRef);
2994  $_procObj->insertPageIncache($this, $timeOutTime);
2995  }
2996  }
2997  }
2998 
3007  public function setPageCacheContent($content, $data, $expirationTstamp)
3008  {
3009  $cacheData = [
3010  'identifier' => $this->newHash,
3011  'page_id' => $this->id,
3012  'content' => $content,
3013  'cache_data' => $data,
3014  'expires' => $expirationTstamp,
3015  'tstamp' => $GLOBALS['EXEC_TIME'],
3016  'pageTitleInfo' => [
3017  'title' => $this->page['title'],
3018  'altPageTitle' => $this->altPageTitle,
3019  'indexedDocTitle' => $this->indexedDocTitle
3020  ]
3021  ];
3022  $this->cacheExpires = $expirationTstamp;
3023  $this->pageCacheTags[] = 'pageId_' . $cacheData['page_id'];
3024  if ($this->page_cache_reg1) {
3025  $reg1 = (int)$this->page_cache_reg1;
3026  $cacheData['reg1'] = $reg1;
3027  $this->pageCacheTags[] = 'reg1_' . $reg1;
3028  }
3029  if (!empty($this->page['cache_tags'])) {
3030  $tags = GeneralUtility::trimExplode(',', $this->page['cache_tags'], true);
3031  $this->pageCacheTags = array_merge($this->pageCacheTags, $tags);
3032  }
3033  $this->pageCache->set($this->newHash, $cacheData, $this->pageCacheTags, $expirationTstamp - $GLOBALS['EXEC_TIME']);
3034  }
3035 
3039  public function clearPageCacheContent()
3040  {
3041  $this->pageCache->remove($this->newHash);
3042  }
3043 
3049  public function clearPageCacheContent_pidList($pidList)
3050  {
3051  $pageIds = GeneralUtility::trimExplode(',', $pidList);
3052  foreach ($pageIds as $pageId) {
3053  $this->pageCache->flushByTag('pageId_' . (int)$pageId);
3054  }
3055  }
3056 
3063  public function setSysLastChanged()
3064  {
3065  // Draft workspaces are always uid 1 or more. We do not update SYS_LASTCHANGED if we are browsing page from one of theses workspaces
3066  if ((int)$this->whichWorkspace() < 1 && $this->page['SYS_LASTCHANGED'] < (int)$this->register['SYS_LASTCHANGED']) {
3067  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
3068  ->getConnectionForTable('pages');
3069  $connection->update(
3070  'pages',
3071  [
3072  'SYS_LASTCHANGED' => (int)$this->register['SYS_LASTCHANGED']
3073  ],
3074  [
3075  'uid' => (int)$this->id
3076  ]
3077  );
3078  }
3079  }
3080 
3086  public function releaseLocks()
3087  {
3088  $this->releaseLock('pagesection');
3089  $this->releaseLock('pages');
3090  }
3091 
3098  public function addCacheTags(array $tags)
3099  {
3100  $this->pageCacheTags = array_merge($this->pageCacheTags, $tags);
3101  }
3102 
3106  public function getPageCacheTags()
3107  {
3108  return $this->pageCacheTags;
3109  }
3110 
3111  /********************************************
3112  *
3113  * Page generation; rendering and inclusion
3114  *
3115  *******************************************/
3119  public function generatePage_preProcessing()
3120  {
3121  // Same codeline as in getFromCache(). But $this->all has been changed by
3122  // \TYPO3\CMS\Core\TypoScript\TemplateService::start() in the meantime, so this must be called again!
3123  $this->newHash = $this->getHash();
3124 
3125  // Setting cache_timeout_default. May be overridden by PHP include scripts.
3126  $this->cacheTimeOutDefault = (int)$this->config['config']['cache_period'];
3127  // Page is generated
3128  $this->no_cacheBeforePageGen = $this->no_cache;
3129  }
3130 
3136  {
3137  $this->getTimeTracker()->push('Prepare page content generation');
3138  if ($this->page['content_from_pid'] > 0) {
3139  // make REAL copy of TSFE object - not reference!
3140  $temp_copy_TSFE = clone $this;
3141  // Set ->id to the content_from_pid value - we are going to evaluate this pid as was it a given id for a page-display!
3142  $temp_copy_TSFE->id = $this->page['content_from_pid'];
3143  $temp_copy_TSFE->MP = '';
3144  $temp_copy_TSFE->getPageAndRootlineWithDomain($this->config['config']['content_from_pid_allowOutsideDomain'] ? 0 : $this->domainStartPage);
3145  $this->contentPid = (int)$temp_copy_TSFE->id;
3146  unset($temp_copy_TSFE);
3147  }
3148  if ($this->config['config']['MP_defaults']) {
3149  $temp_parts = GeneralUtility::trimExplode('|', $this->config['config']['MP_defaults'], true);
3150  foreach ($temp_parts as $temp_p) {
3151  list($temp_idP, $temp_MPp) = explode(':', $temp_p, 2);
3152  $temp_ids = GeneralUtility::intExplode(',', $temp_idP);
3153  foreach ($temp_ids as $temp_id) {
3154  $this->MP_defaults[$temp_id] = $temp_MPp;
3155  }
3156  }
3157  }
3158  // Global vars...
3159  $this->indexedDocTitle = $this->page['title'];
3160  $this->debug = !empty($this->config['config']['debug']);
3161  // Base url:
3162  if (isset($this->config['config']['baseURL'])) {
3163  $this->baseUrl = $this->config['config']['baseURL'];
3164  }
3165  // Internal and External target defaults
3166  $this->intTarget = '' . $this->config['config']['intTarget'];
3167  $this->extTarget = '' . $this->config['config']['extTarget'];
3168  $this->fileTarget = '' . $this->config['config']['fileTarget'];
3169  if ($this->config['config']['spamProtectEmailAddresses'] === 'ascii') {
3170  $this->spamProtectEmailAddresses = 'ascii';
3171  } else {
3172  $this->spamProtectEmailAddresses = MathUtility::forceIntegerInRange($this->config['config']['spamProtectEmailAddresses'], -10, 10, 0);
3173  }
3174  // calculate the absolute path prefix
3175  if (!empty($this->config['config']['absRefPrefix'])) {
3176  $absRefPrefix = trim($this->config['config']['absRefPrefix']);
3177  if ($absRefPrefix === 'auto') {
3178  $this->absRefPrefix = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
3179  } else {
3180  $this->absRefPrefix = $absRefPrefix;
3181  }
3182  } else {
3183  $this->absRefPrefix = '';
3184  }
3185  if ($this->type && $this->config['config']['frameReloadIfNotInFrameset']) {
3186  $this->logDeprecatedTyposcript(
3187  'config.frameReloadIfNotInFrameset',
3188  'frameReloadIfNotInFrameset has been marked as deprecated since TYPO3 v8, ' .
3189  'and will be removed in TYPO3 v9.'
3190  );
3191  $tdlLD = $this->tmpl->linkData($this->page, '_top', $this->no_cache, '');
3192  $this->additionalJavaScript['JSCode'] .= 'if(!parent.' . trim($this->sPre) . ' && !parent.view_frame) top.location.href="' . $this->baseUrlWrap($tdlLD['totalURL']) . '"';
3193  }
3194  $this->compensateFieldWidth = '' . $this->config['config']['compensateFieldWidth'];
3195  $this->lockFilePath = '' . $this->config['config']['lockFilePath'];
3196  $this->lockFilePath = $this->lockFilePath ?: $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'];
3197  if (isset($this->config['config']['noScaleUp'])) {
3198  $this->logDeprecatedTyposcript(
3199  'config.noScaleUp',
3200  'The TypoScript property "config.noScaleUp" is deprecated since TYPO3 v8 and will be removed in TYPO3 v9. ' .
3201  'Please use the global TYPO3 configuration setting "GFX/processor_allowUpscaling" instead.'
3202  );
3203  }
3204  $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_allowUpscaling'] = (bool)(isset($this->config['config']['noScaleUp']) ? !$this->config['config']['noScaleUp'] : $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_allowUpscaling']);
3205  $this->ATagParams = trim($this->config['config']['ATagParams']) ? ' ' . trim($this->config['config']['ATagParams']) : '';
3206  if ($this->config['config']['setJS_mouseOver']) {
3207  $this->logDeprecatedTyposcript(
3208  'config.setJS_mouseOver',
3209  'The TypoScript property "config.setJS_mouseOver" is deprecated since TYPO3 v8 and will be removed in TYPO3 v9. Please include the JavaScript snippet directly via TypoScript page.jsInline.'
3210  );
3211  $this->setJS('mouseOver');
3212  }
3213  if ($this->config['config']['setJS_openPic']) {
3214  $this->logDeprecatedTyposcript(
3215  'config.setJS_openPic',
3216  'The TypoScript property "config.setJS_openPic" is deprecated since TYPO3 v8 and will be removed in TYPO3 v9. Please include the JavaScript snippet directly via TypoScript page.jsInline.'
3217  );
3218  $this->setJS('openPic');
3219  }
3221  // linkVars
3222  $this->calculateLinkVars();
3223  // dtdAllowsFrames indicates whether to use the target attribute in links
3224  $this->dtdAllowsFrames = false;
3225  if ($this->config['config']['doctype']) {
3226  if (in_array(
3227  (string)$this->config['config']['doctype'],
3228  ['xhtml_trans', 'xhtml_frames', 'xhtml_basic', 'html5'],
3229  true
3230  )
3231  ) {
3232  $this->dtdAllowsFrames = true;
3233  }
3234  } else {
3235  $this->dtdAllowsFrames = true;
3236  }
3237  // Setting XHTML-doctype from doctype
3238  if (!$this->config['config']['xhtmlDoctype']) {
3239  $this->config['config']['xhtmlDoctype'] = $this->config['config']['doctype'];
3240  }
3241  if ($this->config['config']['xhtmlDoctype']) {
3242  $this->xhtmlDoctype = $this->config['config']['xhtmlDoctype'];
3243  // Checking XHTML-docytpe
3244  switch ((string)$this->config['config']['xhtmlDoctype']) {
3245  case 'xhtml_trans':
3246  case 'xhtml_strict':
3247  $this->xhtmlVersion = 100;
3248  break;
3249  case 'xhtml_frames':
3250  $this->logDeprecatedTyposcript(
3251  'config.xhtmlDoctype=frames',
3252  'xhtmlDoctype = xhtml_frames and doctype = xhtml_frames have been marked as deprecated since TYPO3 v8, ' .
3253  'and will be removed in TYPO3 v9.'
3254  );
3255  $this->xhtmlVersion = 100;
3256  break;
3257  case 'xhtml_basic':
3258  $this->xhtmlVersion = 105;
3259  break;
3260  case 'xhtml_11':
3261  case 'xhtml+rdfa_10':
3262  $this->xhtmlVersion = 110;
3263  break;
3264  default:
3265  $this->pageRenderer->setRenderXhtml(false);
3266  $this->xhtmlDoctype = '';
3267  $this->xhtmlVersion = 0;
3268  }
3269  } else {
3270  $this->pageRenderer->setRenderXhtml(false);
3271  }
3272 
3273  // Global content object
3274  $this->newCObj();
3275  $this->getTimeTracker()->pull();
3276  }
3277 
3285  protected function initializeSearchWordDataInTsfe()
3286  {
3287  $this->sWordRegEx = '';
3288  $this->sWordList = GeneralUtility::_GP('sword_list');
3289  if (is_array($this->sWordList)) {
3290  $space = !empty($this->config['config']['sword_standAlone']) ? '[[:space:]]' : '';
3291  foreach ($this->sWordList as $val) {
3292  if (trim($val) !== '') {
3293  $this->sWordRegEx .= $space . preg_quote($val, '/') . $space . '|';
3294  }
3295  }
3296  $this->sWordRegEx = rtrim($this->sWordRegEx, '|');
3297  }
3298  }
3299 
3307  public function generatePage_whichScript()
3308  {
3309  if (!$GLOBALS['TYPO3_CONF_VARS']['FE']['noPHPscriptInclude'] && $this->config['config']['pageGenScript']) {
3311  return $this->tmpl->getFileName($this->config['config']['pageGenScript']);
3312  }
3313  return null;
3314  }
3315 
3321  {
3322  // 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.
3323  if ($this->no_cacheBeforePageGen) {
3324  $this->set_no_cache('no_cache has been set before the page was generated - safety check', true);
3325  }
3326  // Hook for post-processing of page content cached/non-cached:
3327  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'])) {
3328  $_params = ['pObj' => &$this];
3329  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'] as $_funcRef) {
3330  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3331  }
3332  }
3333  // Processing if caching is enabled:
3334  if (!$this->no_cache) {
3335  // Hook for post-processing of page content before being cached:
3336  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached'])) {
3337  $_params = ['pObj' => &$this];
3338  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-cached'] as $_funcRef) {
3339  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3340  }
3341  }
3342  }
3343  // Convert char-set for output: (should be BEFORE indexing of the content (changed 22/4 2005)),
3344  // because otherwise indexed search might convert from the wrong charset!
3345  // One thing is that the charset mentioned in the HTML header would be wrong since the output charset (metaCharset)
3346  // has not been converted to from utf-8. And indexed search will internally convert from metaCharset
3347  // to utf-8 so the content MUST be in metaCharset already!
3348  $this->content = $this->convOutputCharset($this->content);
3349  // Hook for indexing pages
3350  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageIndexing'])) {
3351  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageIndexing'] as $_classRef) {
3352  $_procObj = GeneralUtility::getUserObj($_classRef);
3353  $_procObj->hook_indexContent($this);
3354  }
3355  }
3356  // Storing for cache:
3357  if (!$this->no_cache) {
3358  $this->realPageCacheContent();
3359  }
3360  // Sets sys-last-change:
3361  $this->setSysLastChanged();
3362  }
3363 
3367  protected function regeneratePageTitle()
3368  {
3369  PageGenerator::generatePageTitle();
3370  }
3371 
3375  public function INTincScript()
3376  {
3377  // Deprecated stuff:
3378  // @deprecated: annotation added TYPO3 4.6
3379  $this->additionalHeaderData = is_array($this->config['INTincScript_ext']['additionalHeaderData']) ? $this->config['INTincScript_ext']['additionalHeaderData'] : [];
3380  $this->additionalFooterData = is_array($this->config['INTincScript_ext']['additionalFooterData']) ? $this->config['INTincScript_ext']['additionalFooterData'] : [];
3381  $this->additionalJavaScript = $this->config['INTincScript_ext']['additionalJavaScript'];
3382  $this->additionalCSS = $this->config['INTincScript_ext']['additionalCSS'];
3383  $this->divSection = '';
3384  if (empty($this->config['INTincScript_ext']['pageRenderer'])) {
3385  $this->initPageRenderer();
3386  } else {
3388  $pageRenderer = unserialize($this->config['INTincScript_ext']['pageRenderer']);
3389  $this->pageRenderer = $pageRenderer;
3391  }
3392 
3394  $this->getTimeTracker()->push('Substitute header section');
3395  $this->INTincScript_loadJSCode();
3396  $this->regeneratePageTitle();
3397 
3398  $this->content = str_replace(
3399  [
3400  '<!--HD_' . $this->config['INTincScript_ext']['divKey'] . '-->',
3401  '<!--FD_' . $this->config['INTincScript_ext']['divKey'] . '-->',
3402  '<!--TDS_' . $this->config['INTincScript_ext']['divKey'] . '-->'
3403  ],
3404  [
3405  $this->convOutputCharset(implode(LF, $this->additionalHeaderData)),
3406  $this->convOutputCharset(implode(LF, $this->additionalFooterData)),
3407  $this->convOutputCharset($this->divSection),
3408  ],
3409  $this->pageRenderer->renderJavaScriptAndCssForProcessingOfUncachedContentObjects($this->content, $this->config['INTincScript_ext']['divKey'])
3410  );
3411  // Replace again, because header and footer data and page renderer replacements may introduce additional placeholders (see #44825)
3413  $this->setAbsRefPrefix();
3414  $this->getTimeTracker()->pull();
3415  }
3416 
3423  {
3424  do {
3425  $INTiS_config = $this->config['INTincScript'];
3426  $this->INTincScript_process($INTiS_config);
3427  // Check if there were new items added to INTincScript during the previous execution:
3428  $INTiS_config = array_diff_assoc($this->config['INTincScript'], $INTiS_config);
3429  $reprocess = count($INTiS_config) > 0;
3430  } while ($reprocess);
3431  }
3432 
3439  protected function INTincScript_process($INTiS_config)
3440  {
3441  $timeTracker = $this->getTimeTracker();
3442  $timeTracker->push('Split content');
3443  // Splits content with the key.
3444  $INTiS_splitC = explode('<!--INT_SCRIPT.', $this->content);
3445  $this->content = '';
3446  $timeTracker->setTSlogMessage('Parts: ' . count($INTiS_splitC));
3447  $timeTracker->pull();
3448  foreach ($INTiS_splitC as $INTiS_c => $INTiS_cPart) {
3449  // If the split had a comment-end after 32 characters it's probably a split-string
3450  if (substr($INTiS_cPart, 32, 3) === '-->') {
3451  $INTiS_key = 'INT_SCRIPT.' . substr($INTiS_cPart, 0, 32);
3452  if (is_array($INTiS_config[$INTiS_key])) {
3453  $label = 'Include ' . $INTiS_config[$INTiS_key]['type'];
3454  $label = $label . isset($INTiS_config[$INTiS_key]['file']) ? ' ' . $INTiS_config[$INTiS_key]['file'] : '';
3455  $timeTracker->push($label, '');
3456  $incContent = '';
3457  $INTiS_cObj = unserialize($INTiS_config[$INTiS_key]['cObj']);
3458  /* @var $INTiS_cObj ContentObjectRenderer */
3459  switch ($INTiS_config[$INTiS_key]['type']) {
3460  case 'COA':
3461  $incContent = $INTiS_cObj->cObjGetSingle('COA', $INTiS_config[$INTiS_key]['conf']);
3462  break;
3463  case 'FUNC':
3464  $incContent = $INTiS_cObj->cObjGetSingle('USER', $INTiS_config[$INTiS_key]['conf']);
3465  break;
3466  case 'POSTUSERFUNC':
3467  $incContent = $INTiS_cObj->callUserFunction($INTiS_config[$INTiS_key]['postUserFunc'], $INTiS_config[$INTiS_key]['conf'], $INTiS_config[$INTiS_key]['content']);
3468  break;
3469  }
3470  $this->content .= $this->convOutputCharset($incContent);
3471  $this->content .= substr($INTiS_cPart, 35);
3472  $timeTracker->pull($incContent);
3473  } else {
3474  $this->content .= substr($INTiS_cPart, 35);
3475  }
3476  } else {
3477  $this->content .= ($INTiS_c ? '<!--INT_SCRIPT.' : '') . $INTiS_cPart;
3478  }
3479  }
3480  }
3481 
3485  public function INTincScript_loadJSCode()
3486  {
3487  // Add javascript
3488  $jsCode = trim($this->JSCode);
3489  $additionalJavaScript = is_array($this->additionalJavaScript)
3490  ? implode(LF, $this->additionalJavaScript)
3493  if ($jsCode !== '' || $additionalJavaScript !== '') {
3494  $this->additionalHeaderData['JSCode'] = '
3495 <script type="text/javascript">
3496  /*<![CDATA[*/
3497 <!--
3498 ' . $additionalJavaScript . '
3499 ' . $jsCode . '
3500 // -->
3501  /*]]>*/
3502 </script>';
3503  }
3504  // Add CSS
3505  $additionalCss = is_array($this->additionalCSS) ? implode(LF, $this->additionalCSS) : $this->additionalCSS;
3506  $additionalCss = trim($additionalCss);
3507  if ($additionalCss !== '') {
3508  $this->additionalHeaderData['_CSS'] = '
3509 <style type="text/css">
3510 ' . $additionalCss . '
3511 </style>';
3512  }
3513  }
3514 
3520  public function isINTincScript()
3521  {
3522  return is_array($this->config['INTincScript']) && empty($this->activeUrlHandlers);
3523  }
3524 
3525  /********************************************
3526  *
3527  * Finished off; outputting, storing session data, statistics...
3528  *
3529  *******************************************/
3536  public function isOutputting()
3537  {
3538  // Initialize by status if there is a Redirect URL
3539  $enableOutput = empty($this->activeUrlHandlers);
3540  // Call hook for possible disabling of output:
3541  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['isOutputting']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['isOutputting'])) {
3542  $_params = ['pObj' => &$this, 'enableOutput' => &$enableOutput];
3543  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['isOutputting'] as $_funcRef) {
3544  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3545  }
3546  }
3547  return $enableOutput;
3548  }
3549 
3557  public function processOutput()
3558  {
3559  // Set header for charset-encoding unless disabled
3560  if (empty($this->config['config']['disableCharsetHeader'])) {
3561  $headLine = 'Content-Type: ' . $this->contentType . '; charset=' . trim($this->metaCharset);
3562  header($headLine);
3563  }
3564  // Set header for content language unless disabled
3565  if (empty($this->config['config']['disableLanguageHeader']) && !empty($this->sys_language_isocode)) {
3566  $headLine = 'Content-Language: ' . trim($this->sys_language_isocode);
3567  header($headLine);
3568  }
3569  // Set cache related headers to client (used to enable proxy / client caching!)
3570  if (!empty($this->config['config']['sendCacheHeaders'])) {
3571  $this->sendCacheHeaders();
3572  }
3573  // Set headers, if any
3574  if (is_array($this->config['config']['additionalHeaders.'])) {
3575  ksort($this->config['config']['additionalHeaders.']);
3576  foreach ($this->config['config']['additionalHeaders.'] as $options) {
3577  header(
3578  trim($options['header']),
3579  // "replace existing headers" is turned on by default, unless turned off
3580  ($options['replace'] !== '0'),
3581  ((int)$options['httpResponseCode'] ?: null)
3582  );
3583  }
3584  }
3585  // Make substitution of eg. username/uid in content only if cache-headers for client/proxy caching is NOT sent!
3586  if (!$this->isClientCachable) {
3587  $this->contentStrReplace();
3588  }
3589  // Hook for post-processing of page content before output:
3590  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-output']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-output'])) {
3591  $_params = ['pObj' => &$this];
3592  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-output'] as $_funcRef) {
3593  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3594  }
3595  }
3596  }
3597 
3604  public function sendCacheHeaders()
3605  {
3606  // Getting status whether we can send cache control headers for proxy caching:
3607  $doCache = $this->isStaticCacheble();
3608  // 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...
3609  $loginsDeniedCfg = empty($this->config['config']['sendCacheHeaders_onlyWhenLoginDeniedInBranch']) || empty($this->loginAllowedInBranch);
3610  // Finally, when backend users are logged in, do not send cache headers at all (Admin Panel might be displayed for instance).
3611  if ($doCache && !$this->beUserLogin && !$this->doWorkspacePreview() && $loginsDeniedCfg) {
3612  // Build headers:
3613  $headers = [
3614  'Expires: ' . gmdate('D, d M Y H:i:s T', $this->cacheExpires),
3615  'ETag: "' . md5($this->content) . '"',
3616  'Cache-Control: max-age=' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']),
3617  // no-cache
3618  'Pragma: public'
3619  ];
3620  $this->isClientCachable = true;
3621  } else {
3622  // Build headers
3623  // "no-store" is used to ensure that the client HAS to ask the server every time, and is not allowed to store anything at all
3624  $headers = [
3625  'Cache-Control: private, no-store'
3626  ];
3627  $this->isClientCachable = false;
3628  // Now, if a backend user is logged in, tell him in the Admin Panel log what the caching status would have been:
3629  if ($this->beUserLogin) {
3630  if ($doCache) {
3631  $this->getTimeTracker()->setTSlogMessage('Cache-headers with max-age "' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']) . '" would have been sent');
3632  } else {
3633  $reasonMsg = '';
3634  $reasonMsg .= !$this->no_cache ? '' : 'Caching disabled (no_cache). ';
3635  $reasonMsg .= !$this->isINTincScript() ? '' : '*_INT object(s) on page. ';
3636  $reasonMsg .= !is_array($this->fe_user->user) ? '' : 'Frontend user logged in. ';
3637  $this->getTimeTracker()->setTSlogMessage('Cache-headers would disable proxy caching! Reason(s): "' . $reasonMsg . '"', 1);
3638  }
3639  }
3640  }
3641  // Send headers:
3642  foreach ($headers as $hL) {
3643  header($hL);
3644  }
3645  }
3646 
3657  public function isStaticCacheble()
3658  {
3659  $doCache = !$this->no_cache && !$this->isINTincScript() && !$this->isUserOrGroupSet();
3660  return $doCache;
3661  }
3662 
3666  public function contentStrReplace()
3667  {
3668  $search = [];
3669  $replace = [];
3670  // Substitutes username mark with the username
3671  if (!empty($this->fe_user->user['uid'])) {
3672  // User name:
3673  $token = isset($this->config['config']['USERNAME_substToken']) ? trim($this->config['config']['USERNAME_substToken']) : '';
3674  $search[] = $token ? $token : '<!--###USERNAME###-->';
3675  $replace[] = htmlspecialchars($this->fe_user->user['username']);
3676  // User uid (if configured):
3677  $token = isset($this->config['config']['USERUID_substToken']) ? trim($this->config['config']['USERUID_substToken']) : '';
3678  if ($token) {
3679  $search[] = $token;
3680  $replace[] = $this->fe_user->user['uid'];
3681  }
3682  }
3683  // Substitutes get_URL_ID in case of GET-fallback
3684  if ($this->getMethodUrlIdToken) {
3685  $search[] = $this->getMethodUrlIdToken;
3686  $replace[] = $this->fe_user->get_URL_ID;
3687  }
3688  // Hook for supplying custom search/replace data
3689  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-contentStrReplace'])) {
3690  $contentStrReplaceHooks = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-contentStrReplace'];
3691  if (is_array($contentStrReplaceHooks)) {
3692  $_params = [
3693  'search' => &$search,
3694  'replace' => &$replace
3695  ];
3696  foreach ($contentStrReplaceHooks as $_funcRef) {
3697  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3698  }
3699  }
3700  }
3701  if (!empty($search)) {
3702  $this->content = str_replace($search, $replace, $this->content);
3703  }
3704  }
3705 
3709  public function storeSessionData()
3710  {
3711  $this->fe_user->storeSessionData();
3712  }
3713 
3720  public function setParseTime()
3721  {
3723  // Compensates for the time consumed with Back end user initialization.
3724  $microtime_start = isset($GLOBALS['TYPO3_MISC']['microtime_start']) ? $GLOBALS['TYPO3_MISC']['microtime_start'] : null;
3725  $microtime_end = isset($GLOBALS['TYPO3_MISC']['microtime_end']) ? $GLOBALS['TYPO3_MISC']['microtime_end'] : null;
3726  $microtime_BE_USER_start = isset($GLOBALS['TYPO3_MISC']['microtime_BE_USER_start']) ? $GLOBALS['TYPO3_MISC']['microtime_BE_USER_start'] : null;
3727  $microtime_BE_USER_end = isset($GLOBALS['TYPO3_MISC']['microtime_BE_USER_end']) ? $GLOBALS['TYPO3_MISC']['microtime_BE_USER_end'] : null;
3728  $timeTracker = $this->getTimeTracker();
3729  $this->scriptParseTime = $timeTracker->getMilliseconds($microtime_end) - $timeTracker->getMilliseconds($microtime_start) - ($timeTracker->getMilliseconds($microtime_BE_USER_end) - $timeTracker->getMilliseconds($microtime_BE_USER_start));
3730  }
3731 
3735  public function previewInfo()
3736  {
3737  if ($this->fePreview !== 0) {
3738  $previewInfo = '';
3739  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_previewInfo']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_previewInfo'])) {
3740  $_params = ['pObj' => &$this];
3741  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_previewInfo'] as $_funcRef) {
3742  $previewInfo .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3743  }
3744  }
3745  $this->content = str_ireplace('</body>', $previewInfo . '</body>', $this->content);
3746  }
3747  }
3748 
3752  public function hook_eofe()
3753  {
3754  // Call hook for end-of-frontend processing:
3755  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe'])) {
3756  $_params = ['pObj' => &$this];
3757  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe'] as $_funcRef) {
3758  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
3759  }
3760  }
3761  }
3762 
3769  public function beLoginLinkIPList()
3770  {
3772  if (!empty($this->config['config']['beLoginLinkIPList'])) {
3773  if (GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $this->config['config']['beLoginLinkIPList'])) {
3774  $label = !$this->beUserLogin ? $this->config['config']['beLoginLinkIPList_login'] : $this->config['config']['beLoginLinkIPList_logout'];
3775  if ($label) {
3776  if (!$this->beUserLogin) {
3777  $link = '<a href="' . htmlspecialchars((TYPO3_mainDir . 'index.php?redirect_url=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI')))) . '">' . $label . '</a>';
3778  } else {
3779  $link = '<a href="' . htmlspecialchars((TYPO3_mainDir . 'index.php?L=OUT&redirect_url=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI')))) . '">' . $label . '</a>';
3780  }
3781  return $link;
3782  }
3783  }
3784  }
3785  return '';
3786  }
3787 
3791  public function addTempContentHttpHeaders()
3792  {
3793  header('HTTP/1.0 503 Service unavailable');
3794  header('Retry-after: 3600');
3795  header('Pragma: no-cache');
3796  header('Cache-control: no-cache');
3797  header('Expire: 0');
3798  }
3799 
3800  /********************************************
3801  *
3802  * Various internal API functions
3803  *
3804  *******************************************/
3816  public function encryptCharcode($n, $start, $end, $offset)
3817  {
3819  $n = $n + $offset;
3820  if ($offset > 0 && $n > $end) {
3821  $n = $start + ($n - $end - 1);
3822  } elseif ($offset < 0 && $n < $start) {
3823  $n = $end - ($start - $n - 1);
3824  }
3825  return chr($n);
3826  }
3827 
3836  public function encryptEmail($string, $back = false)
3837  {
3839  $out = '';
3840  // obfuscates using the decimal HTML entity references for each character
3841  if ($this->spamProtectEmailAddresses === 'ascii') {
3842  $stringLength = strlen($string);
3843  for ($a = 0; $a < $stringLength; $a++) {
3844  $out .= '&#' . ord(substr($string, $a, 1)) . ';';
3845  }
3846  } else {
3847  // like str_rot13() but with a variable offset and a wider character range
3848  $len = strlen($string);
3849  $offset = (int)$this->spamProtectEmailAddresses * ($back ? -1 : 1);
3850  for ($i = 0; $i < $len; $i++) {
3851  $charValue = ord($string[$i]);
3852  // 0-9 . , - + / :
3853  if ($charValue >= 43 && $charValue <= 58) {
3854  $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
3855  } elseif ($charValue >= 64 && $charValue <= 90) {
3856  // A-Z @
3857  $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
3858  } elseif ($charValue >= 97 && $charValue <= 122) {
3859  // a-z
3860  $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
3861  } else {
3862  $out .= $string[$i];
3863  }
3864  }
3865  }
3866  return $out;
3867  }
3868 
3875  public function newCObj()
3876  {
3877  $this->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
3878  $this->cObj->start($this->page, 'pages');
3879  }
3880 
3887  public function setAbsRefPrefix()
3888  {
3889  if (!$this->absRefPrefix) {
3890  return;
3891  }
3892  $search = [
3893  '"typo3temp/',
3894  '"typo3conf/ext/',
3895  '"' . TYPO3_mainDir . 'ext/',
3896  '"' . TYPO3_mainDir . 'sysext/'
3897  ];
3898  $replace = [
3899  '"' . $this->absRefPrefix . 'typo3temp/',
3900  '"' . $this->absRefPrefix . 'typo3conf/ext/',
3901  '"' . $this->absRefPrefix . TYPO3_mainDir . 'ext/',
3902  '"' . $this->absRefPrefix . TYPO3_mainDir . 'sysext/'
3903  ];
3905  $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
3906  $storages = $storageRepository->findAll();
3907  foreach ($storages as $storage) {
3908  if ($storage->getDriverType() === 'Local' && $storage->isPublic() && $storage->isOnline()) {
3909  $folder = $storage->getPublicUrl($storage->getRootLevelFolder(), true);
3910  $search[] = '"' . $folder;
3911  $replace[] = '"' . $this->absRefPrefix . $folder;
3912  }
3913  }
3914  // Process additional directories
3915  $directories = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['additionalAbsRefPrefixDirectories'], true);
3916  foreach ($directories as $directory) {
3917  $search[] = '"' . $directory;
3918  $replace[] = '"' . $this->absRefPrefix . $directory;
3919  }
3920  $this->content = str_replace(
3921  $search,
3922  $replace,
3923  $this->content
3924  );
3925  }
3926 
3934  public function baseUrlWrap($url)
3935  {
3936  if ($this->baseUrl) {
3937  $urlParts = parse_url($url);
3938  if (empty($urlParts['scheme']) && $url[0] !== '/') {
3939  $url = $this->baseUrl . $url;
3940  }
3941  }
3942  return $url;
3943  }
3944 
3953  public function logDeprecatedTyposcript($typoScriptProperty, $explanation = '')
3954  {
3955  $explanationText = $explanation !== '' ? ' - ' . $explanation : '';
3956  $this->getTimeTracker()->setTSlogMessage($typoScriptProperty . ' is deprecated.' . $explanationText, 2);
3957  GeneralUtility::deprecationLog('TypoScript ' . $typoScriptProperty . ' is deprecated' . $explanationText);
3958  }
3959 
3966  public function updateMD5paramsRecord($hash)
3967  {
3968  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
3969  ->getConnectionForTable('cache_md5params');
3970  $connection->update(
3971  'cache_md5params',
3972  [
3973  'tstamp' => $GLOBALS['EXEC_TIME']
3974  ],
3975  [
3976  'md5hash' => $hash
3977  ]
3978  );
3979  }
3980 
3981  /********************************************
3982  * PUBLIC ACCESSIBLE WORKSPACES FUNCTIONS
3983  *******************************************/
3984 
3990  public function doWorkspacePreview()
3991  {
3992  return $this->workspacePreview !== 0;
3993  }
3994 
4001  public function whichWorkspace($returnTitle = false)
4002  {
4003  $ws = null;
4004  if ($this->doWorkspacePreview()) {
4005  $ws = (int)$this->workspacePreview;
4006  } elseif ($this->beUserLogin) {
4007  $ws = $this->getBackendUser()->workspace;
4008  }
4009  if ($ws && $returnTitle) {
4010  GeneralUtility::deprecationLog('The parameter $returnTitle of $TSFE->whichWorkspace() is marked as deprecated and has no effect anymore. It will be removed in TYPO3 v9.');
4011  if (ExtensionManagementUtility::isLoaded('workspaces')) {
4012  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
4013  ->getQueryBuilderForTable('sys_workspace');
4014 
4015  $queryBuilder->getRestrictions()->removeAll();
4016 
4017  $row = $queryBuilder
4018  ->select('title')
4019  ->from('sys_workspace')
4020  ->where(
4021  $queryBuilder->expr()->eq(
4022  'uid',
4023  $queryBuilder->createNamedParameter($ws, \PDO::PARAM_INT)
4024  )
4025  )
4026  ->execute()
4027  ->fetch();
4028 
4029  if ($row) {
4030  return $row['title'];
4031  }
4032  }
4033  }
4034  return $ws;
4035  }
4036 
4043  public function includeLibraries(array $libraries)
4044  {
4046  $timeTracker = $this->getTimeTracker();
4047  $timeTracker->push('Include libraries');
4048  $timeTracker->setTSlogMessage('Files for inclusion: "' . implode(', ', $libraries) . '"');
4049  foreach ($libraries as $library) {
4050  $file = $this->tmpl->getFileName($library);
4051  if ($file) {
4052  include_once './' . $file;
4053  } else {
4054  $timeTracker->setTSlogMessage('Include file "' . $file . '" did not exist!', 2);
4055  }
4056  }
4057  $timeTracker->pull();
4058  }
4059 
4060  /********************************************
4061  *
4062  * Various external API functions - for use in plugins etc.
4063  *
4064  *******************************************/
4065 
4071  public function getPagesTSconfig()
4072  {
4073  if (!is_array($this->pagesTSconfig)) {
4074  $TSdataArray = [];
4075  foreach ($this->rootLine as $k => $v) {
4076  // add TSconfig first, as $TSdataArray is reversed below and it shall be included last
4077  $TSdataArray[] = $v['TSconfig'];
4078  if (trim($v['tsconfig_includes'])) {
4079  $includeTsConfigFileList = GeneralUtility::trimExplode(',', $v['tsconfig_includes'], true);
4080  // reverse the includes first to make sure their order is preserved when $TSdataArray is reversed
4081  $includeTsConfigFileList = array_reverse($includeTsConfigFileList);
4082  // Traversing list
4083  foreach ($includeTsConfigFileList as $includeTsConfigFile) {
4084  if (strpos($includeTsConfigFile, 'EXT:') === 0) {
4085  list($includeTsConfigFileExtensionKey, $includeTsConfigFilename) = explode(
4086  '/',
4087  substr($includeTsConfigFile, 4),
4088  2
4089  );
4090  if ((string)$includeTsConfigFileExtensionKey !== ''
4091  && (string)$includeTsConfigFilename !== ''
4092  && ExtensionManagementUtility::isLoaded($includeTsConfigFileExtensionKey)
4093  ) {
4094  $extensionPath = ExtensionManagementUtility::extPath($includeTsConfigFileExtensionKey);
4095  $includeTsConfigFileAndPath = PathUtility::getCanonicalPath($extensionPath . $includeTsConfigFilename);
4096  if (strpos($includeTsConfigFileAndPath, $extensionPath) === 0 && file_exists($includeTsConfigFileAndPath)) {
4097  $TSdataArray[] = file_get_contents($includeTsConfigFileAndPath);
4098  }
4099  }
4100  }
4101  }
4102  }
4103  }
4104  // Adding the default configuration:
4105  $TSdataArray[] = $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'];
4106  // Bring everything in the right order. Default first, then the Rootline down to the current page
4107  $TSdataArray = array_reverse($TSdataArray);
4108  // Parsing the user TS (or getting from cache)
4109  $TSdataArray = TypoScriptParser::checkIncludeLines_array($TSdataArray);
4110  $userTS = implode(LF . '[GLOBAL]' . LF, $TSdataArray);
4111  $identifier = md5('pageTS:' . $userTS);
4112  $contentHashCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
4113  $this->pagesTSconfig = $contentHashCache->get($identifier);
4114  if (!is_array($this->pagesTSconfig)) {
4115  $parseObj = GeneralUtility::makeInstance(TypoScriptParser::class);
4116  $parseObj->parse($userTS);
4117  $this->pagesTSconfig = $parseObj->setup;
4118  $contentHashCache->set($identifier, $this->pagesTSconfig, ['PAGES_TSconfig'], 0);
4119  }
4120  }
4121  return $this->pagesTSconfig;
4122  }
4123 
4131  public function setJS($key, $content = '')
4132  {
4133  if ($key) {
4134  switch ($key) {
4135  case 'mouseOver':
4136  $this->additionalJavaScript[$key] = ' // JS function for mouse-over
4137  function over(name, imgObj) { //
4138  if (document[name]) {document[name].src = eval(name+"_h.src");}
4139  else if (document.getElementById && document.getElementById(name)) {document.getElementById(name).src = eval(name+"_h.src");}
4140  else if (imgObj) {imgObj.src = eval(name+"_h.src");}
4141  }
4142  // JS function for mouse-out
4143  function out(name, imgObj) { //
4144  if (document[name]) {document[name].src = eval(name+"_n.src");}
4145  else if (document.getElementById && document.getElementById(name)) {document.getElementById(name).src = eval(name+"_n.src");}
4146  else if (imgObj) {imgObj.src = eval(name+"_n.src");}
4147  }';
4148  break;
4149  case 'openPic':
4150  $this->additionalJavaScript[$key] = ' function openPic(url, winName, winParams) { //
4151  var theWindow = window.open(url, winName, winParams);
4152  if (theWindow) {theWindow.focus();}
4153  }';
4154  break;
4155  default:
4156  $this->additionalJavaScript[$key] = $content;
4157  }
4158  }
4159  }
4160 
4168  public function setCSS($key, $content)
4169  {
4170  if ($key) {
4171  $this->additionalCSS[$key] = $content;
4172  }
4173  }
4174 
4182  public function uniqueHash($str = '')
4183  {
4184  return md5($this->uniqueString . '_' . $str . $this->uniqueCounter++);
4185  }
4186 
4193  public function set_no_cache($reason = '', $internal = false)
4194  {
4195  if ($internal && isset($GLOBALS['BE_USER'])) {
4197  } else {
4199  }
4200 
4201  if ($reason !== '') {
4202  $warning = '$TSFE->set_no_cache() was triggered. Reason: ' . $reason . '.';
4203  } else {
4204  $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
4205  // This is a hack to work around ___FILE___ resolving symbolic links
4206  $PATH_site_real = dirname(realpath(PATH_site . 'typo3')) . '/';
4207  $file = $trace[0]['file'];
4208  if (strpos($file, $PATH_site_real) === 0) {
4209  $file = str_replace($PATH_site_real, '', $file);
4210  } else {
4211  $file = str_replace(PATH_site, '', $file);
4212  }
4213  $line = $trace[0]['line'];
4214  $trigger = $file . ' on line ' . $line;
4215  $warning = '$GLOBALS[\'TSFE\']->set_no_cache() was triggered by ' . $trigger . '.';
4216  }
4217  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter']) {
4218  $warning .= ' However, $TYPO3_CONF_VARS[\'FE\'][\'disableNoCacheParameter\'] is set, so it will be ignored!';
4219  $this->getTimeTracker()->setTSlogMessage($warning, 2);
4220  } else {
4221  $warning .= ' Caching is disabled!';
4222  $this->disableCache();
4223  }
4224  GeneralUtility::sysLog($warning, 'cms', $severity);
4225  }
4226 
4232  protected function disableCache()
4233  {
4234  $this->no_cache = true;
4235  }
4236 
4242  public function set_cache_timeout_default($seconds)
4243  {
4244  $this->cacheTimeOutDefault = (int)$seconds;
4245  }
4246 
4252  public function get_cache_timeout()
4253  {
4255  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
4256  $cachedCacheLifetimeIdentifier = 'core-tslib_fe-get_cache_timeout';
4257  $cachedCacheLifetime = $runtimeCache->get($cachedCacheLifetimeIdentifier);
4258  if ($cachedCacheLifetime === false) {
4259  if ($this->page['cache_timeout']) {
4260  // Cache period was set for the page:
4261  $cacheTimeout = $this->page['cache_timeout'];
4262  } elseif ($this->cacheTimeOutDefault) {
4263  // Cache period was set for the whole site:
4264  $cacheTimeout = $this->cacheTimeOutDefault;
4265  } else {
4266  // No cache period set at all, so we take one day (60*60*24 seconds = 86400 seconds):
4267  $cacheTimeout = 86400;
4268  }
4269  if ($this->config['config']['cache_clearAtMidnight']) {
4270  $timeOutTime = $GLOBALS['EXEC_TIME'] + $cacheTimeout;
4271  $midnightTime = mktime(0, 0, 0, date('m', $timeOutTime), date('d', $timeOutTime), date('Y', $timeOutTime));
4272  // If the midnight time of the expire-day is greater than the current time,
4273  // we may set the timeOutTime to the new midnighttime.
4274  if ($midnightTime > $GLOBALS['EXEC_TIME']) {
4275  $cacheTimeout = $midnightTime - $GLOBALS['EXEC_TIME'];
4276  }
4277  }
4278 
4279  // Calculate the timeout time for records on the page and adjust cache timeout if necessary
4280  $cacheTimeout = min($this->calculatePageCacheTimeout(), $cacheTimeout);
4281 
4282  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['get_cache_timeout'])) {
4283  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['get_cache_timeout'] as $_funcRef) {
4284  $params = ['cacheTimeout' => $cacheTimeout];
4285  $cacheTimeout = GeneralUtility::callUserFunction($_funcRef, $params, $this);
4286  }
4287  }
4288  $runtimeCache->set($cachedCacheLifetimeIdentifier, $cacheTimeout);
4289  $cachedCacheLifetime = $cacheTimeout;
4290  }
4291  return $cachedCacheLifetime;
4292  }
4293 
4300  public function getUniqueId($desired = '')
4301  {
4302  if ($desired === '') {
4303  // id has to start with a letter to reach XHTML compliance
4304  $uniqueId = 'a' . $this->uniqueHash();
4305  } else {
4306  $uniqueId = $desired;
4307  for ($i = 1; isset($this->usedUniqueIds[$uniqueId]); $i++) {
4308  $uniqueId = $desired . '_' . $i;
4309  }
4310  }
4311  $this->usedUniqueIds[$uniqueId] = true;
4312  return $uniqueId;
4313  }
4314 
4315  /*********************************************
4316  *
4317  * Localization and character set conversion
4318  *
4319  *********************************************/
4326  public function sL($input)
4327  {
4328  if (substr($input, 0, 4) !== 'LLL:') {
4329  // Not a label, return the key as this
4330  return $input;
4331  }
4332  // If cached label
4333  if (!isset($this->LL_labels_cache[$this->lang][$input])) {
4334  $restStr = trim(substr($input, 4));
4335  $extPrfx = '';
4336  if (strpos($restStr, 'EXT:') === 0) {
4337  $restStr = trim(substr($restStr, 4));
4338  $extPrfx = 'EXT:';
4339  }
4340  $parts = explode(':', $restStr);
4341  $parts[0] = $extPrfx . $parts[0];
4342  // Getting data if not cached
4343  if (!isset($this->LL_files_cache[$parts[0]])) {
4344  $this->LL_files_cache[$parts[0]] = $this->readLLfile($parts[0]);
4345  }
4346  $this->LL_labels_cache[$this->lang][$input] = $this->getLLL($parts[1], $this->LL_files_cache[$parts[0]]);
4347  }
4348  return $this->LL_labels_cache[$this->lang][$input];
4349  }
4350 
4357  public function readLLfile($fileRef)
4358  {
4360  $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
4361 
4362  if ($this->lang !== 'default') {
4363  $languages = array_reverse($this->languageDependencies);
4364  // At least we need to have English
4365  if (empty($languages)) {
4366  $languages[] = 'default';
4367  }
4368  } else {
4369  $languages = ['default'];
4370  }
4371 
4372  $localLanguage = [];
4373  foreach ($languages as $language) {
4374  $tempLL = $languageFactory->getParsedData($fileRef, $language, 'utf-8');
4375  $localLanguage['default'] = $tempLL['default'];
4376  if (!isset($localLanguage[$this->lang])) {
4377  $localLanguage[$this->lang] = $localLanguage['default'];
4378  }
4379  if ($this->lang !== 'default' && isset($tempLL[$language])) {
4380  // Merge current language labels onto labels from previous language
4381  // This way we have a label with fall back applied
4382  ArrayUtility::mergeRecursiveWithOverrule($localLanguage[$this->lang], $tempLL[$language], true, false);
4383  }
4384  }
4385 
4386  return $localLanguage;
4387  }
4388 
4396  public function getLLL($index, $LOCAL_LANG)
4397  {
4398  if (isset($LOCAL_LANG[$this->lang][$index][0]['target'])) {
4399  return $LOCAL_LANG[$this->lang][$index][0]['target'];
4400  }
4401  if (isset($LOCAL_LANG['default'][$index][0]['target'])) {
4402  return $LOCAL_LANG['default'][$index][0]['target'];
4403  }
4404  return false;
4405  }
4406 
4410  public function initLLvars()
4411  {
4412  // Init languageDependencies list
4413  $this->languageDependencies = [];
4414  // Setting language key and split index:
4415  $this->lang = $this->config['config']['language'] ?: 'default';
4416  $this->pageRenderer->setLanguage($this->lang);
4417 
4418  // Finding the requested language in this list based
4419  // on the $lang key being inputted to this function.
4421  $locales = GeneralUtility::makeInstance(Locales::class);
4422  $locales->initialize();
4423 
4424  // Language is found. Configure it:
4425  if (in_array($this->lang, $locales->getLocales())) {
4426  $this->languageDependencies[] = $this->lang;
4427  foreach ($locales->getLocaleDependencies($this->lang) as $language) {
4428  $this->languageDependencies[] = $language;
4429  }
4430  }
4431 
4432  // Rendering charset of HTML page.
4433  if ($this->config['config']['metaCharset']) {
4434  $this->metaCharset = trim(strtolower($this->config['config']['metaCharset']));
4435  }
4436  }
4437 
4450  public function csConv($str, $from = '')
4451  {
4453  if ($from) {
4455  $charsetConverter = GeneralUtility::makeInstance(CharsetConverter::class);
4456  $output = $charsetConverter->conv($str, $charsetConverter->parse_charset($from), 'utf-8');
4457  return $output ?: $str;
4458  }
4459  return $str;
4460  }
4461 
4468  public function convOutputCharset($content)
4469  {
4470  if ($this->metaCharset !== 'utf-8') {
4472  $charsetConverter = GeneralUtility::makeInstance(CharsetConverter::class);
4473  $content = $charsetConverter->conv($content, 'utf-8', $this->metaCharset, true);
4474  }
4475  return $content;
4476  }
4477 
4481  public function convPOSTCharset()
4482  {
4483  if ($this->metaCharset !== 'utf-8' && is_array($_POST) && !empty($_POST)) {
4484  $this->convertCharsetRecursivelyToUtf8($_POST, $this->metaCharset);
4485  $GLOBALS['HTTP_POST_VARS'] = $_POST;
4486  }
4487  }
4488 
4495  protected function convertCharsetRecursivelyToUtf8(&$data, string $fromCharset)
4496  {
4497  foreach ($data as $key => $value) {
4498  if (is_array($data[$key])) {
4499  $this->convertCharsetRecursivelyToUtf8($data[$key], $fromCharset);
4500  } elseif (is_string($data[$key])) {
4501  $data[$key] = mb_convert_encoding($data[$key], 'utf-8', $fromCharset);
4502  }
4503  }
4504  }
4505 
4511  protected function calculatePageCacheTimeout()
4512  {
4513  $result = PHP_INT_MAX;
4514  // Get the configuration
4515  $tablesToConsider = $this->getCurrentPageCacheConfiguration();
4516  // Get the time, rounded to the minute (do not pollute MySQL cache!)
4517  // It is ok that we do not take seconds into account here because this
4518  // value will be subtracted later. So we never get the time "before"
4519  // the cache change.
4520  $now = $GLOBALS['ACCESS_TIME'];
4521  // Find timeout by checking every table
4522  foreach ($tablesToConsider as $tableDef) {
4523  $result = min($result, $this->getFirstTimeValueForRecord($tableDef, $now));
4524  }
4525  // We return + 1 second just to ensure that cache is definitely regenerated
4526  return $result === PHP_INT_MAX ? PHP_INT_MAX : $result - $now + 1;
4527  }
4528 
4544  {
4545  $result = ['tt_content:' . $this->id];
4546  if (isset($this->config['config']['cache.'][$this->id])) {
4547  $result = array_merge($result, GeneralUtility::trimExplode(',', $this->config['config']['cache.'][$this->id]));
4548  }
4549  if (isset($this->config['config']['cache.']['all'])) {
4550  $result = array_merge($result, GeneralUtility::trimExplode(',', $this->config['config']['cache.']['all']));
4551  }
4552  return array_unique($result);
4553  }
4554 
4564  protected function getFirstTimeValueForRecord($tableDef, $now)
4565  {
4566  $now = (int)$now;
4567  $result = PHP_INT_MAX;
4568  list($tableName, $pid) = GeneralUtility::trimExplode(':', $tableDef);
4569  if (empty($tableName) || empty($pid)) {
4570  throw new \InvalidArgumentException('Unexpected value for parameter $tableDef. Expected <tablename>:<pid>, got \'' . htmlspecialchars($tableDef) . '\'.', 1307190365);
4571  }
4572 
4573  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
4574  ->getQueryBuilderForTable($tableName);
4575  $queryBuilder->getRestrictions()
4576  ->removeByType(StartTimeRestriction::class)
4577  ->removeByType(EndTimeRestriction::class);
4578  $timeFields = [];
4579  $timeConditions = $queryBuilder->expr()->orX();
4580  foreach (['starttime', 'endtime'] as $field) {
4581  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field])) {
4582  $timeFields[$field] = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field];
4583  $queryBuilder->addSelectLiteral(
4584  'MIN('
4585  . 'CASE WHEN '
4586  . $queryBuilder->expr()->lte(
4587  $timeFields[$field],
4588  $queryBuilder->createNamedParameter($now, \PDO::PARAM_INT)
4589  )
4590  . ' THEN NULL ELSE ' . $queryBuilder->quoteIdentifier($timeFields[$field]) . ' END'
4591  . ') AS ' . $queryBuilder->quoteIdentifier($timeFields[$field])
4592  );
4593  $timeConditions->add(
4594  $queryBuilder->expr()->gt(
4595  $timeFields[$field],
4596  $queryBuilder->createNamedParameter($now, \PDO::PARAM_INT)
4597  )
4598  );
4599  }
4600  }
4601 
4602  // if starttime or endtime are defined, evaluate them
4603  if (!empty($timeFields)) {
4604  // find the timestamp, when the current page's content changes the next time
4605  $row = $queryBuilder
4606  ->from($tableName)
4607  ->where(
4608  $queryBuilder->expr()->eq(
4609  'pid',
4610  $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
4611  ),
4612  $timeConditions
4613  )
4614  ->execute()
4615  ->fetch();
4616 
4617  if ($row) {
4618  foreach ($timeFields as $timeField => $_) {
4619  // if a MIN value is found, take it into account for the
4620  // cache lifetime we have to filter out start/endtimes < $now,
4621  // as the SQL query also returns rows with starttime < $now
4622  // and endtime > $now (and using a starttime from the past
4623  // would be wrong)
4624  if ($row[$timeField] !== null && (int)$row[$timeField] > $now) {
4625  $result = min($result, (int)$row[$timeField]);
4626  }
4627  }
4628  }
4629  }
4630 
4631  return $result;
4632  }
4633 
4639  protected function getSysDomainCache()
4640  {
4641  $entryIdentifier = 'core-database-sys_domain-complete';
4643  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
4644 
4645  $sysDomainData = [];
4646  if ($runtimeCache->has($entryIdentifier)) {
4647  $sysDomainData = $runtimeCache->get($entryIdentifier);
4648  } else {
4649  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
4650  $queryBuilder->setRestrictions(GeneralUtility::makeInstance(DefaultRestrictionContainer::class));
4651  $result = $queryBuilder
4652  ->select('uid', 'pid', 'domainName', 'forced')
4653  ->from('sys_domain')
4654  ->where(
4655  $queryBuilder->expr()->eq(
4656  'redirectTo',
4657  $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
4658  )
4659  )
4660  ->orderBy('sorting', 'ASC')
4661  ->execute();
4662 
4663  while ($row = $result->fetch()) {
4664  // if there is already an entry for this pid, check if we should overwrite it
4665  if (isset($sysDomainData[$row['pid']])) {
4666  // There is already a "forced" entry, which must not be overwritten
4667  if ($sysDomainData[$row['pid']]['forced']) {
4668  continue;
4669  }
4670 
4671  // The current domain record is also NOT-forced, keep the old unless the new one matches the current request
4672  if (!$row['forced'] && !$this->domainNameMatchesCurrentRequest($row['domainName'])) {
4673  continue;
4674  }
4675  }
4676 
4677  // as we passed all previous checks, we save this domain for the current pid
4678  $sysDomainData[$row['pid']] = [
4679  'uid' => $row['uid'],
4680  'pid' => $row['pid'],
4681  'domainName' => rtrim($row['domainName'], '/'),
4682  'forced' => $row['forced'],
4683  ];
4684  }
4685  $runtimeCache->set($entryIdentifier, $sysDomainData);
4686  }
4687  return $sysDomainData;
4688  }
4689 
4697  public function domainNameMatchesCurrentRequest($domainName)
4698  {
4699  $currentDomain = GeneralUtility::getIndpEnv('HTTP_HOST');
4700  $currentPathSegment = trim(preg_replace('|/[^/]*$|', '', GeneralUtility::getIndpEnv('SCRIPT_NAME')));
4701  return $currentDomain === $domainName || $currentDomain . $currentPathSegment === $domainName;
4702  }
4703 
4712  public function getDomainDataForPid($targetPid)
4713  {
4714  // Using array_key_exists() here, nice $result can be NULL
4715  // (happens, if there's no domain records defined)
4716  if (!array_key_exists($targetPid, $this->domainDataCache)) {
4717  $result = null;
4718  $sysDomainData = $this->getSysDomainCache();
4719  $rootline = $this->sys_page->getRootLine($targetPid);
4720  // walk the rootline downwards from the target page
4721  // to the root page, until a domain record is found
4722  foreach ($rootline as $pageInRootline) {
4723  $pidInRootline = $pageInRootline['uid'];
4724  if (isset($sysDomainData[$pidInRootline])) {
4725  $result = $sysDomainData[$pidInRootline];
4726  break;
4727  }
4728  }
4729  $this->domainDataCache[$targetPid] = $result;
4730  }
4731 
4732  return $this->domainDataCache[$targetPid];
4733  }
4734 
4742  public function getDomainNameForPid($targetPid)
4743  {
4744  $domainData = $this->getDomainDataForPid($targetPid);
4745  return $domainData ? $domainData['domainName'] : null;
4746  }
4747 
4754  public function getRequestedId()
4755  {
4756  return $this->requestedId ?: $this->id;
4757  }
4758 
4793  protected function acquireLock($type, $key)
4794  {
4795  $lockFactory = GeneralUtility::makeInstance(LockFactory::class);
4796  $this->locks[$type]['accessLock'] = $lockFactory->createLocker($type);
4797 
4798  $this->locks[$type]['pageLock'] = $lockFactory->createLocker(
4799  $key,
4801  );
4802 
4803  do {
4804  if (!$this->locks[$type]['accessLock']->acquire()) {
4805  throw new \RuntimeException('Could not acquire access lock for "' . $type . '"".', 1294586098);
4806  }
4807 
4808  try {
4809  $locked = $this->locks[$type]['pageLock']->acquire(
4811  );
4812  } catch (LockAcquireWouldBlockException $e) {
4813  // somebody else has the lock, we keep waiting
4814 
4815  // first release the access lock
4816  $this->locks[$type]['accessLock']->release();
4817  // now lets make a short break (100ms) until we try again, since
4818  // the page generation by the lock owner will take a while anyways
4819  usleep(100000);
4820  continue;
4821  }
4822  $this->locks[$type]['accessLock']->release();
4823  if ($locked) {
4824  break;
4825  }
4826  throw new \RuntimeException('Could not acquire page lock for ' . $key . '.', 1460975877);
4827  } while (true);
4828  }
4829 
4838  protected function releaseLock($type)
4839  {
4840  if ($this->locks[$type]['accessLock']) {
4841  if (!$this->locks[$type]['accessLock']->acquire()) {
4842  throw new \RuntimeException('Could not acquire access lock for "' . $type . '"".', 1460975902);
4843  }
4844 
4845  $this->locks[$type]['pageLock']->release();
4846  $this->locks[$type]['pageLock']->destroy();
4847  $this->locks[$type]['pageLock'] = null;
4848 
4849  $this->locks[$type]['accessLock']->release();
4850  $this->locks[$type]['accessLock'] = null;
4851  }
4852  }
4853 
4859  protected function getBackendUser()
4860  {
4861  return $GLOBALS['BE_USER'];
4862  }
4863 
4867  protected function getTimeTracker()
4868  {
4869  return GeneralUtility::makeInstance(TimeTracker::class);
4870  }
4871 
4877  protected function getDocumentTemplate()
4878  {
4879  return $GLOBALS['TBE_TEMPLATE'];
4880  }
4881 }
static devLog($msg, $extKey, $severity=0, $dataVar=false)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static isAllowedLinkVarValue($haystack, $needle)
__construct($_=null, $id, $type, $no_cache='', $cHash='', $_2=null, $MP='', $RDCT='')
debug($variable='', $name=' *variable *', $line=' *line *', $file=' *file *', $recursiveDepth=3, $debugLevel='E_DEBUG')
static isFirstPartOfStr($str, $partStr)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
static setSingletonInstance($className, SingletonInterface $instance)
static getFileAbsFileName($filename, $_=null, $_2=null)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static hideIfDefaultLanguage($localizationConfiguration)
static makeInstance($className,... $constructorArguments)
static hideIfNotTranslated($l18n_cfg_fieldValue)
static _GETset($inputGet, $key='')
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
static redirect($url, $httpStatus=self::HTTP_STATUS_303)
static stripLogicalOperatorPrefix(string $constraint)
getPageShortcut($SC, $mode, $thisUid, $itera=20, $pageLog=[], $disableGroupCheck=false)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
$locales
Definition: be_users.php:6