TYPO3 CMS  TYPO3_8-7
BackendUserAuthentication.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 
31 
40 {
45  public $usergroup_column = 'usergroup';
46 
51  public $usergroup_table = 'be_groups';
52 
58  public $groupData = [
59  'filemounts' => []
60  ];
61 
66  public $userGroups = [];
67 
72  public $userGroupsUID = [];
73 
78  public $groupList = '';
79 
88  public $workspace = -99;
89 
94  public $workspaceRec = [];
95 
103  public $dataLists = [
104  'webmount_list' => '',
105  'filemount_list' => '',
106  'file_permissions' => '',
107  'modList' => '',
108  'tables_select' => '',
109  'tables_modify' => '',
110  'pagetypes_select' => '',
111  'non_exclude_fields' => '',
112  'explicit_allowdeny' => '',
113  'allowed_languages' => '',
114  'workspace_perms' => '',
115  'custom_options' => ''
116  ];
117 
122  public $includeGroupArray = [];
123 
128  public $TSdataArray = [];
129 
134  public $userTS_text = '';
135 
140  public $userTS = [];
141 
146  public $userTSUpdated = false;
147 
152  public $userTS_dontGetCached = false;
153 
158  public $errorMsg = '';
159 
165 
169  protected $fileStorages;
170 
174  protected $filePermissions;
175 
180  public $user_table = 'be_users';
181 
186  public $username_column = 'username';
187 
192  public $userident_column = 'password';
193 
198  public $userid_column = 'uid';
199 
203  public $lastLogin_column = 'lastlogin';
204 
208  public $enablecolumns = [
209  'rootLevel' => 1,
210  'deleted' => 'deleted',
211  'disabled' => 'disable',
212  'starttime' => 'starttime',
213  'endtime' => 'endtime'
214  ];
215 
220  public $formfield_uname = 'username';
221 
226  public $formfield_uident = 'userident';
227 
232  public $formfield_status = 'login_status';
233 
238  public $writeStdLog = true;
239 
244  public $writeAttemptLog = true;
245 
255  public $sessionTimeout = 6000;
256 
260  public $firstMainGroup = 0;
261 
266  public $uc;
267 
277  public $uc_default = [
278  'interfaceSetup' => '',
279  // serialized content that is used to store interface pane and menu positions. Set by the logout.php-script
280  'moduleData' => [],
281  // user-data for the modules
282  'thumbnailsByDefault' => 1,
283  'emailMeAtLogin' => 0,
284  'startModule' => 'help_AboutAboutmodules',
285  'titleLen' => 50,
286  'edit_RTE' => '1',
287  'edit_docModuleUpload' => '1',
288  'resizeTextareas' => 1,
289  'resizeTextareas_MaxHeight' => 500,
290  'resizeTextareas_Flexible' => 0
291  ];
292 
296  public function __construct()
297  {
298  parent::__construct();
299  $this->name = self::getCookieName();
300  $this->loginType = 'BE';
301  $this->warningEmail = $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'];
302  $this->lockIP = $GLOBALS['TYPO3_CONF_VARS']['BE']['lockIP'];
303  $this->sessionTimeout = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['sessionTimeout'];
304  }
305 
312  public function isAdmin()
313  {
314  return is_array($this->user) && ($this->user['admin'] & 1) == 1;
315  }
316 
325  public function isMemberOfGroup($groupId)
326  {
327  $groupId = (int)$groupId;
328  if ($this->groupList && $groupId) {
329  return GeneralUtility::inList($this->groupList, $groupId);
330  }
331  return false;
332  }
333 
349  public function doesUserHaveAccess($row, $perms)
350  {
351  $userPerms = $this->calcPerms($row);
352  return ($userPerms & $perms) == $perms;
353  }
354 
371  public function isInWebMount($id, $readPerms = '', $exitOnError = 0)
372  {
373  if (!$GLOBALS['TYPO3_CONF_VARS']['BE']['lockBeUserToDBmounts'] || $this->isAdmin()) {
374  return 1;
375  }
376  $id = (int)$id;
377  // Check if input id is an offline version page in which case we will map id to the online version:
378  $checkRec = BackendUtility::getRecord('pages', $id, 'pid,t3ver_oid');
379  if ($checkRec['pid'] == -1) {
380  $id = (int)$checkRec['t3ver_oid'];
381  }
382  if (!$readPerms) {
383  $readPerms = $this->getPagePermsClause(1);
384  }
385  if ($id > 0) {
386  $wM = $this->returnWebmounts();
387  $rL = BackendUtility::BEgetRootLine($id, ' AND ' . $readPerms);
388  foreach ($rL as $v) {
389  if ($v['uid'] && in_array($v['uid'], $wM)) {
390  return $v['uid'];
391  }
392  }
393  }
394  if ($exitOnError) {
395  throw new \RuntimeException('Access Error: This page is not within your DB-mounts', 1294586445);
396  }
397  return null;
398  }
399 
408  public function modAccess($conf, $exitOnError)
409  {
410  if (!BackendUtility::isModuleSetInTBE_MODULES($conf['name'])) {
411  if ($exitOnError) {
412  throw new \RuntimeException('Fatal Error: This module "' . $conf['name'] . '" is not enabled in TBE_MODULES', 1294586446);
413  }
414  return false;
415  }
416  // Workspaces check:
417  if (
418  !empty($conf['workspaces'])
419  && \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')
420  && ($this->workspace !== 0 || !GeneralUtility::inList($conf['workspaces'], 'online'))
421  && ($this->workspace !== -1 || !GeneralUtility::inList($conf['workspaces'], 'offline'))
422  && ($this->workspace <= 0 || !GeneralUtility::inList($conf['workspaces'], 'custom'))
423  ) {
424  if ($exitOnError) {
425  throw new \RuntimeException('Workspace Error: This module "' . $conf['name'] . '" is not available under the current workspace', 1294586447);
426  }
427  return false;
428  }
429  // Returns TRUE if conf[access] is not set at all or if the user is admin
430  if (!$conf['access'] || $this->isAdmin()) {
431  return true;
432  }
433  // If $conf['access'] is set but not with 'admin' then we return TRUE, if the module is found in the modList
434  $acs = false;
435  if (!strstr($conf['access'], 'admin') && $conf['name']) {
436  $acs = $this->check('modules', $conf['name']);
437  }
438  if (!$acs && $exitOnError) {
439  throw new \RuntimeException('Access Error: You don\'t have access to this module.', 1294586448);
440  }
441  return $acs;
442  }
443 
459  public function getPagePermsClause($perms)
460  {
461  if (is_array($this->user)) {
462  if ($this->isAdmin()) {
463  return ' 1=1';
464  }
465  // Make sure it's integer.
466  $perms = (int)$perms;
467  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
468  ->getQueryBuilderForTable('pages')
469  ->expr();
470 
471  // User
472  $constraint = $expressionBuilder->orX(
473  $expressionBuilder->comparison(
474  $expressionBuilder->bitAnd('pages.perms_everybody', $perms),
476  $perms
477  ),
478  $expressionBuilder->andX(
479  $expressionBuilder->eq('pages.perms_userid', (int)$this->user['uid']),
480  $expressionBuilder->comparison(
481  $expressionBuilder->bitAnd('pages.perms_user', $perms),
483  $perms
484  )
485  )
486  );
487 
488  // Group (if any is set)
489  if ($this->groupList) {
490  $constraint->add(
491  $expressionBuilder->andX(
492  $expressionBuilder->in(
493  'pages.perms_groupid',
494  GeneralUtility::intExplode(',', $this->groupList)
495  ),
496  $expressionBuilder->comparison(
497  $expressionBuilder->bitAnd('pages.perms_group', $perms),
499  $perms
500  )
501  )
502  );
503  }
504 
505  $constraint = ' (' . (string)$constraint . ')';
506 
507  // ****************
508  // getPagePermsClause-HOOK
509  // ****************
510  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getPagePermsClause'])) {
511  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getPagePermsClause'] as $_funcRef) {
512  $_params = ['currentClause' => $constraint, 'perms' => $perms];
513  $constraint = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
514  }
515  }
516  return $constraint;
517  }
518  return ' 1=0';
519  }
520 
530  public function calcPerms($row)
531  {
532  // Return 31 for admin users.
533  if ($this->isAdmin()) {
534  return Permission::ALL;
535  }
536  // Return 0 if page is not within the allowed web mount
537  if (!$this->isInWebMount($row['uid'])) {
538  return Permission::NOTHING;
539  }
540  $out = Permission::NOTHING;
541  if (
542  isset($row['perms_userid']) && isset($row['perms_user']) && isset($row['perms_groupid'])
543  && isset($row['perms_group']) && isset($row['perms_everybody']) && isset($this->groupList)
544  ) {
545  if ($this->user['uid'] == $row['perms_userid']) {
546  $out |= $row['perms_user'];
547  }
548  if ($this->isMemberOfGroup($row['perms_groupid'])) {
549  $out |= $row['perms_group'];
550  }
551  $out |= $row['perms_everybody'];
552  }
553  // ****************
554  // CALCPERMS hook
555  // ****************
556  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['calcPerms'])) {
557  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['calcPerms'] as $_funcRef) {
558  $_params = [
559  'row' => $row,
560  'outputPermissions' => $out
561  ];
562  $out = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
563  }
564  }
565  return $out;
566  }
567 
573  public function isRTE()
574  {
575  return (bool)$this->uc['edit_RTE'];
576  }
577 
588  public function check($type, $value)
589  {
590  return isset($this->groupData[$type])
591  && ($this->isAdmin() || GeneralUtility::inList($this->groupData[$type], $value));
592  }
593 
603  public function checkAuthMode($table, $field, $value, $authMode)
604  {
605  // Admin users can do anything:
606  if ($this->isAdmin()) {
607  return true;
608  }
609  // Allow all blank values:
610  if ((string)$value === '') {
611  return true;
612  }
613  // Certain characters are not allowed in the value
614  if (preg_match('/[:|,]/', $value)) {
615  return false;
616  }
617  // Initialize:
618  $testValue = $table . ':' . $field . ':' . $value;
619  $out = true;
620  // Checking value:
621  switch ((string)$authMode) {
622  case 'explicitAllow':
623  if (!GeneralUtility::inList($this->groupData['explicit_allowdeny'], ($testValue . ':ALLOW'))) {
624  $out = false;
625  }
626  break;
627  case 'explicitDeny':
628  if (GeneralUtility::inList($this->groupData['explicit_allowdeny'], $testValue . ':DENY')) {
629  $out = false;
630  }
631  break;
632  case 'individual':
633  if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
634  $items = $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'];
635  if (is_array($items)) {
636  foreach ($items as $iCfg) {
637  if ((string)$iCfg[1] === (string)$value && $iCfg[4]) {
638  switch ((string)$iCfg[4]) {
639  case 'EXPL_ALLOW':
640  if (!GeneralUtility::inList($this->groupData['explicit_allowdeny'], ($testValue . ':ALLOW'))) {
641  $out = false;
642  }
643  break;
644  case 'EXPL_DENY':
645  if (GeneralUtility::inList($this->groupData['explicit_allowdeny'], $testValue . ':DENY')) {
646  $out = false;
647  }
648  break;
649  }
650  break;
651  }
652  }
653  }
654  }
655  break;
656  }
657  return $out;
658  }
659 
666  public function checkLanguageAccess($langValue)
667  {
668  // The users language list must be non-blank - otherwise all languages are allowed.
669  if (trim($this->groupData['allowed_languages']) !== '') {
670  $langValue = (int)$langValue;
671  // Language must either be explicitly allowed OR the lang Value be "-1" (all languages)
672  if ($langValue != -1 && !$this->check('allowed_languages', $langValue)) {
673  return false;
674  }
675  }
676  return true;
677  }
678 
686  public function checkFullLanguagesAccess($table, $record)
687  {
688  if (!$this->checkLanguageAccess(0)) {
689  return false;
690  }
691  if (BackendUtility::isTableLocalizable($table) || $table === 'pages') {
692  if ($table === 'pages') {
693  $l10nTable = 'pages_language_overlay';
694  $pointerField = $GLOBALS['TCA'][$l10nTable]['ctrl']['transOrigPointerField'];
695  $pointerValue = $record['uid'];
696  } else {
697  $l10nTable = $table;
698  $pointerField = $GLOBALS['TCA'][$l10nTable]['ctrl']['transOrigPointerField'];
699  $pointerValue = $record[$pointerField] > 0 ? $record[$pointerField] : $record['uid'];
700  }
701 
702  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($l10nTable);
703  $queryBuilder->getRestrictions()
704  ->removeAll()
705  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
706  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
707 
708  $recordLocalizations = $queryBuilder->select('*')
709  ->from($l10nTable)
710  ->where(
711  $queryBuilder->expr()->eq(
712  $pointerField,
713  $queryBuilder->createNamedParameter($pointerValue, \PDO::PARAM_INT)
714  )
715  )
716  ->execute()
717  ->fetchAll();
718 
719  foreach ($recordLocalizations as $recordLocalization) {
720  if (!$this->checkLanguageAccess($recordLocalization[$GLOBALS['TCA'][$l10nTable]['ctrl']['languageField']])) {
721  return false;
722  }
723  }
724  }
725  return true;
726  }
727 
743  public function recordEditAccessInternals($table, $idOrRow, $newRecord = false, $deletedRecord = false, $checkFullLanguageAccess = false)
744  {
745  if (!isset($GLOBALS['TCA'][$table])) {
746  return false;
747  }
748  // Always return TRUE for Admin users.
749  if ($this->isAdmin()) {
750  return true;
751  }
752  // Fetching the record if the $idOrRow variable was not an array on input:
753  if (!is_array($idOrRow)) {
754  if ($deletedRecord) {
755  $idOrRow = BackendUtility::getRecord($table, $idOrRow, '*', '', false);
756  } else {
757  $idOrRow = BackendUtility::getRecord($table, $idOrRow);
758  }
759  if (!is_array($idOrRow)) {
760  $this->errorMsg = 'ERROR: Record could not be fetched.';
761  return false;
762  }
763  }
764  // Checking languages:
765  if ($GLOBALS['TCA'][$table]['ctrl']['languageField']) {
766  // Language field must be found in input row - otherwise it does not make sense.
767  if (isset($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
768  if (!$this->checkLanguageAccess($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
769  $this->errorMsg = 'ERROR: Language was not allowed.';
770  return false;
771  }
772  if (
773  $checkFullLanguageAccess && $idOrRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']] == 0
774  && !$this->checkFullLanguagesAccess($table, $idOrRow)
775  ) {
776  $this->errorMsg = 'ERROR: Related/affected language was not allowed.';
777  return false;
778  }
779  } else {
780  $this->errorMsg = 'ERROR: The "languageField" field named "'
781  . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '" was not found in testing record!';
782  return false;
783  }
784  } elseif ($table === 'pages') {
785  if (!$this->checkLanguageAccess(0)) {
786  $this->errorMsg = 'ERROR: Language was not allowed.';
787  return false;
788  }
789  if ($checkFullLanguageAccess && !$this->checkFullLanguagesAccess($table, $idOrRow)) {
790  $this->errorMsg = 'ERROR: Related/affected language was not allowed.';
791  return false;
792  }
793  }
794  // Checking authMode fields:
795  if (is_array($GLOBALS['TCA'][$table]['columns'])) {
796  foreach ($GLOBALS['TCA'][$table]['columns'] as $fieldName => $fieldValue) {
797  if (isset($idOrRow[$fieldName])) {
798  if (
799  $fieldValue['config']['type'] === 'select' && $fieldValue['config']['authMode']
800  && $fieldValue['config']['authMode_enforce'] === 'strict'
801  ) {
802  if (!$this->checkAuthMode($table, $fieldName, $idOrRow[$fieldName], $fieldValue['config']['authMode'])) {
803  $this->errorMsg = 'ERROR: authMode "' . $fieldValue['config']['authMode']
804  . '" failed for field "' . $fieldName . '" with value "'
805  . $idOrRow[$fieldName] . '" evaluated';
806  return false;
807  }
808  }
809  }
810  }
811  }
812  // Checking "editlock" feature (doesn't apply to new records)
813  if (!$newRecord && $GLOBALS['TCA'][$table]['ctrl']['editlock']) {
814  if (isset($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['editlock']])) {
815  if ($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['editlock']]) {
816  $this->errorMsg = 'ERROR: Record was locked for editing. Only admin users can change this state.';
817  return false;
818  }
819  } else {
820  $this->errorMsg = 'ERROR: The "editLock" field named "' . $GLOBALS['TCA'][$table]['ctrl']['editlock']
821  . '" was not found in testing record!';
822  return false;
823  }
824  }
825  // Checking record permissions
826  // THIS is where we can include a check for "perms_" fields for other records than pages...
827  // Process any hooks
828  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['recordEditAccessInternals'])) {
829  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['recordEditAccessInternals'] as $funcRef) {
830  $params = [
831  'table' => $table,
832  'idOrRow' => $idOrRow,
833  'newRecord' => $newRecord
834  ];
835  if (!GeneralUtility::callUserFunction($funcRef, $params, $this)) {
836  return false;
837  }
838  }
839  }
840  // Finally, return TRUE if all is well.
841  return true;
842  }
843 
854  public function isPSet($compiledPermissions, $tableName, $actionType = '')
855  {
856  if ($this->isAdmin()) {
857  $result = true;
858  } elseif ($tableName === 'pages') {
859  switch ($actionType) {
860  case 'edit':
861  $result = ($compiledPermissions & Permission::PAGE_EDIT) !== 0;
862  break;
863  case 'new':
864  // Create new page OR page content
865  $result = ($compiledPermissions & Permission::PAGE_NEW + Permission::CONTENT_EDIT) !== 0;
866  break;
867  case 'delete':
868  $result = ($compiledPermissions & Permission::PAGE_DELETE) !== 0;
869  break;
870  case 'editcontent':
871  $result = ($compiledPermissions & Permission::CONTENT_EDIT) !== 0;
872  break;
873  default:
874  $result = false;
875  }
876  } else {
877  $result = ($compiledPermissions & Permission::CONTENT_EDIT) !== 0;
878  }
879  return $result;
880  }
881 
887  public function mayMakeShortcut()
888  {
889  return $this->getTSConfigVal('options.enableBookmarks')
890  && !$this->getTSConfigVal('options.mayNotCreateEditBookmarks');
891  }
892 
904  public function workspaceCannotEditRecord($table, $recData)
905  {
906  // Only test offline spaces:
907  if ($this->workspace !== 0) {
908  if (!is_array($recData)) {
909  $recData = BackendUtility::getRecord(
910  $table,
911  $recData,
912  'pid' . ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] ? ',t3ver_wsid,t3ver_stage' : '')
913  );
914  }
915  if (is_array($recData)) {
916  // We are testing a "version" (identified by a pid of -1): it can be edited provided
917  // that workspace matches and versioning is enabled for the table.
918  if ((int)$recData['pid'] === -1) {
919  // No versioning, basic error, inconsistency even! Such records should not have a pid of -1!
920  if (!$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
921  return 'Versioning disabled for table';
922  }
923  if ((int)$recData['t3ver_wsid'] !== $this->workspace) {
924  // So does workspace match?
925  return 'Workspace ID of record didn\'t match current workspace';
926  }
927  // So is the user allowed to "use" the edit stage within the workspace?
928  return $this->workspaceCheckStageForCurrent(0)
929  ? false
930  : 'User\'s access level did not allow for editing';
931  }
932  // We are testing a "live" record:
933  // For "Live" records, check that PID for table allows editing
934  if ($res = $this->workspaceAllowLiveRecordsInPID($recData['pid'], $table)) {
935  // Live records are OK in this branch, but what about the stage of branch point, if any:
936  // OK
937  return $res > 0
938  ? false
939  : 'Stage for versioning root point and users access level did not allow for editing';
940  }
941  // If not offline and not in versionized branch, output error:
942  return 'Online record was not in versionized branch!';
943  }
944  return 'No record';
945  }
946  // OK because workspace is 0
947  return false;
948  }
949 
958  public function workspaceCannotEditOfflineVersion($table, $recData)
959  {
960  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
961  if (!is_array($recData)) {
962  $recData = BackendUtility::getRecord($table, $recData, 'uid,pid,t3ver_wsid,t3ver_stage');
963  }
964  if (is_array($recData)) {
965  if ((int)$recData['pid'] === -1) {
966  return $this->workspaceCannotEditRecord($table, $recData);
967  }
968  return 'Not an offline version';
969  }
970  return 'No record';
971  }
972  return 'Table does not support versioning.';
973  }
974 
986  public function workspaceAllowLiveRecordsInPID($pid, $table)
987  {
988  // Always for Live workspace AND if live-edit is enabled
989  // and tables are completely without versioning it is ok as well.
990  if (
991  $this->workspace === 0
992  || $this->workspaceRec['live_edit'] && !$GLOBALS['TCA'][$table]['ctrl']['versioningWS']
993  || $GLOBALS['TCA'][$table]['ctrl']['versioningWS_alwaysAllowLiveEdit']
994  ) {
995  // OK to create for this table.
996  return 2;
997  }
998  // If the answer is FALSE it means the only valid way to create or edit records in the PID is by versioning
999  return false;
1000  }
1001 
1009  public function workspaceCreateNewRecord($pid, $table)
1010  {
1011  if ($res = $this->workspaceAllowLiveRecordsInPID($pid, $table)) {
1012  // If LIVE records cannot be created in the current PID due to workspace restrictions, prepare creation of placeholder-record
1013  if ($res < 0) {
1014  // Stage for versioning root point and users access level did not allow for editing
1015  return false;
1016  }
1017  } elseif (!$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1018  // So, if no live records were allowed, we have to create a new version of this record:
1019  return false;
1020  }
1021  return true;
1022  }
1023 
1032  public function workspaceAllowAutoCreation($table, $id, $recpid)
1033  {
1034  // Auto-creation of version: In offline workspace, test if versioning is
1035  // enabled and look for workspace version of input record.
1036  // If there is no versionized record found we will create one and save to that.
1037  if (
1038  $this->workspace !== 0
1039  && $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $recpid >= 0
1040  && !BackendUtility::getWorkspaceVersionOfRecord($this->workspace, $table, $id, 'uid')
1041  ) {
1042  // There must be no existing version of this record in workspace.
1043  return true;
1044  }
1045  return false;
1046  }
1047 
1057  public function workspaceCheckStageForCurrent($stage)
1058  {
1059  // Always allow for admins
1060  if ($this->isAdmin()) {
1061  return true;
1062  }
1063  if ($this->workspace !== 0 && \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
1064  $stage = (int)$stage;
1065  $stat = $this->checkWorkspaceCurrent();
1066  // Check if custom staging is activated
1067  $workspaceRec = BackendUtility::getRecord('sys_workspace', $stat['uid']);
1068  if ($workspaceRec['custom_stages'] > 0 && $stage !== 0 && $stage !== -10) {
1069  // Get custom stage record
1070  $workspaceStageRec = BackendUtility::getRecord('sys_workspace_stage', $stage);
1071  // Check if the user is responsible for the current stage
1072  if (
1073  $stat['_ACCESS'] === 'owner'
1074  || $stat['_ACCESS'] === 'member'
1075  && GeneralUtility::inList($workspaceStageRec['responsible_persons'], 'be_users_' . $this->user['uid'])
1076  ) {
1077  return true;
1078  }
1079  // Check if the user is in a group which is responsible for the current stage
1080  foreach ($this->userGroupsUID as $groupUid) {
1081  if (
1082  $stat['_ACCESS'] === 'owner'
1083  || $stat['_ACCESS'] === 'member'
1084  && GeneralUtility::inList($workspaceStageRec['responsible_persons'], 'be_groups_' . $groupUid)
1085  ) {
1086  return true;
1087  }
1088  }
1089  } elseif ($stage == -10 || $stage == -20) {
1090  if ($stat['_ACCESS'] === 'owner') {
1091  return true;
1092  }
1093  return false;
1094  } else {
1095  $memberStageLimit = $this->workspaceRec['review_stage_edit'] ? 1 : 0;
1096  if (
1097  $stat['_ACCESS'] === 'owner'
1098  || $stat['_ACCESS'] === 'reviewer' && $stage <= 1
1099  || $stat['_ACCESS'] === 'member' && $stage <= $memberStageLimit
1100  ) {
1101  return true;
1102  }
1103  }
1104  } else {
1105  // Always OK for live workspace.
1106  return true;
1107  }
1108  return false;
1109  }
1110 
1121  public function workspacePublishAccess($wsid)
1122  {
1123  if ($this->isAdmin()) {
1124  return true;
1125  }
1126  // If no access to workspace, of course you cannot publish!
1127  $retVal = false;
1128  $wsAccess = $this->checkWorkspace($wsid);
1129  if ($wsAccess) {
1130  switch ($wsAccess['uid']) {
1131  case 0:
1132  // Live workspace
1133  // If access to Live workspace, no problem.
1134  $retVal = true;
1135  break;
1136  default:
1137  // Custom workspace
1138  $retVal = $wsAccess['_ACCESS'] === 'owner' || $this->checkWorkspace(0) && !($wsAccess['publish_access'] & Permission::PAGE_EDIT);
1139  // Either be an adminuser OR have access to online
1140  // workspace which is OK as well as long as publishing
1141  // access is not limited by workspace option.
1142  }
1143  }
1144  return $retVal;
1145  }
1146 
1152  public function workspaceSwapAccess()
1153  {
1154  if ($this->workspace > 0 && (int)$this->workspaceRec['swap_modes'] === 2) {
1155  return false;
1156  }
1157  return true;
1158  }
1159 
1169  public function getTSConfig($objectString = '', $config = '')
1170  {
1171  if (empty($objectString) && empty($config)) {
1172  return $this->userTS;
1173  }
1174 
1175  if (!is_array($config)) {
1176  // Getting Root-ts if not sent
1177  $config = $this->userTS;
1178  }
1179  $TSConf = ['value' => null, 'properties' => null];
1180  $parts = GeneralUtility::trimExplode('.', $objectString, true, 2);
1181  $key = $parts[0];
1182  if ($key !== '') {
1183  if (count($parts) > 1 && $parts[1] !== '') {
1184  // Go on, get the next level
1185  if (is_array($config[$key . '.'] ?? false)) {
1186  $TSConf = $this->getTSConfig($parts[1], $config[$key . '.']);
1187  }
1188  } else {
1189  $TSConf['value'] = $config[$key] ?? null;
1190  $TSConf['properties'] = $config[$key . '.'] ?? null;
1191  }
1192  }
1193  return $TSConf;
1194  }
1195 
1203  public function getTSConfigVal($objectString)
1204  {
1205  $TSConf = $this->getTSConfig($objectString);
1206  return $TSConf['value'];
1207  }
1208 
1216  public function getTSConfigProp($objectString)
1217  {
1218  $TSConf = $this->getTSConfig($objectString);
1219  return $TSConf['properties'];
1220  }
1221 
1230  public function returnWebmounts()
1231  {
1232  return (string)$this->groupData['webmounts'] != '' ? explode(',', $this->groupData['webmounts']) : [];
1233  }
1234 
1241  public function setWebmounts(array $mountPointUids, $append = false)
1242  {
1243  if (empty($mountPointUids)) {
1244  return;
1245  }
1246  if ($append) {
1247  $currentWebMounts = GeneralUtility::intExplode(',', $this->groupData['webmounts']);
1248  $mountPointUids = array_merge($currentWebMounts, $mountPointUids);
1249  }
1250  $this->groupData['webmounts'] = implode(',', array_unique($mountPointUids));
1251  }
1252 
1261  public function jsConfirmation($bitmask)
1262  {
1263  try {
1264  $alertPopupsSetting = trim((string)$this->getTSConfig('options.alertPopups')['value']);
1265  $alertPopup = JsConfirmation::cast($alertPopupsSetting === '' ? null : (int)$alertPopupsSetting);
1266  } catch (InvalidEnumerationValueException $e) {
1267  $alertPopup = new JsConfirmation();
1268  }
1269 
1270  return JsConfirmation::cast($bitmask)->matches($alertPopup);
1271  }
1272 
1282  public function fetchGroupData()
1283  {
1284  if ($this->user['uid']) {
1285  // Get lists for the be_user record and set them as default/primary values.
1286  // Enabled Backend Modules
1287  $this->dataLists['modList'] = $this->user['userMods'];
1288  // Add Allowed Languages
1289  $this->dataLists['allowed_languages'] = $this->user['allowed_languages'];
1290  // Set user value for workspace permissions.
1291  $this->dataLists['workspace_perms'] = $this->user['workspace_perms'];
1292  // Database mountpoints
1293  $this->dataLists['webmount_list'] = $this->user['db_mountpoints'];
1294  // File mountpoints
1295  $this->dataLists['filemount_list'] = $this->user['file_mountpoints'];
1296  // Fileoperation permissions
1297  $this->dataLists['file_permissions'] = $this->user['file_permissions'];
1298  // Setting default User TSconfig:
1299  $this->TSdataArray[] = $this->addTScomment('From $GLOBALS["TYPO3_CONF_VARS"]["BE"]["defaultUserTSconfig"]:')
1300  . $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'];
1301  // Default TSconfig for admin-users
1302  if ($this->isAdmin()) {
1303  $this->TSdataArray[] = $this->addTScomment('"admin" user presets:') . '
1304  admPanel.enable.all = 1
1305  ';
1306  if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('sys_note')) {
1307  $this->TSdataArray[] = '
1308  // Setting defaults for sys_note author / email...
1309  TCAdefaults.sys_note.author = ' . $this->user['realName'] . '
1310  TCAdefaults.sys_note.email = ' . $this->user['email'] . '
1311  ';
1312  }
1313  }
1314  // BE_GROUPS:
1315  // Get the groups...
1316  if (!empty($this->user[$this->usergroup_column])) {
1317  // Fetch groups will add a lot of information to the internal arrays: modules, accesslists, TSconfig etc.
1318  // Refer to fetchGroups() function.
1319  $this->fetchGroups($this->user[$this->usergroup_column]);
1320  }
1321 
1322  // Populating the $this->userGroupsUID -array with the groups in the order in which they were LAST included.!!
1323  $this->userGroupsUID = array_reverse(array_unique(array_reverse($this->includeGroupArray)));
1324  // Finally this is the list of group_uid's in the order they are parsed (including subgroups!)
1325  // and without duplicates (duplicates are presented with their last entrance in the list,
1326  // which thus reflects the order of the TypoScript in TSconfig)
1327  $this->groupList = implode(',', $this->userGroupsUID);
1328  $this->setCachedList($this->groupList);
1329 
1330  // Add the TSconfig for this specific user:
1331  $this->TSdataArray[] = $this->addTScomment('USER TSconfig field') . $this->user['TSconfig'];
1332  // Check include lines.
1333  $this->TSdataArray = \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines_array($this->TSdataArray);
1334  // Imploding with "[global]" will make sure that non-ended confinements with braces are ignored.
1335  $this->userTS_text = implode(LF . '[GLOBAL]' . LF, $this->TSdataArray);
1336  if (!$this->userTS_dontGetCached) {
1337  // Perform TS-Config parsing with condition matching
1338  $parseObj = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TsConfigParser::class);
1339  $res = $parseObj->parseTSconfig($this->userTS_text, 'userTS');
1340  if ($res) {
1341  $this->userTS = $res['TSconfig'];
1342  $this->userTSUpdated = (bool)$res['cached'];
1343  }
1344  } else {
1345  // Parsing the user TSconfig (or getting from cache)
1346  $hash = md5('userTS:' . $this->userTS_text);
1347  $cachedContent = BackendUtility::getHash($hash);
1348  if (is_array($cachedContent) && !$this->userTS_dontGetCached) {
1349  $this->userTS = $cachedContent;
1350  } else {
1351  $parseObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::class);
1352  $parseObj->parse($this->userTS_text);
1353  $this->userTS = $parseObj->setup;
1354  BackendUtility::storeHash($hash, $this->userTS, 'BE_USER_TSconfig');
1355  // Update UC:
1356  $this->userTSUpdated = true;
1357  }
1358  }
1359  // Processing webmounts
1360  // Admin's always have the root mounted
1361  if ($this->isAdmin() && !$this->getTSConfigVal('options.dontMountAdminMounts')) {
1362  $this->dataLists['webmount_list'] = '0,' . $this->dataLists['webmount_list'];
1363  }
1364  // The lists are cleaned for duplicates
1365  $this->groupData['webmounts'] = GeneralUtility::uniqueList($this->dataLists['webmount_list']);
1366  $this->groupData['pagetypes_select'] = GeneralUtility::uniqueList($this->dataLists['pagetypes_select']);
1367  $this->groupData['tables_select'] = GeneralUtility::uniqueList($this->dataLists['tables_modify'] . ',' . $this->dataLists['tables_select']);
1368  $this->groupData['tables_modify'] = GeneralUtility::uniqueList($this->dataLists['tables_modify']);
1369  $this->groupData['non_exclude_fields'] = GeneralUtility::uniqueList($this->dataLists['non_exclude_fields']);
1370  $this->groupData['explicit_allowdeny'] = GeneralUtility::uniqueList($this->dataLists['explicit_allowdeny']);
1371  $this->groupData['allowed_languages'] = GeneralUtility::uniqueList($this->dataLists['allowed_languages']);
1372  $this->groupData['custom_options'] = GeneralUtility::uniqueList($this->dataLists['custom_options']);
1373  $this->groupData['modules'] = GeneralUtility::uniqueList($this->dataLists['modList']);
1374  $this->groupData['file_permissions'] = GeneralUtility::uniqueList($this->dataLists['file_permissions']);
1375  $this->groupData['workspace_perms'] = $this->dataLists['workspace_perms'];
1376 
1377  // Checking read access to webmounts:
1378  if (trim($this->groupData['webmounts']) !== '') {
1379  $webmounts = explode(',', $this->groupData['webmounts']);
1380  // Explode mounts
1381  // Selecting all webmounts with permission clause for reading
1382  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1383  $queryBuilder->getRestrictions()
1384  ->removeAll()
1385  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1386 
1387  $MProws = $queryBuilder->select('uid')
1388  ->from('pages')
1389  // @todo DOCTRINE: check how to make getPagePermsClause() portable
1390  ->where(
1391  $this->getPagePermsClause(1),
1392  $queryBuilder->expr()->in(
1393  'uid',
1394  $queryBuilder->createNamedParameter(
1395  GeneralUtility::intExplode(',', $this->groupData['webmounts']),
1396  Connection::PARAM_INT_ARRAY
1397  )
1398  )
1399  )
1400  ->execute()
1401  ->fetchAll();
1402  $MProws = array_column(($MProws ?: []), 'uid', 'uid');
1403  foreach ($webmounts as $idx => $mountPointUid) {
1404  // If the mount ID is NOT found among selected pages, unset it:
1405  if ($mountPointUid > 0 && !isset($MProws[$mountPointUid])) {
1406  unset($webmounts[$idx]);
1407  }
1408  }
1409  // Implode mounts in the end.
1410  $this->groupData['webmounts'] = implode(',', $webmounts);
1411  }
1412  // Setting up workspace situation (after webmounts are processed!):
1413  $this->workspaceInit();
1414  }
1415  }
1416 
1425  public function fetchGroups($grList, $idList = '')
1426  {
1427  // Fetching records of the groups in $grList (which are not blocked by lockedToDomain either):
1428  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->usergroup_table);
1429  $expressionBuilder = $queryBuilder->expr();
1430  $constraints = $expressionBuilder->andX(
1431  $expressionBuilder->eq(
1432  'pid',
1433  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1434  ),
1435  $expressionBuilder->in(
1436  'uid',
1437  $queryBuilder->createNamedParameter(
1438  GeneralUtility::intExplode(',', $grList),
1439  Connection::PARAM_INT_ARRAY
1440  )
1441  ),
1442  $expressionBuilder->orX(
1443  $expressionBuilder->eq('lockToDomain', $queryBuilder->quote('')),
1444  $expressionBuilder->isNull('lockToDomain'),
1445  $expressionBuilder->eq(
1446  'lockToDomain',
1447  $queryBuilder->createNamedParameter(GeneralUtility::getIndpEnv('HTTP_HOST'), \PDO::PARAM_STR)
1448  )
1449  )
1450  );
1451  // Hook for manipulation of the WHERE sql sentence which controls which BE-groups are included
1452  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroupQuery'])) {
1453  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroupQuery'] as $classRef) {
1454  $hookObj = GeneralUtility::getUserObj($classRef);
1455  if (method_exists($hookObj, 'fetchGroupQuery_processQuery')) {
1456  $constraints = $hookObj->fetchGroupQuery_processQuery($this, $grList, $idList, (string)$constraints);
1457  }
1458  }
1459  }
1460  $res = $queryBuilder->select('*')
1461  ->from($this->usergroup_table)
1462  ->where($constraints)
1463  ->execute();
1464  // The userGroups array is filled
1465  while ($row = $res->fetch(\PDO::FETCH_ASSOC)) {
1466  $this->userGroups[$row['uid']] = $row;
1467  }
1468  // Traversing records in the correct order
1469  foreach (explode(',', $grList) as $uid) {
1470  // Get row:
1471  $row = $this->userGroups[$uid];
1472  // Must be an array and $uid should not be in the idList, because then it is somewhere previously in the grouplist
1473  if (is_array($row) && !GeneralUtility::inList($idList, $uid)) {
1474  // Include sub groups
1475  if (trim($row['subgroup'])) {
1476  // Make integer list
1477  $theList = implode(',', GeneralUtility::intExplode(',', $row['subgroup']));
1478  // Call recursively, pass along list of already processed groups so they are not recursed again.
1479  $this->fetchGroups($theList, $idList . ',' . $uid);
1480  }
1481  // Add the group uid, current list, TSconfig to the internal arrays.
1482  $this->includeGroupArray[] = $uid;
1483  $this->TSdataArray[] = $this->addTScomment('Group "' . $row['title'] . '" [' . $row['uid'] . '] TSconfig field:') . $row['TSconfig'];
1484  // Mount group database-mounts
1485  if (($this->user['options'] & Permission::PAGE_SHOW) == 1) {
1486  $this->dataLists['webmount_list'] .= ',' . $row['db_mountpoints'];
1487  }
1488  // Mount group file-mounts
1489  if (($this->user['options'] & Permission::PAGE_EDIT) == 2) {
1490  $this->dataLists['filemount_list'] .= ',' . $row['file_mountpoints'];
1491  }
1492  // The lists are made: groupMods, tables_select, tables_modify, pagetypes_select, non_exclude_fields, explicit_allowdeny, allowed_languages, custom_options
1493  $this->dataLists['modList'] .= ',' . $row['groupMods'];
1494  $this->dataLists['tables_select'] .= ',' . $row['tables_select'];
1495  $this->dataLists['tables_modify'] .= ',' . $row['tables_modify'];
1496  $this->dataLists['pagetypes_select'] .= ',' . $row['pagetypes_select'];
1497  $this->dataLists['non_exclude_fields'] .= ',' . $row['non_exclude_fields'];
1498  $this->dataLists['explicit_allowdeny'] .= ',' . $row['explicit_allowdeny'];
1499  $this->dataLists['allowed_languages'] .= ',' . $row['allowed_languages'];
1500  $this->dataLists['custom_options'] .= ',' . $row['custom_options'];
1501  $this->dataLists['file_permissions'] .= ',' . $row['file_permissions'];
1502  // Setting workspace permissions:
1503  $this->dataLists['workspace_perms'] |= $row['workspace_perms'];
1504  // If this function is processing the users OWN group-list (not subgroups) AND
1505  // if the ->firstMainGroup is not set, then the ->firstMainGroup will be set.
1506  if ($idList === '' && !$this->firstMainGroup) {
1507  $this->firstMainGroup = $uid;
1508  }
1509  }
1510  }
1511  // HOOK: fetchGroups_postProcessing
1512  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroups_postProcessing'])) {
1513  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroups_postProcessing'] as $_funcRef) {
1514  $_params = [];
1515  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1516  }
1517  }
1518  }
1519 
1530  public function setCachedList($cList)
1531  {
1532  if ((string)$cList != (string)$this->user['usergroup_cached_list']) {
1533  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users')->update(
1534  'be_users',
1535  ['usergroup_cached_list' => $cList],
1536  ['uid' => (int)$this->user['uid']]
1537  );
1538  }
1539  }
1540 
1545  protected function initializeFileStorages()
1546  {
1547  $this->fileStorages = [];
1549  $storageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class);
1550  // Admin users have all file storages visible, without any filters
1551  if ($this->isAdmin()) {
1552  $storageObjects = $storageRepository->findAll();
1553  foreach ($storageObjects as $storageObject) {
1554  $this->fileStorages[$storageObject->getUid()] = $storageObject;
1555  }
1556  } else {
1557  // Regular users only have storages that are defined in their filemounts
1558  // Permissions and file mounts for the storage are added in StoragePermissionAspect
1559  foreach ($this->getFileMountRecords() as $row) {
1560  if (!array_key_exists((int)$row['base'], $this->fileStorages)) {
1561  $storageObject = $storageRepository->findByUid($row['base']);
1562  if ($storageObject) {
1563  $this->fileStorages[$storageObject->getUid()] = $storageObject;
1564  }
1565  }
1566  }
1567  }
1568 
1569  // This has to be called always in order to set certain filters
1571  }
1572 
1579  public function getCategoryMountPoints()
1580  {
1581  $categoryMountPoints = '';
1582 
1583  // Category mounts of the groups
1584  if (is_array($this->userGroups)) {
1585  foreach ($this->userGroups as $group) {
1586  if ($group['category_perms']) {
1587  $categoryMountPoints .= ',' . $group['category_perms'];
1588  }
1589  }
1590  }
1591 
1592  // Category mounts of the user record
1593  if ($this->user['category_perms']) {
1594  $categoryMountPoints .= ',' . $this->user['category_perms'];
1595  }
1596 
1597  // Make the ids unique
1598  $categoryMountPoints = GeneralUtility::trimExplode(',', $categoryMountPoints);
1599  $categoryMountPoints = array_filter($categoryMountPoints); // remove empty value
1600  $categoryMountPoints = array_unique($categoryMountPoints); // remove unique value
1601 
1602  return $categoryMountPoints;
1603  }
1604 
1612  public function getFileMountRecords()
1613  {
1614  static $fileMountRecordCache = [];
1615 
1616  if (!empty($fileMountRecordCache)) {
1617  return $fileMountRecordCache;
1618  }
1619 
1620  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1621 
1622  // Processing file mounts (both from the user and the groups)
1623  $fileMounts = array_unique(GeneralUtility::intExplode(',', $this->dataLists['filemount_list'], true));
1624 
1625  // Limit file mounts if set in workspace record
1626  if ($this->workspace > 0 && !empty($this->workspaceRec['file_mountpoints'])) {
1627  $workspaceFileMounts = GeneralUtility::intExplode(',', $this->workspaceRec['file_mountpoints'], true);
1628  $fileMounts = array_intersect($fileMounts, $workspaceFileMounts);
1629  }
1630 
1631  if (!empty($fileMounts)) {
1632  $orderBy = $GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'] ?? 'sorting';
1633 
1634  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_filemounts');
1635  $queryBuilder->getRestrictions()
1636  ->removeAll()
1637  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1638  ->add(GeneralUtility::makeInstance(HiddenRestriction::class))
1639  ->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
1640 
1641  $queryBuilder->select('*')
1642  ->from('sys_filemounts')
1643  ->where(
1644  $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($fileMounts, Connection::PARAM_INT_ARRAY))
1645  );
1646 
1647  foreach (QueryHelper::parseOrderBy($orderBy) as $fieldAndDirection) {
1648  $queryBuilder->addOrderBy(...$fieldAndDirection);
1649  }
1650 
1651  $fileMountRecords = $queryBuilder->execute()->fetchAll(\PDO::FETCH_ASSOC);
1652  if ($fileMountRecords !== false) {
1653  foreach ($fileMountRecords as $fileMount) {
1654  $fileMountRecordCache[$fileMount['base'] . $fileMount['path']] = $fileMount;
1655  }
1656  }
1657  }
1658 
1659  // Read-only file mounts
1660  $readOnlyMountPoints = trim($GLOBALS['BE_USER']->getTSConfigVal('options.folderTree.altElementBrowserMountPoints'));
1661  if ($readOnlyMountPoints) {
1662  // We cannot use the API here but need to fetch the default storage record directly
1663  // to not instantiate it (which directly applies mount points) before all mount points are resolved!
1664  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_storage');
1665  $defaultStorageRow = $queryBuilder->select('uid')
1666  ->from('sys_file_storage')
1667  ->where(
1668  $queryBuilder->expr()->eq('is_default', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
1669  )
1670  ->setMaxResults(1)
1671  ->execute()
1672  ->fetch(\PDO::FETCH_ASSOC);
1673 
1674  $readOnlyMountPointArray = GeneralUtility::trimExplode(',', $readOnlyMountPoints);
1675  foreach ($readOnlyMountPointArray as $readOnlyMountPoint) {
1676  $readOnlyMountPointConfiguration = GeneralUtility::trimExplode(':', $readOnlyMountPoint);
1677  if (count($readOnlyMountPointConfiguration) === 2) {
1678  // A storage is passed in the configuration
1679  $storageUid = (int)$readOnlyMountPointConfiguration[0];
1680  $path = $readOnlyMountPointConfiguration[1];
1681  } else {
1682  if (empty($defaultStorageRow)) {
1683  throw new \RuntimeException('Read only mount points have been defined in User TsConfig without specific storage, but a default storage could not be resolved.', 1404472382);
1684  }
1685  // Backwards compatibility: If no storage is passed, we use the default storage
1686  $storageUid = $defaultStorageRow['uid'];
1687  $path = $readOnlyMountPointConfiguration[0];
1688  }
1689  $fileMountRecordCache[$storageUid . $path] = [
1690  'base' => $storageUid,
1691  'title' => $path,
1692  'path' => $path,
1693  'read_only' => true
1694  ];
1695  }
1696  }
1697 
1698  // Personal or Group filemounts are not accessible if file mount list is set in workspace record
1699  if ($this->workspace <= 0 || empty($this->workspaceRec['file_mountpoints'])) {
1700  // If userHomePath is set, we attempt to mount it
1701  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath']) {
1702  list($userHomeStorageUid, $userHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath'], 2);
1703  $userHomeStorageUid = (int)$userHomeStorageUid;
1704  $userHomeFilter = '/' . ltrim($userHomeFilter, '/');
1705  if ($userHomeStorageUid > 0) {
1706  // Try and mount with [uid]_[username]
1707  $path = $userHomeFilter . $this->user['uid'] . '_' . $this->user['username'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir'];
1708  $fileMountRecordCache[$userHomeStorageUid . $path] = [
1709  'base' => $userHomeStorageUid,
1710  'title' => $this->user['username'],
1711  'path' => $path,
1712  'read_only' => false,
1713  'user_mount' => true
1714  ];
1715  // Try and mount with only [uid]
1716  $path = $userHomeFilter . $this->user['uid'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir'];
1717  $fileMountRecordCache[$userHomeStorageUid . $path] = [
1718  'base' => $userHomeStorageUid,
1719  'title' => $this->user['username'],
1720  'path' => $path,
1721  'read_only' => false,
1722  'user_mount' => true
1723  ];
1724  }
1725  }
1726 
1727  // Mount group home-dirs
1728  if ((is_array($this->user) && $this->user['options'] & Permission::PAGE_EDIT) == 2 && $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'] != '') {
1729  // If groupHomePath is set, we attempt to mount it
1730  list($groupHomeStorageUid, $groupHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'], 2);
1731  $groupHomeStorageUid = (int)$groupHomeStorageUid;
1732  $groupHomeFilter = '/' . ltrim($groupHomeFilter, '/');
1733  if ($groupHomeStorageUid > 0) {
1734  foreach ($this->userGroups as $groupData) {
1735  $path = $groupHomeFilter . $groupData['uid'];
1736  $fileMountRecordCache[$groupHomeStorageUid . $path] = [
1737  'base' => $groupHomeStorageUid,
1738  'title' => $groupData['title'],
1739  'path' => $path,
1740  'read_only' => false,
1741  'user_mount' => true
1742  ];
1743  }
1744  }
1745  }
1746  }
1747 
1748  return $fileMountRecordCache;
1749  }
1750 
1759  public function getFileStorages()
1760  {
1761  // Initializing file mounts after the groups are fetched
1762  if ($this->fileStorages === null) {
1763  $this->initializeFileStorages();
1764  }
1765  return $this->fileStorages;
1766  }
1767 
1774  {
1775  // Add the option for also displaying the non-hidden files
1776  if ($this->uc['showHiddenFilesAndFolders']) {
1778  }
1779  }
1780 
1817  public function getFilePermissions()
1818  {
1819  if (!isset($this->filePermissions)) {
1820  $filePermissions = [
1821  // File permissions
1822  'addFile' => false,
1823  'readFile' => false,
1824  'writeFile' => false,
1825  'copyFile' => false,
1826  'moveFile' => false,
1827  'renameFile' => false,
1828  'deleteFile' => false,
1829  // Folder permissions
1830  'addFolder' => false,
1831  'readFolder' => false,
1832  'writeFolder' => false,
1833  'copyFolder' => false,
1834  'moveFolder' => false,
1835  'renameFolder' => false,
1836  'deleteFolder' => false,
1837  'recursivedeleteFolder' => false
1838  ];
1839  if ($this->isAdmin()) {
1840  $filePermissions = array_map('is_bool', $filePermissions);
1841  } else {
1842  $userGroupRecordPermissions = GeneralUtility::trimExplode(',', $this->groupData['file_permissions'], true);
1843  array_walk(
1844  $userGroupRecordPermissions,
1845  function ($permission) use (&$filePermissions) {
1846  $filePermissions[$permission] = true;
1847  }
1848  );
1849 
1850  // Finally overlay any userTSconfig
1851  $permissionsTsConfig = $this->getTSConfigProp('permissions.file.default');
1852  if (!empty($permissionsTsConfig)) {
1853  array_walk(
1854  $permissionsTsConfig,
1855  function ($value, $permission) use (&$filePermissions) {
1856  $filePermissions[$permission] = (bool)$value;
1857  }
1858  );
1859  }
1860  }
1861  $this->filePermissions = $filePermissions;
1862  }
1863  return $this->filePermissions;
1864  }
1865 
1876  public function getFilePermissionsForStorage(\TYPO3\CMS\Core\Resource\ResourceStorage $storageObject)
1877  {
1878  $finalUserPermissions = $this->getFilePermissions();
1879  if (!$this->isAdmin()) {
1880  $storageFilePermissions = $this->getTSConfigProp('permissions.file.storage.' . $storageObject->getUid());
1881  if (!empty($storageFilePermissions)) {
1882  array_walk(
1883  $storageFilePermissions,
1884  function ($value, $permission) use (&$finalUserPermissions) {
1885  $finalUserPermissions[$permission] = (bool)$value;
1886  }
1887  );
1888  }
1889  }
1890  return $finalUserPermissions;
1891  }
1892 
1910  public function getDefaultUploadFolder($pid = null, $table = null, $field = null)
1911  {
1912  $uploadFolder = $this->getTSConfigVal('options.defaultUploadFolder');
1913  if ($uploadFolder) {
1914  $uploadFolder = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($uploadFolder);
1915  } else {
1916  foreach ($this->getFileStorages() as $storage) {
1917  if ($storage->isDefault() && $storage->isWritable()) {
1918  try {
1919  $uploadFolder = $storage->getDefaultFolder();
1920  if ($uploadFolder->checkActionPermission('write')) {
1921  break;
1922  }
1923  $uploadFolder = null;
1924  } catch (\TYPO3\CMS\Core\Resource\Exception $folderAccessException) {
1925  // If the folder is not accessible (no permissions / does not exist) we skip this one.
1926  }
1927  break;
1928  }
1929  }
1930  if (!$uploadFolder instanceof \TYPO3\CMS\Core\Resource\Folder) {
1932  foreach ($this->getFileStorages() as $storage) {
1933  if ($storage->isWritable()) {
1934  try {
1935  $uploadFolder = $storage->getDefaultFolder();
1936  if ($uploadFolder->checkActionPermission('write')) {
1937  break;
1938  }
1939  $uploadFolder = null;
1940  } catch (\TYPO3\CMS\Core\Resource\Exception $folderAccessException) {
1941  // If the folder is not accessible (no permissions / does not exist) try the next one.
1942  }
1943  }
1944  }
1945  }
1946  }
1947 
1948  // HOOK: getDefaultUploadFolder
1949  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder'])) {
1950  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder'] as $_funcRef) {
1951  $_params = [
1952  'uploadFolder' => $uploadFolder,
1953  'pid' => $pid,
1954  'table' => $table,
1955  'field' => $field,
1956  ];
1957  $uploadFolder = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1958  }
1959  }
1960 
1961  if ($uploadFolder instanceof \TYPO3\CMS\Core\Resource\Folder) {
1962  return $uploadFolder;
1963  }
1964  return false;
1965  }
1966 
1976  {
1977  $defaultTemporaryFolder = null;
1978  $defaultFolder = $this->getDefaultUploadFolder();
1979 
1980  if ($defaultFolder !== false) {
1981  $tempFolderName = '_temp_';
1982  $createFolder = !$defaultFolder->hasFolder($tempFolderName);
1983  if ($createFolder === true) {
1984  try {
1985  $defaultTemporaryFolder = $defaultFolder->createFolder($tempFolderName);
1986  } catch (\TYPO3\CMS\Core\Resource\Exception $folderAccessException) {
1987  }
1988  } else {
1989  $defaultTemporaryFolder = $defaultFolder->getSubfolder($tempFolderName);
1990  }
1991  }
1992 
1993  return $defaultTemporaryFolder;
1994  }
1995 
2002  public function addTScomment($str)
2003  {
2004  $delimiter = '# ***********************************************';
2005  $out = $delimiter . LF;
2006  $lines = GeneralUtility::trimExplode(LF, $str);
2007  foreach ($lines as $v) {
2008  $out .= '# ' . $v . LF;
2009  }
2010  $out .= $delimiter . LF;
2011  return $out;
2012  }
2013 
2020  public function workspaceInit()
2021  {
2022  // Initializing workspace by evaluating and setting the workspace, possibly updating it in the user record!
2023  $this->setWorkspace($this->user['workspace_id']);
2024  // Limiting the DB mountpoints if there any selected in the workspace record
2026  if ($allowed_languages = $this->getTSConfigVal('options.workspaces.allowed_languages.' . $this->workspace)) {
2027  $this->groupData['allowed_languages'] = $allowed_languages;
2028  $this->groupData['allowed_languages'] = GeneralUtility::uniqueList($this->groupData['allowed_languages']);
2029  }
2030  }
2031 
2036  {
2037  $dbMountpoints = trim($this->workspaceRec['db_mountpoints'] ?? '');
2038  if ($this->workspace > 0 && $dbMountpoints != '') {
2039  $filteredDbMountpoints = [];
2040  // Notice: We cannot call $this->getPagePermsClause(1);
2041  // as usual because the group-list is not available at this point.
2042  // But bypassing is fine because all we want here is check if the
2043  // workspace mounts are inside the current webmounts rootline.
2044  // The actual permission checking on page level is done elsewhere
2045  // as usual anyway before the page tree is rendered.
2046  $readPerms = '1=1';
2047  // Traverse mount points of the
2048  $dbMountpoints = GeneralUtility::intExplode(',', $dbMountpoints);
2049  foreach ($dbMountpoints as $mpId) {
2050  if ($this->isInWebMount($mpId, $readPerms)) {
2051  $filteredDbMountpoints[] = $mpId;
2052  }
2053  }
2054  // Re-insert webmounts:
2055  $filteredDbMountpoints = array_unique($filteredDbMountpoints);
2056  $this->groupData['webmounts'] = implode(',', $filteredDbMountpoints);
2057  }
2058  }
2059 
2067  public function checkWorkspace($wsRec, $fields = 'uid,title,adminusers,members,reviewers,publish_access,stagechg_notification')
2068  {
2069  $retVal = false;
2070  // If not array, look up workspace record:
2071  if (!is_array($wsRec)) {
2072  switch ((string)$wsRec) {
2073  case '0':
2074  $wsRec = ['uid' => $wsRec];
2075  break;
2076  default:
2077  if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
2078  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
2079  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
2080  $wsRec = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields))
2081  ->from('sys_workspace')
2082  ->where($queryBuilder->expr()->eq(
2083  'uid',
2084  $queryBuilder->createNamedParameter($wsRec, \PDO::PARAM_INT)
2085  ))
2086  ->orderBy('title')
2087  ->setMaxResults(1)
2088  ->execute()
2089  ->fetch(\PDO::FETCH_ASSOC);
2090  }
2091  }
2092  }
2093  // If wsRec is set to an array, evaluate it:
2094  if (is_array($wsRec)) {
2095  if ($this->isAdmin()) {
2096  return array_merge($wsRec, ['_ACCESS' => 'admin']);
2097  }
2098  switch ((string)$wsRec['uid']) {
2099  case '0':
2100  $retVal = $this->groupData['workspace_perms'] & Permission::PAGE_SHOW
2101  ? array_merge($wsRec, ['_ACCESS' => 'online'])
2102  : false;
2103  break;
2104  default:
2105  // Checking if the guy is admin:
2106  if (GeneralUtility::inList($wsRec['adminusers'], 'be_users_' . $this->user['uid'])) {
2107  return array_merge($wsRec, ['_ACCESS' => 'owner']);
2108  }
2109  // Checking if he is owner through a user group of his:
2110  foreach ($this->userGroupsUID as $groupUid) {
2111  if (GeneralUtility::inList($wsRec['adminusers'], 'be_groups_' . $groupUid)) {
2112  return array_merge($wsRec, ['_ACCESS' => 'owner']);
2113  }
2114  }
2115  // Checking if he is reviewer user:
2116  if (GeneralUtility::inList($wsRec['reviewers'], 'be_users_' . $this->user['uid'])) {
2117  return array_merge($wsRec, ['_ACCESS' => 'reviewer']);
2118  }
2119  // Checking if he is reviewer through a user group of his:
2120  foreach ($this->userGroupsUID as $groupUid) {
2121  if (GeneralUtility::inList($wsRec['reviewers'], 'be_groups_' . $groupUid)) {
2122  return array_merge($wsRec, ['_ACCESS' => 'reviewer']);
2123  }
2124  }
2125  // Checking if he is member as user:
2126  if (GeneralUtility::inList($wsRec['members'], 'be_users_' . $this->user['uid'])) {
2127  return array_merge($wsRec, ['_ACCESS' => 'member']);
2128  }
2129  // Checking if he is member through a user group of his:
2130  foreach ($this->userGroupsUID as $groupUid) {
2131  if (GeneralUtility::inList($wsRec['members'], 'be_groups_' . $groupUid)) {
2132  return array_merge($wsRec, ['_ACCESS' => 'member']);
2133  }
2134  }
2135  }
2136  }
2137  return $retVal;
2138  }
2139 
2147  public function checkWorkspaceCurrent()
2148  {
2149  if (!isset($this->checkWorkspaceCurrent_cache)) {
2150  $this->checkWorkspaceCurrent_cache = $this->checkWorkspace($this->workspace);
2151  }
2153  }
2154 
2160  public function setWorkspace($workspaceId)
2161  {
2162  // Check workspace validity and if not found, revert to default workspace.
2163  if (!$this->setTemporaryWorkspace($workspaceId)) {
2164  $this->setDefaultWorkspace();
2165  }
2166  // Unset access cache:
2167  $this->checkWorkspaceCurrent_cache = null;
2168  // If ID is different from the stored one, change it:
2169  if ((int)$this->workspace !== (int)$this->user['workspace_id']) {
2170  $this->user['workspace_id'] = $this->workspace;
2171  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users')->update(
2172  'be_users',
2173  ['workspace_id' => $this->user['workspace_id']],
2174  ['uid' => (int)$this->user['uid']]
2175  );
2176  $this->simplelog('User changed workspace to "' . $this->workspace . '"');
2177  }
2178  }
2179 
2186  public function setTemporaryWorkspace($workspaceId)
2187  {
2188  $result = false;
2189  $workspaceRecord = $this->checkWorkspace($workspaceId, '*');
2190 
2191  if ($workspaceRecord) {
2192  $this->workspaceRec = $workspaceRecord;
2193  $this->workspace = (int)$workspaceId;
2194  $result = true;
2195  }
2196 
2197  return $result;
2198  }
2199 
2203  public function setDefaultWorkspace()
2204  {
2205  $this->workspace = (int)$this->getDefaultWorkspace();
2206  $this->workspaceRec = $this->checkWorkspace($this->workspace, '*');
2207  }
2208 
2214  public function setWorkspacePreview($previewState)
2215  {
2216  $this->user['workspace_preview'] = $previewState;
2217  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users')->update(
2218  'be_users',
2219  ['workspace_preview_id' => $this->user['workspace_preview']],
2220  ['uid' => (int)$this->user['uid']]
2221  );
2222  }
2223 
2231  public function getDefaultWorkspace()
2232  {
2233  $defaultWorkspace = -99;
2234  if (!\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces') || $this->checkWorkspace(0)) {
2235  // Check online
2236  $defaultWorkspace = 0;
2237  } elseif ($this->checkWorkspace(-1)) {
2238  // Check offline
2239  $defaultWorkspace = -1;
2240  } elseif (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
2241  // Traverse custom workspaces:
2242  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
2243  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
2244  $workspaces = $queryBuilder->select('uid', 'title', 'adminusers', 'members', 'reviewers')
2245  ->from('sys_workspace')
2246  ->orderBy('title')
2247  ->execute()
2248  ->fetchAll(\PDO::FETCH_ASSOC);
2249 
2250  if ($workspaces !== false) {
2251  foreach ($workspaces as $rec) {
2252  if ($this->checkWorkspace($rec)) {
2253  $defaultWorkspace = $rec['uid'];
2254  break;
2255  }
2256  }
2257  }
2258  }
2259  return $defaultWorkspace;
2260  }
2261 
2280  public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename = '', $recuid = '', $recpid = '', $event_pid = -1, $NEWid = '', $userId = 0)
2281  {
2282  if (!$userId && !empty($this->user['uid'])) {
2283  $userId = $this->user['uid'];
2284  }
2285 
2286  if (!empty($this->user['ses_backuserid'])) {
2287  if (empty($data)) {
2288  $data = [];
2289  }
2290  $data['originalUser'] = $this->user['ses_backuserid'];
2291  }
2292 
2293  $fields = [
2294  'userid' => (int)$userId,
2295  'type' => (int)$type,
2296  'action' => (int)$action,
2297  'error' => (int)$error,
2298  'details_nr' => (int)$details_nr,
2299  'details' => $details,
2300  'log_data' => serialize($data),
2301  'tablename' => $tablename,
2302  'recuid' => (int)$recuid,
2303  'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
2304  'tstamp' => $GLOBALS['EXEC_TIME'] ?? time(),
2305  'event_pid' => (int)$event_pid,
2306  'NEWid' => $NEWid,
2307  'workspace' => $this->workspace
2308  ];
2309 
2310  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_log');
2311  $connection->insert(
2312  'sys_log',
2313  $fields,
2314  [
2315  \PDO::PARAM_INT,
2316  \PDO::PARAM_INT,
2317  \PDO::PARAM_INT,
2318  \PDO::PARAM_INT,
2319  \PDO::PARAM_INT,
2320  \PDO::PARAM_STR,
2321  \PDO::PARAM_STR,
2322  \PDO::PARAM_STR,
2323  \PDO::PARAM_INT,
2324  \PDO::PARAM_STR,
2325  \PDO::PARAM_INT,
2326  \PDO::PARAM_INT,
2327  \PDO::PARAM_STR,
2328  \PDO::PARAM_STR,
2329  ]
2330  );
2331 
2332  return (int)$connection->lastInsertId('sys_log');
2333  }
2334 
2343  public function simplelog($message, $extKey = '', $error = 0)
2344  {
2345  return $this->writelog(4, 0, $error, 0, ($extKey ? '[' . $extKey . '] ' : '') . $message, []);
2346  }
2347 
2359  public function checkLogFailures($email, $secondsBack = 3600, $max = 3)
2360  {
2361  if ($email) {
2362  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
2363 
2364  // Get last flag set in the log for sending
2365  $theTimeBack = $GLOBALS['EXEC_TIME'] - $secondsBack;
2366  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
2367  $queryBuilder->select('tstamp')
2368  ->from('sys_log')
2369  ->where(
2370  $queryBuilder->expr()->eq(
2371  'type',
2372  $queryBuilder->createNamedParameter(255, \PDO::PARAM_INT)
2373  ),
2374  $queryBuilder->expr()->eq(
2375  'action',
2376  $queryBuilder->createNamedParameter(4, \PDO::PARAM_INT)
2377  ),
2378  $queryBuilder->expr()->gt(
2379  'tstamp',
2380  $queryBuilder->createNamedParameter($theTimeBack, \PDO::PARAM_INT)
2381  )
2382  )
2383  ->orderBy('tstamp', 'DESC')
2384  ->setMaxResults(1);
2385  if ($testRow = $queryBuilder->execute()->fetch(\PDO::FETCH_ASSOC)) {
2386  $theTimeBack = $testRow['tstamp'];
2387  }
2388 
2389  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
2390  $result = $queryBuilder->select('*')
2391  ->from('sys_log')
2392  ->where(
2393  $queryBuilder->expr()->eq(
2394  'type',
2395  $queryBuilder->createNamedParameter(255, \PDO::PARAM_INT)
2396  ),
2397  $queryBuilder->expr()->eq(
2398  'action',
2399  $queryBuilder->createNamedParameter(3, \PDO::PARAM_INT)
2400  ),
2401  $queryBuilder->expr()->neq(
2402  'error',
2403  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
2404  ),
2405  $queryBuilder->expr()->gt(
2406  'tstamp',
2407  $queryBuilder->createNamedParameter($theTimeBack, \PDO::PARAM_INT)
2408  )
2409  )
2410  ->orderBy('tstamp')
2411  ->execute();
2412 
2413  // Check for more than $max number of error failures with the last period.
2414  if ($result->rowCount() > $max) {
2415  // OK, so there were more than the max allowed number of login failures - so we will send an email then.
2416  $subject = 'TYPO3 Login Failure Warning (at ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ')';
2417  $email_body = 'There have been some attempts (' . $result->rowCount() . ') to login at the TYPO3
2418 site "' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '" (' . GeneralUtility::getIndpEnv('HTTP_HOST') . ').
2419 
2420 This is a dump of the failures:
2421 
2422 ';
2423  while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
2424  $theData = unserialize($row['log_data']);
2425  $email_body .= date(
2426  $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
2427  $row['tstamp']
2428  ) . ': ' . @sprintf($row['details'], (string)$theData[0], (string)$theData[1], (string)$theData[2]);
2429  $email_body .= LF;
2430  }
2432  $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
2433  $mail->setTo($email)->setSubject($subject)->setBody($email_body);
2434  $mail->send();
2435  // Logout written to log
2436  $this->writelog(255, 4, 0, 3, 'Failure warning (%s failures within %s seconds) sent by email to %s', [$result->rowCount(), $secondsBack, $email]);
2437  }
2438  }
2439  }
2440 
2447  public static function getCookieName()
2448  {
2449  $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName']);
2450  if (empty($configuredCookieName)) {
2451  $configuredCookieName = 'be_typo_user';
2452  }
2453  return $configuredCookieName;
2454  }
2455 
2463  public function checkLockToIP()
2464  {
2465  $isValid = true;
2466  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['enabledBeUserIPLock']) {
2467  $IPList = $this->getTSConfigVal('options.lockToIP');
2468  if (trim($IPList)) {
2469  $isValid = GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $IPList);
2470  }
2471  }
2472  return $isValid;
2473  }
2474 
2484  public function backendCheckLogin($proceedIfNoUserIsLoggedIn = false)
2485  {
2486  if (empty($this->user['uid'])) {
2487  if ($proceedIfNoUserIsLoggedIn === false) {
2488  $url = GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir;
2490  }
2491  } else {
2492  // ...and if that's the case, call these functions
2493  $this->fetchGroupData();
2494  // The groups are fetched and ready for permission checking in this initialization.
2495  // Tables.php must be read before this because stuff like the modules has impact in this
2496  if ($this->checkLockToIP()) {
2497  if ($this->isUserAllowedToLogin()) {
2498  // Setting the UC array. It's needed with fetchGroupData first, due to default/overriding of values.
2499  $this->backendSetUC();
2500  // Email at login - if option set.
2501  $this->emailAtLogin();
2502  } else {
2503  throw new \RuntimeException('Login Error: TYPO3 is in maintenance mode at the moment. Only administrators are allowed access.', 1294585860);
2504  }
2505  } else {
2506  throw new \RuntimeException('Login Error: IP locking prevented you from being authorized. Can\'t proceed, sorry.', 1294585861);
2507  }
2508  }
2509  }
2510 
2517  public function backendSetUC()
2518  {
2519  // UC - user configuration is a serialized array inside the user object
2520  // If there is a saved uc we implement that instead of the default one.
2521  $this->unpack_uc();
2522  // Setting defaults if uc is empty
2523  $updated = false;
2524  $originalUc = [];
2525  if (is_array($this->uc) && isset($this->uc['ucSetByInstallTool'])) {
2526  $originalUc = $this->uc;
2527  unset($originalUc['ucSetByInstallTool'], $this->uc);
2528  }
2529  if (!is_array($this->uc)) {
2530  $this->uc = array_merge(
2531  $this->uc_default,
2532  (array)$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUC'],
2533  GeneralUtility::removeDotsFromTS((array)$this->getTSConfigProp('setup.default')),
2534  $originalUc
2535  );
2536  $this->overrideUC();
2537  $updated = true;
2538  }
2539  // If TSconfig is updated, update the defaultUC.
2540  if ($this->userTSUpdated) {
2541  $this->overrideUC();
2542  $updated = true;
2543  }
2544  // Setting default lang from be_user record.
2545  if (!isset($this->uc['lang'])) {
2546  $this->uc['lang'] = $this->user['lang'];
2547  $updated = true;
2548  }
2549  // Setting the time of the first login:
2550  if (!isset($this->uc['firstLoginTimeStamp'])) {
2551  $this->uc['firstLoginTimeStamp'] = $GLOBALS['EXEC_TIME'];
2552  $updated = true;
2553  }
2554  // Saving if updated.
2555  if ($updated) {
2556  $this->writeUC();
2557  }
2558  }
2559 
2566  public function overrideUC()
2567  {
2568  $this->uc = array_merge((array)$this->uc, (array)$this->getTSConfigProp('setup.override'));
2569  }
2570 
2576  public function resetUC()
2577  {
2578  $this->user['uc'] = '';
2579  $this->uc = '';
2580  $this->backendSetUC();
2581  }
2582 
2589  private function emailAtLogin()
2590  {
2591  if ($this->loginSessionStarted) {
2592  // Send notify-mail
2593  $subject = 'At "' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '"' . ' from '
2594  . GeneralUtility::getIndpEnv('REMOTE_ADDR')
2595  . (GeneralUtility::getIndpEnv('REMOTE_HOST') ? ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')' : '');
2596  $msg = sprintf(
2597  'User "%s" logged in from %s (%s) at "%s" (%s)',
2598  $this->user['username'],
2599  GeneralUtility::getIndpEnv('REMOTE_ADDR'),
2600  GeneralUtility::getIndpEnv('REMOTE_HOST'),
2601  $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
2602  GeneralUtility::getIndpEnv('HTTP_HOST')
2603  );
2604  // Warning email address
2605  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr']) {
2606  $warn = 0;
2607  $prefix = '';
2608  if ((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] & 1) {
2609  // first bit: All logins
2610  $warn = 1;
2611  $prefix = $this->isAdmin() ? '[AdminLoginWarning]' : '[LoginWarning]';
2612  }
2613  if ($this->isAdmin() && (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] & 2) {
2614  // second bit: Only admin-logins
2615  $warn = 1;
2616  $prefix = '[AdminLoginWarning]';
2617  }
2618  if ($warn) {
2620  $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
2621  $mail->setTo($GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'])->setSubject($prefix . ' ' . $subject)->setBody($msg);
2622  $mail->send();
2623  }
2624  }
2625  // If An email should be sent to the current user, do that:
2626  if (($this->uc['emailMeAtLogin'] ?? false) && strstr($this->user['email'] ?? '', '@')) {
2628  $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
2629  $mail->setTo($this->user['email'])->setSubject($subject)->setBody($msg);
2630  $mail->send();
2631  }
2632  }
2633  }
2634 
2646  protected function isUserAllowedToLogin()
2647  {
2648  $isUserAllowedToLogin = false;
2649  $adminOnlyMode = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['adminOnly'];
2650  // Backend user is allowed if adminOnly is not set or user is an admin:
2651  if (!$adminOnlyMode || $this->isAdmin()) {
2652  $isUserAllowedToLogin = true;
2653  } elseif ($this->user['ses_backuserid']) {
2654  $backendUserId = (int)$this->user['ses_backuserid'];
2655  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
2656  $isUserAllowedToLogin = (bool)$queryBuilder->count('uid')
2657  ->from('be_users')
2658  ->where(
2659  $queryBuilder->expr()->eq(
2660  'uid',
2661  $queryBuilder->createNamedParameter($backendUserId, \PDO::PARAM_INT)
2662  ),
2663  $queryBuilder->expr()->eq('admin', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
2664  )
2665  ->execute()
2666  ->fetchColumn(0);
2667  }
2668  return $isUserAllowedToLogin;
2669  }
2670 
2674  public function logoff()
2675  {
2676  if (isset($GLOBALS['BE_USER']) && $GLOBALS['BE_USER'] instanceof self && isset($GLOBALS['BE_USER']->user['uid'])) {
2678  // Release the locked records
2679  $this->releaseLockedRecords((int)$GLOBALS['BE_USER']->user['uid']);
2680  }
2681  parent::logoff();
2682  }
2683 
2688  protected function releaseLockedRecords(int $userId)
2689  {
2690  if ($userId > 0) {
2691  GeneralUtility::makeInstance(ConnectionPool::class)
2692  ->getConnectionForTable('sys_lockedrecords')
2693  ->delete(
2694  'sys_lockedrecords',
2695  ['userid' => $userId]
2696  );
2697  }
2698  }
2699 }
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
checkWorkspace($wsRec, $fields='uid, title, adminusers, members, reviewers, publish_access, stagechg_notification')
writelog($type, $action, $error, $details_nr, $details, $data, $tablename='', $recuid='', $recpid='', $event_pid=-1, $NEWid='', $userId=0)
static setShowHiddenFilesAndFolders($showHiddenFilesAndFolders)
isPSet($compiledPermissions, $tableName, $actionType='')
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
static BEgetRootLine($uid, $clause='', $workspaceOL=false)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
$fields
Definition: pages.php:4
static get($classNameOrType='default',... $constructorArguments)
static redirect($url, $httpStatus=self::HTTP_STATUS_303)
getFilePermissionsForStorage(\TYPO3\CMS\Core\Resource\ResourceStorage $storageObject)
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static uniqueList($in_list, $secondParameter=null)
recordEditAccessInternals($table, $idOrRow, $newRecord=false, $deletedRecord=false, $checkFullLanguageAccess=false)