[BUGFIX] Check default storage first to find default upload folder
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Authentication / BackendUserAuthentication.php
1 <?php
2 namespace TYPO3\CMS\Core\Authentication;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Resource\ResourceStorage;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20
21 /**
22 * TYPO3 backend user authentication
23 * Contains most of the functions used for checking permissions, authenticating users,
24 * setting up the user, and API for user from outside.
25 * This class contains the configuration of the database fields used plus some
26 * functions for the authentication process of backend users.
27 *
28 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
29 * @internal
30 */
31 class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication {
32
33 /**
34 * Should be set to the usergroup-column (id-list) in the user-record
35 * @var string
36 */
37 public $usergroup_column = 'usergroup';
38
39 /**
40 * The name of the group-table
41 * @var string
42 */
43 public $usergroup_table = 'be_groups';
44
45 /**
46 * holds lists of eg. tables, fields and other values related to the permission-system. See fetchGroupData
47 * @var array
48 * @internal
49 */
50 public $groupData = array(
51 'filemounts' => array()
52 );
53
54 /**
55 * This array will hold the groups that the user is a member of
56 * @var array
57 */
58 public $userGroups = array();
59
60 /**
61 * This array holds the uid's of the groups in the listed order
62 * @var array
63 */
64 public $userGroupsUID = array();
65
66 /**
67 * This is $this->userGroupsUID imploded to a comma list... Will correspond to the 'usergroup_cached_list'
68 * @var string
69 */
70 public $groupList = '';
71
72 /**
73 * User workspace.
74 * -99 is ERROR (none available)
75 * -1 is offline
76 * 0 is online
77 * >0 is custom workspaces
78 * @var int
79 */
80 public $workspace = -99;
81
82 /**
83 * Custom workspace record if any
84 * @var array
85 */
86 public $workspaceRec = array();
87
88 /**
89 * Used to accumulate data for the user-group.
90 * DON NOT USE THIS EXTERNALLY!
91 * Use $this->groupData instead
92 * @var array
93 * @internal
94 */
95 public $dataLists = array(
96 'webmount_list' => '',
97 'filemount_list' => '',
98 'file_permissions' => '',
99 'modList' => '',
100 'tables_select' => '',
101 'tables_modify' => '',
102 'pagetypes_select' => '',
103 'non_exclude_fields' => '',
104 'explicit_allowdeny' => '',
105 'allowed_languages' => '',
106 'workspace_perms' => '',
107 'custom_options' => ''
108 );
109
110 /**
111 * For debugging/display of order in which subgroups are included.
112 * @var array
113 */
114 public $includeHierarchy = array();
115
116 /**
117 * List of group_id's in the order they are processed.
118 * @var array
119 */
120 public $includeGroupArray = array();
121
122 /**
123 * Set to 'WIN', if windows
124 * @var string
125 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8, use the constant TYPO3_OS directly
126 */
127 public $OS = '';
128
129 /**
130 * Used to accumulate the TSconfig data of the user
131 * @var array
132 */
133 public $TSdataArray = array();
134
135 /**
136 * Contains the non-parsed user TSconfig
137 * @var string
138 */
139 public $userTS_text = '';
140
141 /**
142 * Contains the parsed user TSconfig
143 * @var array
144 */
145 public $userTS = array();
146
147 /**
148 * Set internally if the user TSconfig was parsed and needs to be cached.
149 * @var bool
150 */
151 public $userTSUpdated = FALSE;
152
153 /**
154 * Set this from outside if you want the user TSconfig to ALWAYS be parsed and not fetched from cache.
155 * @var bool
156 */
157 public $userTS_dontGetCached = FALSE;
158
159 /**
160 * RTE availability errors collected.
161 * @var array
162 */
163 public $RTE_errors = array();
164
165 /**
166 * Contains last error message
167 * @var string
168 */
169 public $errorMsg = '';
170
171 /**
172 * Cache for checkWorkspaceCurrent()
173 * @var array|NULL
174 */
175 public $checkWorkspaceCurrent_cache = NULL;
176
177 /**
178 * @var \TYPO3\CMS\Core\Resource\ResourceStorage[]
179 */
180 protected $fileStorages;
181
182 /**
183 * @var array
184 */
185 protected $filePermissions;
186
187 /**
188 * Table to use for session data
189 * @var string
190 */
191 public $session_table = 'be_sessions';
192
193 /**
194 * Table in database with user data
195 * @var string
196 */
197 public $user_table = 'be_users';
198
199 /**
200 * Column for login-name
201 * @var string
202 */
203 public $username_column = 'username';
204
205 /**
206 * Column for password
207 * @var string
208 */
209 public $userident_column = 'password';
210
211 /**
212 * Column for user-id
213 * @var string
214 */
215 public $userid_column = 'uid';
216
217 /**
218 * @var string
219 */
220 public $lastLogin_column = 'lastlogin';
221
222 /**
223 * @var array
224 */
225 public $enablecolumns = array(
226 'rootLevel' => 1,
227 'deleted' => 'deleted',
228 'disabled' => 'disable',
229 'starttime' => 'starttime',
230 'endtime' => 'endtime'
231 );
232
233 /**
234 * Form field with login-name
235 * @var string
236 */
237 public $formfield_uname = 'username';
238
239 /**
240 * Form field with password
241 * @var string
242 */
243 public $formfield_uident = 'userident';
244
245 /**
246 * Form field with a unique value which is used to encrypt the password and username
247 * @var string
248 */
249 public $formfield_chalvalue = 'challenge';
250
251 /**
252 * Form field with status: *'login', 'logout'
253 * @var string
254 */
255 public $formfield_status = 'login_status';
256
257 /**
258 * Decides if the writelog() function is called at login and logout
259 * @var bool
260 */
261 public $writeStdLog = TRUE;
262
263 /**
264 * If the writelog() functions is called if a login-attempt has be tried without success
265 * @var bool
266 */
267 public $writeAttemptLog = TRUE;
268
269 /**
270 * if > 0 : session-timeout in seconds.
271 * if FALSE/<0 : no timeout.
272 * if string: The string is field name from the user table where the timeout can be found.
273 * @var string|int
274 */
275 public $auth_timeout_field = 6000;
276
277 /**
278 * @var bool
279 */
280 public $challengeStoredInCookie = TRUE;
281
282 /**
283 * @var int
284 */
285 public $firstMainGroup = 0;
286
287 /**
288 * User Config
289 * @var array
290 */
291 public $uc;
292
293 /**
294 * User Config Default values:
295 * The array may contain other fields for configuration.
296 * For this, see "setup" extension and "TSConfig" document (User TSconfig, "setup.[xxx]....")
297 * Reserved keys for other storage of session data:
298 * moduleData
299 * moduleSessionID
300 * @var array
301 */
302 public $uc_default = array(
303 'interfaceSetup' => '',
304 // serialized content that is used to store interface pane and menu positions. Set by the logout.php-script
305 'moduleData' => array(),
306 // user-data for the modules
307 'thumbnailsByDefault' => 1,
308 'emailMeAtLogin' => 0,
309 'startModule' => 'help_AboutmodulesAboutmodules',
310 'hideSubmoduleIcons' => 0,
311 'titleLen' => 50,
312 'edit_RTE' => '1',
313 'edit_docModuleUpload' => '1',
314 'navFrameWidth' => '',
315 // Default is 245 pixels
316 'navFrameResizable' => 0,
317 'resizeTextareas' => 1,
318 'resizeTextareas_MaxHeight' => 500,
319 'resizeTextareas_Flexible' => 0
320 );
321
322 /**
323 * Constructor
324 */
325 public function __construct() {
326 parent::__construct();
327 $this->name = self::getCookieName();
328 $this->loginType = 'BE';
329 $this->OS = TYPO3_OS;
330 }
331
332 /**
333 * Returns TRUE if user is admin
334 * Basically this function evaluates if the ->user[admin] field has bit 0 set. If so, user is admin.
335 *
336 * @return bool
337 */
338 public function isAdmin() {
339 return is_array($this->user) && ($this->user['admin'] & 1) == 1;
340 }
341
342 /**
343 * Returns TRUE if the current user is a member of group $groupId
344 * $groupId must be set. $this->groupList must contain groups
345 * Will return TRUE also if the user is a member of a group through subgroups.
346 *
347 * @param int $groupId Group ID to look for in $this->groupList
348 * @return bool
349 */
350 public function isMemberOfGroup($groupId) {
351 $groupId = (int)$groupId;
352 if ($this->groupList && $groupId) {
353 return GeneralUtility::inList($this->groupList, $groupId);
354 }
355 return FALSE;
356 }
357
358 /**
359 * Checks if the permissions is granted based on a page-record ($row) and $perms (binary and'ed)
360 *
361 * Bits for permissions, see $perms variable:
362 *
363 * 1 - Show: See/Copy page and the pagecontent.
364 * 16- Edit pagecontent: Change/Add/Delete/Move pagecontent.
365 * 2- Edit page: Change/Move the page, eg. change title, startdate, hidden.
366 * 4- Delete page: Delete the page and pagecontent.
367 * 8- New pages: Create new pages under the page.
368 *
369 * @param array $row Is the pagerow for which the permissions is checked
370 * @param int $perms Is the binary representation of the permission we are going to check. Every bit in this number represents a permission that must be set. See function explanation.
371 * @return bool
372 */
373 public function doesUserHaveAccess($row, $perms) {
374 $userPerms = $this->calcPerms($row);
375 return ($userPerms & $perms) == $perms;
376 }
377
378 /**
379 * Checks if the page id, $id, is found within the webmounts set up for the user.
380 * This should ALWAYS be checked for any page id a user works with, whether it's about reading, writing or whatever.
381 * The point is that this will add the security that a user can NEVER touch parts outside his mounted
382 * pages in the page tree. This is otherwise possible if the raw page permissions allows for it.
383 * So this security check just makes it easier to make safe user configurations.
384 * If the user is admin OR if this feature is disabled
385 * (fx. by setting TYPO3_CONF_VARS['BE']['lockBeUserToDBmounts']=0) then it returns "1" right away
386 * Otherwise the function will return the uid of the webmount which was first found in the rootline of the input page $id
387 *
388 * @param int $id Page ID to check
389 * @param string $readPerms Content of "->getPagePermsClause(1)" (read-permissions). If not set, they will be internally calculated (but if you have the correct value right away you can save that database lookup!)
390 * @param bool|int $exitOnError If set, then the function will exit with an error message.
391 * @throws \RuntimeException
392 * @return int|NULL The page UID of a page in the rootline that matched a mount point
393 */
394 public function isInWebMount($id, $readPerms = '', $exitOnError = 0) {
395 if (!$GLOBALS['TYPO3_CONF_VARS']['BE']['lockBeUserToDBmounts'] || $this->isAdmin()) {
396 return 1;
397 }
398 $id = (int)$id;
399 // Check if input id is an offline version page in which case we will map id to the online version:
400 $checkRec = BackendUtility::getRecord('pages', $id, 'pid,t3ver_oid');
401 if ($checkRec['pid'] == -1) {
402 $id = (int)$checkRec['t3ver_oid'];
403 }
404 if (!$readPerms) {
405 $readPerms = $this->getPagePermsClause(1);
406 }
407 if ($id > 0) {
408 $wM = $this->returnWebmounts();
409 $rL = BackendUtility::BEgetRootLine($id, ' AND ' . $readPerms);
410 foreach ($rL as $v) {
411 if ($v['uid'] && in_array($v['uid'], $wM)) {
412 return $v['uid'];
413 }
414 }
415 }
416 if ($exitOnError) {
417 throw new \RuntimeException('Access Error: This page is not within your DB-mounts', 1294586445);
418 }
419 return NULL;
420 }
421
422 /**
423 * Checks access to a backend module with the $MCONF passed as first argument
424 *
425 * @param array $conf $MCONF array of a backend module!
426 * @param bool $exitOnError If set, an array will issue an error message and exit.
427 * @throws \RuntimeException
428 * @return bool Will return TRUE if $MCONF['access'] is not set at all, if the BE_USER is admin or if the module is enabled in the be_users/be_groups records of the user (specifically enabled). Will return FALSE if the module name is not even found in $TBE_MODULES
429 */
430 public function modAccess($conf, $exitOnError) {
431 if (!BackendUtility::isModuleSetInTBE_MODULES($conf['name'])) {
432 if ($exitOnError) {
433 throw new \RuntimeException('Fatal Error: This module "' . $conf['name'] . '" is not enabled in TBE_MODULES', 1294586446);
434 }
435 return FALSE;
436 }
437 // Workspaces check:
438 if (
439 !empty($conf['workspaces'])
440 && \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')
441 && ($this->workspace !== 0 || !GeneralUtility::inList($conf['workspaces'], 'online'))
442 && ($this->workspace !== -1 || !GeneralUtility::inList($conf['workspaces'], 'offline'))
443 && ($this->workspace <= 0 || !GeneralUtility::inList($conf['workspaces'], 'custom'))
444 ) {
445 if ($exitOnError) {
446 throw new \RuntimeException('Workspace Error: This module "' . $conf['name'] . '" is not available under the current workspace', 1294586447);
447 }
448 return FALSE;
449 }
450 // Returns TRUE if conf[access] is not set at all or if the user is admin
451 if (!$conf['access'] || $this->isAdmin()) {
452 return TRUE;
453 }
454 // If $conf['access'] is set but not with 'admin' then we return TRUE, if the module is found in the modList
455 $acs = FALSE;
456 if (!strstr($conf['access'], 'admin') && $conf['name']) {
457 $acs = $this->check('modules', $conf['name']);
458 }
459 if (!$acs && $exitOnError) {
460 throw new \RuntimeException('Access Error: You don\'t have access to this module.', 1294586448);
461 } else {
462 return $acs;
463 }
464 }
465
466 /**
467 * Returns a WHERE-clause for the pages-table where user permissions according to input argument, $perms, is validated.
468 * $perms is the "mask" used to select. Fx. if $perms is 1 then you'll get all pages that a user can actually see!
469 * 2^0 = show (1)
470 * 2^1 = edit (2)
471 * 2^2 = delete (4)
472 * 2^3 = new (8)
473 * If the user is 'admin' " 1=1" is returned (no effect)
474 * If the user is not set at all (->user is not an array), then " 1=0" is returned (will cause no selection results at all)
475 * The 95% use of this function is "->getPagePermsClause(1)" which will
476 * return WHERE clauses for *selecting* pages in backend listings - in other words this will check read permissions.
477 *
478 * @param int $perms Permission mask to use, see function description
479 * @return string Part of where clause. Prefix " AND " to this.
480 */
481 public function getPagePermsClause($perms) {
482 if (is_array($this->user)) {
483 if ($this->isAdmin()) {
484 return ' 1=1';
485 }
486 $perms = (int)$perms;
487 // Make sure it's integer.
488 $str = ' (' . '(pages.perms_everybody & ' . $perms . ' = ' . $perms . ')' . ' OR (pages.perms_userid = '
489 . $this->user['uid'] . ' AND pages.perms_user & ' . $perms . ' = ' . $perms . ')';
490 // User
491 if ($this->groupList) {
492 // Group (if any is set)
493 $str .= ' OR (pages.perms_groupid in (' . $this->groupList . ') AND pages.perms_group & '
494 . $perms . ' = ' . $perms . ')';
495 }
496 $str .= ')';
497 // ****************
498 // getPagePermsClause-HOOK
499 // ****************
500 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getPagePermsClause'])) {
501 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getPagePermsClause'] as $_funcRef) {
502 $_params = array('currentClause' => $str, 'perms' => $perms);
503 $str = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
504 }
505 }
506 return $str;
507 } else {
508 return ' 1=0';
509 }
510 }
511
512 /**
513 * Returns a combined binary representation of the current users permissions for the page-record, $row.
514 * The perms for user, group and everybody is OR'ed together (provided that the page-owner is the user
515 * and for the groups that the user is a member of the group.
516 * If the user is admin, 31 is returned (full permissions for all five flags)
517 *
518 * @param array $row Input page row with all perms_* fields available.
519 * @return int Bitwise representation of the users permissions in relation to input page row, $row
520 */
521 public function calcPerms($row) {
522 // Return 31 for admin users.
523 if ($this->isAdmin()) {
524 return 31;
525 }
526 // Return 0 if page is not within the allowed web mount
527 if (!$this->isInWebMount($row['uid'])) {
528 return 0;
529 }
530 $out = 0;
531 if (
532 isset($row['perms_userid']) && isset($row['perms_user']) && isset($row['perms_groupid'])
533 && isset($row['perms_group']) && isset($row['perms_everybody']) && isset($this->groupList)
534 ) {
535 if ($this->user['uid'] == $row['perms_userid']) {
536 $out |= $row['perms_user'];
537 }
538 if ($this->isMemberOfGroup($row['perms_groupid'])) {
539 $out |= $row['perms_group'];
540 }
541 $out |= $row['perms_everybody'];
542 }
543 // ****************
544 // CALCPERMS hook
545 // ****************
546 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['calcPerms'])) {
547 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['calcPerms'] as $_funcRef) {
548 $_params = array(
549 'row' => $row,
550 'outputPermissions' => $out
551 );
552 $out = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
553 }
554 }
555 return $out;
556 }
557
558 /**
559 * Returns TRUE if the RTE (Rich Text Editor) can be enabled for the user
560 * Strictly this is not permissions being checked but rather a series of settings like
561 * a loaded extension, browser/client type and a configuration option in ->uc[edit_RTE]
562 * The reasons for a FALSE return can be found in $this->RTE_errors
563 *
564 * @return bool
565 */
566 public function isRTE() {
567 // Start:
568 $this->RTE_errors = array();
569 if (!$this->uc['edit_RTE']) {
570 $this->RTE_errors[] = 'RTE is not enabled for user!';
571 }
572 if (!$GLOBALS['TYPO3_CONF_VARS']['BE']['RTEenabled']) {
573 $this->RTE_errors[] = 'RTE is not enabled in $TYPO3_CONF_VARS["BE"]["RTEenabled"]';
574 }
575 // Acquire RTE object:
576 $RTE = BackendUtility::RTEgetObj();
577 if (!is_object($RTE)) {
578 $this->RTE_errors = array_merge($this->RTE_errors, $RTE);
579 }
580 if (!count($this->RTE_errors)) {
581 return TRUE;
582 } else {
583 return FALSE;
584 }
585 }
586
587 /**
588 * Returns TRUE if the $value is found in the list in a $this->groupData[] index pointed to by $type (array key).
589 * Can thus be users to check for modules, exclude-fields, select/modify permissions for tables etc.
590 * If user is admin TRUE is also returned
591 * Please see the document Inside TYPO3 for examples.
592 *
593 * @param string $type The type value; "webmounts", "filemounts", "pagetypes_select", "tables_select", "tables_modify", "non_exclude_fields", "modules
594 * @param string $value String to search for in the groupData-list
595 * @return bool TRUE if permission is granted (that is, the value was found in the groupData list - or the BE_USER is "admin")
596 */
597 public function check($type, $value) {
598 if (isset($this->groupData[$type])) {
599 if ($this->isAdmin() || GeneralUtility::inList($this->groupData[$type], $value)) {
600 return TRUE;
601 }
602 }
603 return FALSE;
604 }
605
606 /**
607 * Checking the authMode of a select field with authMode set
608 *
609 * @param string $table Table name
610 * @param string $field Field name (must be configured in TCA and of type "select" with authMode set!)
611 * @param string $value Value to evaluation (single value, must not contain any of the chars ":,|")
612 * @param string $authMode Auth mode keyword (explicitAllow, explicitDeny, individual)
613 * @return bool Whether access is granted or not
614 */
615 public function checkAuthMode($table, $field, $value, $authMode) {
616 // Admin users can do anything:
617 if ($this->isAdmin()) {
618 return TRUE;
619 }
620 // Allow all blank values:
621 if ((string)$value === '') {
622 return TRUE;
623 }
624 // Certain characters are not allowed in the value
625 if (preg_match('/[:|,]/', $value)) {
626 return FALSE;
627 }
628 // Initialize:
629 $testValue = $table . ':' . $field . ':' . $value;
630 $out = TRUE;
631 // Checking value:
632 switch ((string)$authMode) {
633 case 'explicitAllow':
634 if (!GeneralUtility::inList($this->groupData['explicit_allowdeny'], ($testValue . ':ALLOW'))) {
635 $out = FALSE;
636 }
637 break;
638 case 'explicitDeny':
639 if (GeneralUtility::inList($this->groupData['explicit_allowdeny'], $testValue . ':DENY')) {
640 $out = FALSE;
641 }
642 break;
643 case 'individual':
644 if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
645 $items = $GLOBALS['TCA'][$table]['columns'][$field]['config']['items'];
646 if (is_array($items)) {
647 foreach ($items as $iCfg) {
648 if ((string)$iCfg[1] === (string)$value && $iCfg[4]) {
649 switch ((string)$iCfg[4]) {
650 case 'EXPL_ALLOW':
651 if (!GeneralUtility::inList($this->groupData['explicit_allowdeny'], ($testValue . ':ALLOW'))) {
652 $out = FALSE;
653 }
654 break;
655 case 'EXPL_DENY':
656 if (GeneralUtility::inList($this->groupData['explicit_allowdeny'], $testValue . ':DENY')) {
657 $out = FALSE;
658 }
659 break;
660 }
661 break;
662 }
663 }
664 }
665 }
666 break;
667 }
668 return $out;
669 }
670
671 /**
672 * Checking if a language value (-1, 0 and >0 for sys_language records) is allowed to be edited by the user.
673 *
674 * @param int $langValue Language value to evaluate
675 * @return bool Returns TRUE if the language value is allowed, otherwise FALSE.
676 */
677 public function checkLanguageAccess($langValue) {
678 // The users language list must be non-blank - otherwise all languages are allowed.
679 if (trim($this->groupData['allowed_languages']) !== '') {
680 $langValue = (int)$langValue;
681 // Language must either be explicitly allowed OR the lang Value be "-1" (all languages)
682 if ($langValue != -1 && !$this->check('allowed_languages', $langValue)) {
683 return FALSE;
684 }
685 }
686 return TRUE;
687 }
688
689 /**
690 * Check if user has access to all existing localizations for a certain record
691 *
692 * @param string $table The table
693 * @param array $record The current record
694 * @return bool
695 */
696 public function checkFullLanguagesAccess($table, $record) {
697 $recordLocalizationAccess = $this->checkLanguageAccess(0);
698 if ($recordLocalizationAccess && (BackendUtility::isTableLocalizable($table) || isset($GLOBALS['TCA'][$table]['ctrl']['transForeignTable']))) {
699 if (isset($GLOBALS['TCA'][$table]['ctrl']['transForeignTable'])) {
700 $l10nTable = $GLOBALS['TCA'][$table]['ctrl']['transForeignTable'];
701 $pointerField = $GLOBALS['TCA'][$l10nTable]['ctrl']['transOrigPointerField'];
702 $pointerValue = $record['uid'];
703 } else {
704 $l10nTable = $table;
705 $pointerField = $GLOBALS['TCA'][$l10nTable]['ctrl']['transOrigPointerField'];
706 $pointerValue = $record[$pointerField] > 0 ? $record[$pointerField] : $record['uid'];
707 }
708 $recordLocalizations = BackendUtility::getRecordsByField($l10nTable, $pointerField, $pointerValue, '', '', '', '1');
709 if (is_array($recordLocalizations)) {
710 foreach ($recordLocalizations as $localization) {
711 $recordLocalizationAccess = $recordLocalizationAccess
712 && $this->checkLanguageAccess($localization[$GLOBALS['TCA'][$l10nTable]['ctrl']['languageField']]);
713 if (!$recordLocalizationAccess) {
714 break;
715 }
716 }
717 }
718 }
719 return $recordLocalizationAccess;
720 }
721
722 /**
723 * Checking if a user has editing access to a record from a $GLOBALS['TCA'] table.
724 * The checks does not take page permissions and other "environmental" things into account.
725 * It only deal with record internals; If any values in the record fields disallows it.
726 * For instance languages settings, authMode selector boxes are evaluated (and maybe more in the future).
727 * It will check for workspace dependent access.
728 * The function takes an ID (int) or row (array) as second argument.
729 *
730 * @param string $table Table name
731 * @param mixed $idOrRow If integer, then this is the ID of the record. If Array this just represents fields in the record.
732 * @param bool $newRecord Set, if testing a new (non-existing) record array. Will disable certain checks that doesn't make much sense in that context.
733 * @param bool $deletedRecord Set, if testing a deleted record array.
734 * @param bool $checkFullLanguageAccess Set, whenever access to all translations of the record is required
735 * @return bool TRUE if OK, otherwise FALSE
736 */
737 public function recordEditAccessInternals($table, $idOrRow, $newRecord = FALSE, $deletedRecord = FALSE, $checkFullLanguageAccess = FALSE) {
738 if (!isset($GLOBALS['TCA'][$table])) {
739 return FALSE;
740 }
741 // Always return TRUE for Admin users.
742 if ($this->isAdmin()) {
743 return TRUE;
744 }
745 // Fetching the record if the $idOrRow variable was not an array on input:
746 if (!is_array($idOrRow)) {
747 if ($deletedRecord) {
748 $idOrRow = BackendUtility::getRecord($table, $idOrRow, '*', '', FALSE);
749 } else {
750 $idOrRow = BackendUtility::getRecord($table, $idOrRow);
751 }
752 if (!is_array($idOrRow)) {
753 $this->errorMsg = 'ERROR: Record could not be fetched.';
754 return FALSE;
755 }
756 }
757 // Checking languages:
758 if ($GLOBALS['TCA'][$table]['ctrl']['languageField']) {
759 // Language field must be found in input row - otherwise it does not make sense.
760 if (isset($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
761 if (!$this->checkLanguageAccess($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
762 $this->errorMsg = 'ERROR: Language was not allowed.';
763 return FALSE;
764 } elseif (
765 $checkFullLanguageAccess && $idOrRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']] == 0
766 && !$this->checkFullLanguagesAccess($table, $idOrRow)
767 ) {
768 $this->errorMsg = 'ERROR: Related/affected language was not allowed.';
769 return FALSE;
770 }
771 } else {
772 $this->errorMsg = 'ERROR: The "languageField" field named "'
773 . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '" was not found in testing record!';
774 return FALSE;
775 }
776 } elseif (
777 isset($GLOBALS['TCA'][$table]['ctrl']['transForeignTable']) && $checkFullLanguageAccess &&
778 !$this->checkFullLanguagesAccess($table, $idOrRow)
779 ) {
780 return FALSE;
781 }
782 // Checking authMode fields:
783 if (is_array($GLOBALS['TCA'][$table]['columns'])) {
784 foreach ($GLOBALS['TCA'][$table]['columns'] as $fieldName => $fieldValue) {
785 if (isset($idOrRow[$fieldName])) {
786 if (
787 $fieldValue['config']['type'] === 'select' && $fieldValue['config']['authMode']
788 && $fieldValue['config']['authMode_enforce'] === 'strict'
789 ) {
790 if (!$this->checkAuthMode($table, $fieldName, $idOrRow[$fieldName], $fieldValue['config']['authMode'])) {
791 $this->errorMsg = 'ERROR: authMode "' . $fieldValue['config']['authMode']
792 . '" failed for field "' . $fieldName . '" with value "'
793 . $idOrRow[$fieldName] . '" evaluated';
794 return FALSE;
795 }
796 }
797 }
798 }
799 }
800 // Checking "editlock" feature (doesn't apply to new records)
801 if (!$newRecord && $GLOBALS['TCA'][$table]['ctrl']['editlock']) {
802 if (isset($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['editlock']])) {
803 if ($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['editlock']]) {
804 $this->errorMsg = 'ERROR: Record was locked for editing. Only admin users can change this state.';
805 return FALSE;
806 }
807 } else {
808 $this->errorMsg = 'ERROR: The "editLock" field named "' . $GLOBALS['TCA'][$table]['ctrl']['editlock']
809 . '" was not found in testing record!';
810 return FALSE;
811 }
812 }
813 // Checking record permissions
814 // THIS is where we can include a check for "perms_" fields for other records than pages...
815 // Process any hooks
816 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['recordEditAccessInternals'])) {
817 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['recordEditAccessInternals'] as $funcRef) {
818 $params = array(
819 'table' => $table,
820 'idOrRow' => $idOrRow,
821 'newRecord' => $newRecord
822 );
823 if (!GeneralUtility::callUserFunction($funcRef, $params, $this)) {
824 return FALSE;
825 }
826 }
827 }
828 // Finally, return TRUE if all is well.
829 return TRUE;
830 }
831
832 /**
833 * Checks a type of permission against the compiled permission integer,
834 * $compiledPermissions, and in relation to table, $tableName
835 *
836 * @param int $compiledPermissions Could typically be the "compiled permissions" integer returned by ->calcPerms
837 * @param string $tableName Is the tablename to check: If "pages" table then edit,new,delete and editcontent permissions can be checked. Other tables will be checked for "editcontent" only (and $type will be ignored)
838 * @param string $actionType For $tableName='pages' this can be 'edit' (2), 'new' (8 or 16), 'delete' (4), 'editcontent' (16). For all other tables this is ignored. (16 is used)
839 * @return bool
840 * @access public (used by ClickMenuController)
841 */
842 public function isPSet($compiledPermissions, $tableName, $actionType = '') {
843 if ($this->isAdmin()) {
844 $result = TRUE;
845 } elseif ($tableName == 'pages') {
846 switch ($actionType) {
847 case 'edit':
848 $result = ($compiledPermissions & 2) !== 0;
849 break;
850 case 'new':
851 // Create new page OR page content
852 $result = ($compiledPermissions & 8 + 16) !== 0;
853 break;
854 case 'delete':
855 $result = ($compiledPermissions & 4) !== 0;
856 break;
857 case 'editcontent':
858 $result = ($compiledPermissions & 16) !== 0;
859 break;
860 default:
861 $result = FALSE;
862 }
863 } else {
864 $result = ($compiledPermissions & 16) !== 0;
865 }
866 return $result;
867 }
868
869 /**
870 * Returns TRUE if the BE_USER is allowed to *create* shortcuts in the backend modules
871 *
872 * @return bool
873 */
874 public function mayMakeShortcut() {
875 return $this->getTSConfigVal('options.enableBookmarks')
876 && !$this->getTSConfigVal('options.mayNotCreateEditBookmarks');
877 }
878
879 /**
880 * Checking if editing of an existing record is allowed in current workspace if that is offline.
881 * Rules for editing in offline mode:
882 * - record supports versioning and is an offline version from workspace and has the corrent stage
883 * - or record (any) is in a branch where there is a page which is a version from the workspace
884 * and where the stage is not preventing records
885 *
886 * @param string $table Table of record
887 * @param array $recData Integer (record uid) or array where fields are at least: pid, t3ver_wsid, t3ver_stage (if versioningWS is set)
888 * @return string String error code, telling the failure state. FALSE=All ok
889 */
890 public function workspaceCannotEditRecord($table, $recData) {
891 // Only test offline spaces:
892 if ($this->workspace !== 0) {
893 if (!is_array($recData)) {
894 $recData = BackendUtility::getRecord(
895 $table,
896 $recData,
897 'pid' . ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] ? ',t3ver_wsid,t3ver_stage' : '')
898 );
899 }
900 if (is_array($recData)) {
901 // We are testing a "version" (identified by a pid of -1): it can be edited provided
902 // that workspace matches and versioning is enabled for the table.
903 if ((int)$recData['pid'] === -1) {
904 // No versioning, basic error, inconsistency even! Such records should not have a pid of -1!
905 if (!$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
906 return 'Versioning disabled for table';
907 } elseif ((int)$recData['t3ver_wsid'] !== $this->workspace) {
908 // So does workspace match?
909 return 'Workspace ID of record didn\'t match current workspace';
910 } else {
911 // So is the user allowed to "use" the edit stage within the workspace?
912 return $this->workspaceCheckStageForCurrent(0)
913 ? FALSE
914 : 'User\'s access level did not allow for editing';
915 }
916 } else {
917 // We are testing a "live" record:
918 // For "Live" records, check that PID for table allows editing
919 if ($res = $this->workspaceAllowLiveRecordsInPID($recData['pid'], $table)) {
920 // Live records are OK in this branch, but what about the stage of branch point, if any:
921 // OK
922 return $res > 0
923 ? FALSE
924 : 'Stage for versioning root point and users access level did not allow for editing';
925 } else {
926 // If not offline and not in versionized branch, output error:
927 return 'Online record was not in versionized branch!';
928 }
929 }
930 } else {
931 return 'No record';
932 }
933 } else {
934 // OK because workspace is 0
935 return FALSE;
936 }
937 }
938
939 /**
940 * Evaluates if a user is allowed to edit the offline version
941 *
942 * @param string $table Table of record
943 * @param array $recData Integer (record uid) or array where fields are at least: pid, t3ver_wsid, t3ver_stage (if versioningWS is set)
944 * @return string String error code, telling the failure state. FALSE=All ok
945 * @see workspaceCannotEditRecord()
946 */
947 public function workspaceCannotEditOfflineVersion($table, $recData) {
948 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
949 if (!is_array($recData)) {
950 $recData = BackendUtility::getRecord($table, $recData, 'uid,pid,t3ver_wsid,t3ver_stage');
951 }
952 if (is_array($recData)) {
953 if ((int)$recData['pid'] === -1) {
954 return $this->workspaceCannotEditRecord($table, $recData);
955 } else {
956 return 'Not an offline version';
957 }
958 } else {
959 return 'No record';
960 }
961 } else {
962 return 'Table does not support versioning.';
963 }
964 }
965
966 /**
967 * Check if "live" records from $table may be created or edited in this PID.
968 * If the answer is FALSE it means the only valid way to create or edit records in the PID is by versioning
969 * If the answer is 1 or 2 it means it is OK to create a record, if -1 it means that it is OK in terms
970 * of versioning because the element was within a versionized branch
971 * but NOT ok in terms of the state the root point had!
972 *
973 * @param int $pid PID value to check for. OBSOLETE!
974 * @param string $table Table name
975 * @return mixed Returns FALSE if a live record cannot be created and must be versionized in order to do so. 2 means a) Workspace is "Live" or workspace allows "live edit" of records from non-versionized tables (and the $table is not versionizable). 1 and -1 means the pid is inside a versionized branch where -1 means that the branch-point did NOT allow a new record according to its state.
976 */
977 public function workspaceAllowLiveRecordsInPID($pid, $table) {
978 // Always for Live workspace AND if live-edit is enabled
979 // and tables are completely without versioning it is ok as well.
980 if (
981 $this->workspace === 0
982 || $this->workspaceRec['live_edit'] && !$GLOBALS['TCA'][$table]['ctrl']['versioningWS']
983 || $GLOBALS['TCA'][$table]['ctrl']['versioningWS_alwaysAllowLiveEdit']
984 ) {
985 // OK to create for this table.
986 return 2;
987 } else {
988 // If the answer is FALSE it means the only valid way to create or edit records in the PID is by versioning
989 return FALSE;
990 }
991 }
992
993 /**
994 * Evaluates if a record from $table can be created in $pid
995 *
996 * @param int $pid Page id. This value must be the _ORIG_uid if available: So when you have pages versionized as "page" or "element" you must supply the id of the page version in the workspace!
997 * @param string $table Table name
998 * @return bool TRUE if OK.
999 */
1000 public function workspaceCreateNewRecord($pid, $table) {
1001 if ($res = $this->workspaceAllowLiveRecordsInPID($pid, $table)) {
1002 // If LIVE records cannot be created in the current PID due to workspace restrictions, prepare creation of placeholder-record
1003 if ($res < 0) {
1004 // Stage for versioning root point and users access level did not allow for editing
1005 return FALSE;
1006 }
1007 } elseif (!$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1008 // So, if no live records were allowed, we have to create a new version of this record:
1009 return FALSE;
1010 }
1011 return TRUE;
1012 }
1013
1014 /**
1015 * Evaluates if auto creation of a version of a record is allowed.
1016 *
1017 * @param string $table Table of the record
1018 * @param int $id UID of record
1019 * @param int $recpid PID of record
1020 * @return bool TRUE if ok.
1021 */
1022 public function workspaceAllowAutoCreation($table, $id, $recpid) {
1023 // Auto-creation of version: In offline workspace, test if versioning is
1024 // enabled and look for workspace version of input record.
1025 // If there is no versionized record found we will create one and save to that.
1026 if (
1027 $this->workspace !== 0
1028 && $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $recpid >= 0
1029 && !BackendUtility::getWorkspaceVersionOfRecord($this->workspace, $table, $id, 'uid')
1030 ) {
1031 // There must be no existing version of this record in workspace.
1032 return TRUE;
1033 }
1034 return FALSE;
1035 }
1036
1037 /**
1038 * Checks if an element stage allows access for the user in the current workspace
1039 * In live workspace (= 0) access is always granted for any stage.
1040 * Admins are always allowed.
1041 * An option for custom workspaces allows members to also edit when the stage is "Review"
1042 *
1043 * @param int $stage Stage id from an element: -1,0 = editing, 1 = reviewer, >1 = owner
1044 * @return bool TRUE if user is allowed access
1045 */
1046 public function workspaceCheckStageForCurrent($stage) {
1047 // Always allow for admins
1048 if ($this->isAdmin()) {
1049 return TRUE;
1050 }
1051 if ($this->workspace !== 0 && \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
1052 $stage = (int)$stage;
1053 $stat = $this->checkWorkspaceCurrent();
1054 // Check if custom staging is activated
1055 $workspaceRec = BackendUtility::getRecord('sys_workspace', $stat['uid']);
1056 if ($workspaceRec['custom_stages'] > 0 && $stage !== 0 && $stage !== -10) {
1057 // Get custom stage record
1058 $workspaceStageRec = BackendUtility::getRecord('sys_workspace_stage', $stage);
1059 // Check if the user is responsible for the current stage
1060 if (
1061 $stat['_ACCESS'] === 'owner'
1062 || $stat['_ACCESS'] === 'member'
1063 && GeneralUtility::inList($workspaceStageRec['responsible_persons'], 'be_users_' . $this->user['uid'])
1064 ) {
1065 return TRUE;
1066 }
1067 // Check if the user is in a group which is responsible for the current stage
1068 foreach ($this->userGroupsUID as $groupUid) {
1069 if (
1070 $stat['_ACCESS'] === 'owner'
1071 || $stat['_ACCESS'] === 'member'
1072 && GeneralUtility::inList($workspaceStageRec['responsible_persons'], 'be_groups_' . $groupUid)
1073 ) {
1074 return TRUE;
1075 }
1076 }
1077 } elseif ($stage == -10 || $stage == -20) {
1078 if ($stat['_ACCESS'] === 'owner') {
1079 return TRUE;
1080 } else {
1081 return FALSE;
1082 }
1083 } else {
1084 $memberStageLimit = $this->workspaceRec['review_stage_edit'] ? 1 : 0;
1085 if (
1086 $stat['_ACCESS'] === 'owner'
1087 || $stat['_ACCESS'] === 'reviewer' && $stage <= 1
1088 || $stat['_ACCESS'] === 'member' && $stage <= $memberStageLimit
1089 ) {
1090 return TRUE;
1091 }
1092 }
1093 } else {
1094 // Always OK for live workspace.
1095 return TRUE;
1096 }
1097 return FALSE;
1098 }
1099
1100 /**
1101 * Returns TRUE if the user has access to publish content from the workspace ID given.
1102 * Admin-users are always granted access to do this
1103 * If the workspace ID is 0 (live) all users have access also
1104 * For custom workspaces it depends on whether the user is owner OR like with
1105 * draft workspace if the user has access to Live workspace.
1106 *
1107 * @param int $wsid Workspace UID; 0,1+
1108 * @return bool Returns TRUE if the user has access to publish content from the workspace ID given.
1109 */
1110 public function workspacePublishAccess($wsid) {
1111 if ($this->isAdmin()) {
1112 return TRUE;
1113 }
1114 // If no access to workspace, of course you cannot publish!
1115 $retVal = FALSE;
1116 $wsAccess = $this->checkWorkspace($wsid);
1117 if ($wsAccess) {
1118 switch ($wsAccess['uid']) {
1119 case 0:
1120 // Live workspace
1121 // If access to Live workspace, no problem.
1122 $retVal = TRUE;
1123 break;
1124 default:
1125 // Custom workspace
1126 $retVal = $wsAccess['_ACCESS'] === 'owner' || $this->checkWorkspace(0) && !($wsAccess['publish_access'] & 2);
1127 // Either be an adminuser OR have access to online
1128 // workspace which is OK as well as long as publishing
1129 // access is not limited by workspace option.
1130 }
1131 }
1132 return $retVal;
1133 }
1134
1135 /**
1136 * Workspace swap-mode access?
1137 *
1138 * @return bool Returns TRUE if records can be swapped in the current workspace, otherwise FALSE
1139 */
1140 public function workspaceSwapAccess() {
1141 if ($this->workspace > 0 && (int)$this->workspaceRec['swap_modes'] === 2) {
1142 return FALSE;
1143 } else {
1144 return TRUE;
1145 }
1146 }
1147
1148 /**
1149 * Returns the value/properties of a TS-object as given by $objectString, eg. 'options.dontMountAdminMounts'
1150 * Nice (general!) function for returning a part of a TypoScript array!
1151 *
1152 * @param string $objectString Pointer to an "object" in the TypoScript array, fx. 'options.dontMountAdminMounts'
1153 * @param array|string $config Optional TSconfig array: If array, then this is used and not $this->userTS. If not array, $this->userTS is used.
1154 * @return array An array with two keys, "value" and "properties" where "value" is a string with the value of the object string and "properties" is an array with the properties of the object string.
1155 */
1156 public function getTSConfig($objectString, $config = '') {
1157 if (!is_array($config)) {
1158 // Getting Root-ts if not sent
1159 $config = $this->userTS;
1160 }
1161 $TSConf = array('value' => NULL, 'properties' => NULL);
1162 $parts = GeneralUtility::trimExplode('.', $objectString, TRUE, 2);
1163 $key = $parts[0];
1164 if ($key !== '') {
1165 if (count($parts) > 1 && $parts[1] !== '') {
1166 // Go on, get the next level
1167 if (is_array($config[$key . '.'])) {
1168 $TSConf = $this->getTSConfig($parts[1], $config[$key . '.']);
1169 }
1170 } else {
1171 $TSConf['value'] = $config[$key];
1172 $TSConf['properties'] = $config[$key . '.'];
1173 }
1174 }
1175 return $TSConf;
1176 }
1177
1178 /**
1179 * Returns the "value" of the $objectString from the BE_USERS "User TSconfig" array
1180 *
1181 * @param string $objectString Object string, eg. "somestring.someproperty.somesubproperty
1182 * @return string The value for that object string (object path)
1183 * @see getTSConfig()
1184 */
1185 public function getTSConfigVal($objectString) {
1186 $TSConf = $this->getTSConfig($objectString);
1187 return $TSConf['value'];
1188 }
1189
1190 /**
1191 * Returns the "properties" of the $objectString from the BE_USERS "User TSconfig" array
1192 *
1193 * @param string $objectString Object string, eg. "somestring.someproperty.somesubproperty
1194 * @return array The properties for that object string (object path) - if any
1195 * @see getTSConfig()
1196 */
1197 public function getTSConfigProp($objectString) {
1198 $TSConf = $this->getTSConfig($objectString);
1199 return $TSConf['properties'];
1200 }
1201
1202 /**
1203 * Returns an array with the webmounts.
1204 * If no webmounts, and empty array is returned.
1205 * NOTICE: Deleted pages WILL NOT be filtered out! So if a mounted page has been deleted
1206 * it is STILL coming out as a webmount. This is not checked due to performance.
1207 *
1208 * @return array
1209 */
1210 public function returnWebmounts() {
1211 return (string)$this->groupData['webmounts'] != '' ? explode(',', $this->groupData['webmounts']) : array();
1212 }
1213
1214 /**
1215 * Initializes the given mount points for the current Backend user.
1216 *
1217 * @param array $mountPointUids Page UIDs that should be used as web mountpoints
1218 * @param bool $append If TRUE the given mount point will be appended. Otherwise the current mount points will be replaced.
1219 * @return void
1220 */
1221 public function setWebmounts(array $mountPointUids, $append = FALSE) {
1222 if (empty($mountPointUids)) {
1223 return;
1224 }
1225 if ($append) {
1226 $currentWebMounts = GeneralUtility::intExplode(',', $this->groupData['webmounts']);
1227 $mountPointUids = array_merge($currentWebMounts, $mountPointUids);
1228 }
1229 $this->groupData['webmounts'] = implode(',', array_unique($mountPointUids));
1230 }
1231
1232 /**
1233 * Returns TRUE or FALSE, depending if an alert popup (a javascript confirmation) should be shown
1234 * call like $GLOBALS['BE_USER']->jsConfirmation($BITMASK)
1235 *
1236 * 1 - typeChange
1237 * 2 - copy/move/paste
1238 * 4 - delete
1239 * 8 - frontend editing
1240 * 128 - other (not used yet)
1241 *
1242 * @param int $bitmask Bitmask
1243 * @return bool TRUE if the confirmation should be shown
1244 */
1245 public function jsConfirmation($bitmask) {
1246 $alertPopup = $this->getTSConfig('options.alertPopups');
1247 if (empty($alertPopup['value'])) {
1248 // Default: show all warnings
1249 $alertPopup = 255;
1250 } else {
1251 $alertPopup = (int)$alertPopup['value'];
1252 }
1253 // Show confirmation
1254 return ($alertPopup & $bitmask) == $bitmask;
1255 }
1256
1257 /**
1258 * Initializes a lot of stuff like the access-lists, database-mountpoints and filemountpoints
1259 * This method is called by ->backendCheckLogin() (from extending BackendUserAuthentication)
1260 * if the backend user login has verified OK.
1261 * Generally this is required initialization of a backend user.
1262 *
1263 * @return void
1264 * @access private
1265 * @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
1266 */
1267 public function fetchGroupData() {
1268 if ($this->user['uid']) {
1269 // Get lists for the be_user record and set them as default/primary values.
1270 // Enabled Backend Modules
1271 $this->dataLists['modList'] = $this->user['userMods'];
1272 // Add Allowed Languages
1273 $this->dataLists['allowed_languages'] = $this->user['allowed_languages'];
1274 // Set user value for workspace permissions.
1275 $this->dataLists['workspace_perms'] = $this->user['workspace_perms'];
1276 // User mount points are only added if the user is not an admin as admins do not have visible
1277 // mountpoints fields. Processing them loads mountpoints defined when the user was a non-admin.
1278 if (!$this->isAdmin()) {
1279 // Database mountpoints
1280 $this->dataLists['webmount_list'] = $this->user['db_mountpoints'];
1281 // File mountpoints
1282 $this->dataLists['filemount_list'] = $this->user['file_mountpoints'];
1283 }
1284 // Fileoperation permissions
1285 $this->dataLists['file_permissions'] = $this->user['file_permissions'];
1286 // Setting default User TSconfig:
1287 $this->TSdataArray[] = $this->addTScomment('From $GLOBALS["TYPO3_CONF_VARS"]["BE"]["defaultUserTSconfig"]:')
1288 . $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'];
1289 // Default TSconfig for admin-users
1290 if ($this->isAdmin()) {
1291 $this->TSdataArray[] = $this->addTScomment('"admin" user presets:') . '
1292 admPanel.enable.all = 1
1293 ';
1294 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('sys_note')) {
1295 $this->TSdataArray[] = '
1296 // Setting defaults for sys_note author / email...
1297 TCAdefaults.sys_note.author = ' . $this->user['realName'] . '
1298 TCAdefaults.sys_note.email = ' . $this->user['email'] . '
1299 ';
1300 }
1301 }
1302 // BE_GROUPS:
1303 // Get the groups...
1304 // 240203: Since the group-field never contains any references to groups with a prepended table name
1305 // we think it's safe to just intExplode and re-implode - which should be much faster than the other function call.
1306 $grList = $this->db->cleanIntList($this->user[$this->usergroup_column]);
1307 if ($grList) {
1308 // Fetch groups will add a lot of information to the internal arrays: modules, accesslists, TSconfig etc.
1309 // Refer to fetchGroups() function.
1310 $this->fetchGroups($grList);
1311 }
1312
1313 // Populating the $this->userGroupsUID -array with the groups in the order in which they were LAST included.!!
1314 $this->userGroupsUID = array_reverse(array_unique(array_reverse($this->includeGroupArray)));
1315 // Finally this is the list of group_uid's in the order they are parsed (including subgroups!)
1316 // and without duplicates (duplicates are presented with their last entrance in the list,
1317 // which thus reflects the order of the TypoScript in TSconfig)
1318 $this->groupList = implode(',', $this->userGroupsUID);
1319 $this->setCachedList($this->groupList);
1320
1321 // Add the TSconfig for this specific user:
1322 $this->TSdataArray[] = $this->addTScomment('USER TSconfig field') . $this->user['TSconfig'];
1323 // Check include lines.
1324 $this->TSdataArray = \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines_array($this->TSdataArray);
1325 // Imploding with "[global]" will make sure that non-ended confinements with braces are ignored.
1326 $this->userTS_text = implode(LF . '[GLOBAL]' . LF, $this->TSdataArray);
1327 if (!$this->userTS_dontGetCached) {
1328 // Perform TS-Config parsing with condition matching
1329 $parseObj = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TsConfigParser::class);
1330 $res = $parseObj->parseTSconfig($this->userTS_text, 'userTS');
1331 if ($res) {
1332 $this->userTS = $res['TSconfig'];
1333 $this->userTSUpdated = (bool)$res['cached'];
1334 }
1335 } else {
1336 // Parsing the user TSconfig (or getting from cache)
1337 $hash = md5('userTS:' . $this->userTS_text);
1338 $cachedContent = BackendUtility::getHash($hash);
1339 if (is_array($cachedContent) && !$this->userTS_dontGetCached) {
1340 $this->userTS = $cachedContent;
1341 } else {
1342 $parseObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::class);
1343 $parseObj->parse($this->userTS_text);
1344 $this->userTS = $parseObj->setup;
1345 BackendUtility::storeHash($hash, $this->userTS, 'BE_USER_TSconfig');
1346 // Update UC:
1347 $this->userTSUpdated = TRUE;
1348 }
1349 }
1350 // Processing webmounts
1351 // Admin's always have the root mounted
1352 if ($this->isAdmin() && !$this->getTSConfigVal('options.dontMountAdminMounts')) {
1353 $this->dataLists['webmount_list'] = '0,' . $this->dataLists['webmount_list'];
1354 }
1355 // The lists are cleaned for duplicates
1356 $this->groupData['webmounts'] = GeneralUtility::uniqueList($this->dataLists['webmount_list']);
1357 $this->groupData['pagetypes_select'] = GeneralUtility::uniqueList($this->dataLists['pagetypes_select']);
1358 $this->groupData['tables_select'] = GeneralUtility::uniqueList($this->dataLists['tables_modify'] . ',' . $this->dataLists['tables_select']);
1359 $this->groupData['tables_modify'] = GeneralUtility::uniqueList($this->dataLists['tables_modify']);
1360 $this->groupData['non_exclude_fields'] = GeneralUtility::uniqueList($this->dataLists['non_exclude_fields']);
1361 $this->groupData['explicit_allowdeny'] = GeneralUtility::uniqueList($this->dataLists['explicit_allowdeny']);
1362 $this->groupData['allowed_languages'] = GeneralUtility::uniqueList($this->dataLists['allowed_languages']);
1363 $this->groupData['custom_options'] = GeneralUtility::uniqueList($this->dataLists['custom_options']);
1364 $this->groupData['modules'] = GeneralUtility::uniqueList($this->dataLists['modList']);
1365 $this->groupData['file_permissions'] = GeneralUtility::uniqueList($this->dataLists['file_permissions']);
1366 $this->groupData['workspace_perms'] = $this->dataLists['workspace_perms'];
1367
1368 // Checking read access to webmounts:
1369 if (trim($this->groupData['webmounts']) !== '') {
1370 $webmounts = explode(',', $this->groupData['webmounts']);
1371 // Explode mounts
1372 // Selecting all webmounts with permission clause for reading
1373 $where = 'deleted=0 AND uid IN (' . $this->groupData['webmounts'] . ') AND ' . $this->getPagePermsClause(1);
1374 $MProws = $this->db->exec_SELECTgetRows('uid', 'pages', $where, '', '', '', 'uid');
1375 foreach ($webmounts as $idx => $mountPointUid) {
1376 // If the mount ID is NOT found among selected pages, unset it:
1377 if ($mountPointUid > 0 && !isset($MProws[$mountPointUid])) {
1378 unset($webmounts[$idx]);
1379 }
1380 }
1381 // Implode mounts in the end.
1382 $this->groupData['webmounts'] = implode(',', $webmounts);
1383 }
1384 // Setting up workspace situation (after webmounts are processed!):
1385 $this->workspaceInit();
1386 }
1387 }
1388
1389 /**
1390 * Fetches the group records, subgroups and fills internal arrays.
1391 * Function is called recursively to fetch subgroups
1392 *
1393 * @param string $grList Commalist of be_groups uid numbers
1394 * @param string $idList List of already processed be_groups-uids so the function will not fall into a eternal recursion.
1395 * @return void
1396 * @access private
1397 */
1398 public function fetchGroups($grList, $idList = '') {
1399 // Fetching records of the groups in $grList (which are not blocked by lockedToDomain either):
1400 $lockToDomain_SQL = ' AND (lockToDomain=\'\' OR lockToDomain IS NULL OR lockToDomain=\'' . GeneralUtility::getIndpEnv('HTTP_HOST') . '\')';
1401 $whereSQL = 'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . $grList . ')' . $lockToDomain_SQL;
1402 // Hook for manipulation of the WHERE sql sentence which controls which BE-groups are included
1403 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroupQuery'])) {
1404 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroupQuery'] as $classRef) {
1405 $hookObj = GeneralUtility::getUserObj($classRef);
1406 if (method_exists($hookObj, 'fetchGroupQuery_processQuery')) {
1407 $whereSQL = $hookObj->fetchGroupQuery_processQuery($this, $grList, $idList, $whereSQL);
1408 }
1409 }
1410 }
1411 $res = $this->db->exec_SELECTquery('*', $this->usergroup_table, $whereSQL);
1412 // The userGroups array is filled
1413 while ($row = $this->db->sql_fetch_assoc($res)) {
1414 $this->userGroups[$row['uid']] = $row;
1415 }
1416 $this->db->sql_free_result($res);
1417 // Traversing records in the correct order
1418 $include_staticArr = GeneralUtility::intExplode(',', $grList);
1419 // Traversing list
1420 foreach ($include_staticArr as $uid) {
1421 // Get row:
1422 $row = $this->userGroups[$uid];
1423 // Must be an array and $uid should not be in the idList, because then it is somewhere previously in the grouplist
1424 if (is_array($row) && !GeneralUtility::inList($idList, $uid)) {
1425 // Include sub groups
1426 if (trim($row['subgroup'])) {
1427 // Make integer list
1428 $theList = implode(',', GeneralUtility::intExplode(',', $row['subgroup']));
1429 // Call recursively, pass along list of already processed groups so they are not recursed again.
1430 $this->fetchGroups($theList, $idList . ',' . $uid);
1431 }
1432 // Add the group uid, current list, TSconfig to the internal arrays.
1433 $this->includeGroupArray[] = $uid;
1434 $this->includeHierarchy[] = $idList;
1435 $this->TSdataArray[] = $this->addTScomment('Group "' . $row['title'] . '" [' . $row['uid'] . '] TSconfig field:') . $row['TSconfig'];
1436 // Mount group database-mounts
1437 if (($this->user['options'] & 1) == 1) {
1438 $this->dataLists['webmount_list'] .= ',' . $row['db_mountpoints'];
1439 }
1440 // Mount group file-mounts
1441 if (($this->user['options'] & 2) == 2) {
1442 $this->dataLists['filemount_list'] .= ',' . $row['file_mountpoints'];
1443 }
1444 // The lists are made: groupMods, tables_select, tables_modify, pagetypes_select, non_exclude_fields, explicit_allowdeny, allowed_languages, custom_options
1445 $this->dataLists['modList'] .= ',' . $row['groupMods'];
1446 $this->dataLists['tables_select'] .= ',' . $row['tables_select'];
1447 $this->dataLists['tables_modify'] .= ',' . $row['tables_modify'];
1448 $this->dataLists['pagetypes_select'] .= ',' . $row['pagetypes_select'];
1449 $this->dataLists['non_exclude_fields'] .= ',' . $row['non_exclude_fields'];
1450 $this->dataLists['explicit_allowdeny'] .= ',' . $row['explicit_allowdeny'];
1451 $this->dataLists['allowed_languages'] .= ',' . $row['allowed_languages'];
1452 $this->dataLists['custom_options'] .= ',' . $row['custom_options'];
1453 $this->dataLists['file_permissions'] .= ',' . $row['file_permissions'];
1454 // Setting workspace permissions:
1455 $this->dataLists['workspace_perms'] |= $row['workspace_perms'];
1456 // If this function is processing the users OWN group-list (not subgroups) AND
1457 // if the ->firstMainGroup is not set, then the ->firstMainGroup will be set.
1458 if ($idList === '' && !$this->firstMainGroup) {
1459 $this->firstMainGroup = $uid;
1460 }
1461 }
1462 }
1463 // HOOK: fetchGroups_postProcessing
1464 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroups_postProcessing'])) {
1465 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroups_postProcessing'] as $_funcRef) {
1466 $_params = array();
1467 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1468 }
1469 }
1470 }
1471
1472 /**
1473 * Updates the field be_users.usergroup_cached_list if the groupList of the user
1474 * has changed/is different from the current list.
1475 * The field "usergroup_cached_list" contains the list of groups which the user is a member of.
1476 * After authentication (where these functions are called...) one can depend on this list being
1477 * a representation of the exact groups/subgroups which the BE_USER has membership with.
1478 *
1479 * @param string $cList The newly compiled group-list which must be compared with the current list in the user record and possibly stored if a difference is detected.
1480 * @return void
1481 * @access private
1482 */
1483 public function setCachedList($cList) {
1484 if ((string)$cList != (string)$this->user['usergroup_cached_list']) {
1485 $this->db->exec_UPDATEquery('be_users', 'uid=' . (int)$this->user['uid'], array('usergroup_cached_list' => $cList));
1486 }
1487 }
1488
1489 /**
1490 * Sets up all file storages for a user.
1491 * Needs to be called AFTER the groups have been loaded.
1492 *
1493 * @return void
1494 */
1495 protected function initializeFileStorages() {
1496 $this->fileStorages = array();
1497 /** @var $storageRepository \TYPO3\CMS\Core\Resource\StorageRepository */
1498 $storageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class);
1499 // Admin users have all file storages visible, without any filters
1500 if ($this->isAdmin()) {
1501 $storageObjects = $storageRepository->findAll();
1502 foreach ($storageObjects as $storageObject) {
1503 $this->fileStorages[$storageObject->getUid()] = $storageObject;
1504 }
1505 } else {
1506 // Regular users only have storages that are defined in their filemounts
1507 // Permissions and file mounts for the storage are added in StoragePermissionAspect
1508 foreach ($this->getFileMountRecords() as $row) {
1509 if (!array_key_exists((int)$row['base'], $this->fileStorages)) {
1510 $storageObject = $storageRepository->findByUid($row['base']);
1511 $this->fileStorages[$storageObject->getUid()] = $storageObject;
1512 }
1513 }
1514 }
1515
1516 // This has to be called always in order to set certain filters
1517 $this->evaluateUserSpecificFileFilterSettings();
1518 }
1519
1520 /**
1521 * Returns an array of category mount points. The category permissions from BE Groups
1522 * are also taken into consideration and are merged into User permissions.
1523 *
1524 * @return array
1525 */
1526 public function getCategoryMountPoints() {
1527 $categoryMountPoints = '';
1528
1529 // Category mounts of the groups
1530 if (is_array($this->userGroups)) {
1531 foreach ($this->userGroups as $group) {
1532 if ($group['category_perms']) {
1533 $categoryMountPoints .= ',' . $group['category_perms'];
1534 }
1535 }
1536 }
1537
1538 // Category mounts of the user record
1539 if ($this->user['category_perms']) {
1540 $categoryMountPoints .= ',' . $this->user['category_perms'];
1541 }
1542
1543 // Make the ids unique
1544 $categoryMountPoints = GeneralUtility::trimExplode(',', $categoryMountPoints);
1545 $categoryMountPoints = array_filter($categoryMountPoints); // remove empty value
1546 $categoryMountPoints = array_unique($categoryMountPoints); // remove unique value
1547
1548 return $categoryMountPoints;
1549 }
1550
1551 /**
1552 * Returns an array of file mount records, taking workspaces and user home and group home directories into account
1553 * Needs to be called AFTER the groups have been loaded.
1554 *
1555 * @return array
1556 * @internal
1557 */
1558 public function getFileMountRecords() {
1559 static $fileMountRecordCache = array();
1560
1561 if (!empty($fileMountRecordCache)) {
1562 return $fileMountRecordCache;
1563 }
1564
1565 // Processing file mounts (both from the user and the groups)
1566 $fileMounts = array_unique(GeneralUtility::intExplode(',', $this->dataLists['filemount_list'], TRUE));
1567
1568 // Limit file mounts if set in workspace record
1569 if ($this->workspace > 0 && !empty($this->workspaceRec['file_mountpoints'])) {
1570 $workspaceFileMounts = GeneralUtility::intExplode(',', $this->workspaceRec['file_mountpoints'], TRUE);
1571 $fileMounts = array_intersect($fileMounts, $workspaceFileMounts);
1572 }
1573
1574 if (!empty($fileMounts)) {
1575 $orderBy = isset($GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'])
1576 ? $this->db->stripOrderBy($GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'])
1577 : 'sorting';
1578 $fileMountRecords = $this->db->exec_SELECTgetRows(
1579 '*',
1580 'sys_filemounts',
1581 'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . implode(',', $fileMounts) . ')',
1582 '',
1583 $orderBy
1584 );
1585 foreach ($fileMountRecords as $fileMount) {
1586 $fileMountRecordCache[$fileMount['base'] . $fileMount['path']] = $fileMount;
1587 }
1588 }
1589
1590 // Read-only file mounts
1591 $readOnlyMountPoints = trim($GLOBALS['BE_USER']->getTSConfigVal('options.folderTree.altElementBrowserMountPoints'));
1592 if ($readOnlyMountPoints) {
1593 // We cannot use the API here but need to fetch the default storage record directly
1594 // to not instantiate it (which directly applies mount points) before all mount points are resolved!
1595 $whereClause = 'is_default=1 ' . BackendUtility::BEenableFields('sys_file_storage') . BackendUtility::deleteClause('sys_file_storage');
1596 $defaultStorageRow = $this->db->exec_SELECTgetSingleRow('uid', 'sys_file_storage', $whereClause);
1597 $readOnlyMountPointArray = GeneralUtility::trimExplode(',', $readOnlyMountPoints);
1598 foreach ($readOnlyMountPointArray as $readOnlyMountPoint) {
1599 $readOnlyMountPointConfiguration = GeneralUtility::trimExplode(':', $readOnlyMountPoint);
1600 if (count($readOnlyMountPointConfiguration) === 2) {
1601 // A storage is passed in the configuration
1602 $storageUid = (int)$readOnlyMountPointConfiguration[0];
1603 $path = $readOnlyMountPointConfiguration[1];
1604 } else {
1605 if (empty($defaultStorageRow)) {
1606 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);
1607 }
1608 // Backwards compatibility: If no storage is passed, we use the default storage
1609 $storageUid = $defaultStorageRow['uid'];
1610 $path = $readOnlyMountPointConfiguration[0];
1611 }
1612 $fileMountRecordCache[$storageUid . $path] = array(
1613 'base' => $storageUid,
1614 'title' => $path,
1615 'path' => $path,
1616 'read_only' => TRUE
1617 );
1618 }
1619 }
1620
1621
1622 // Personal or Group filemounts are not accessible if file mount list is set in workspace record
1623 if ($this->workspace <= 0 || empty($this->workspaceRec['file_mountpoints'])) {
1624 // If userHomePath is set, we attempt to mount it
1625 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath']) {
1626 list($userHomeStorageUid, $userHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath'], 2);
1627 $userHomeStorageUid = (int)$userHomeStorageUid;
1628 $userHomeFilter = '/' . ltrim($userHomeFilter, '/');
1629 if ($userHomeStorageUid > 0) {
1630 // Try and mount with [uid]_[username]
1631 $path = $userHomeFilter . $this->user['uid'] . '_' . $this->user['username'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir'];
1632 $fileMountRecordCache[$userHomeStorageUid . $path] = array(
1633 'base' => $userHomeStorageUid,
1634 'title' => $this->user['username'],
1635 'path' => $path,
1636 'read_only' => FALSE,
1637 'user_mount' => TRUE
1638 );
1639 // Try and mount with only [uid]
1640 $path = $userHomeFilter . $this->user['uid'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir'];
1641 $fileMountRecordCache[$userHomeStorageUid . $path] = array(
1642 'base' => $userHomeStorageUid,
1643 'title' => $this->user['username'],
1644 'path' => $path,
1645 'read_only' => FALSE,
1646 'user_mount' => TRUE
1647 );
1648 }
1649 }
1650
1651 // Mount group home-dirs
1652 if ((is_array($this->user) && $this->user['options'] & 2) == 2 && $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'] != '') {
1653 // If groupHomePath is set, we attempt to mount it
1654 list($groupHomeStorageUid, $groupHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'], 2);
1655 $groupHomeStorageUid = (int)$groupHomeStorageUid;
1656 $groupHomeFilter = '/' . ltrim($groupHomeFilter, '/');
1657 if ($groupHomeStorageUid > 0) {
1658 foreach ($this->userGroups as $groupData) {
1659 $path = $groupHomeFilter . $groupData['uid'];
1660 $fileMountRecordCache[$groupHomeStorageUid . $path] = array(
1661 'base' => $groupHomeStorageUid,
1662 'title' => $groupData['title'],
1663 'path' => $path,
1664 'read_only' => FALSE,
1665 'user_mount' => TRUE
1666 );
1667 }
1668 }
1669 }
1670 }
1671
1672 return $fileMountRecordCache;
1673 }
1674
1675 /**
1676 * Returns an array with the filemounts for the user.
1677 * Each filemount is represented with an array of a "name", "path" and "type".
1678 * If no filemounts an empty array is returned.
1679 *
1680 * @api
1681 * @return \TYPO3\CMS\Core\Resource\ResourceStorage[]
1682 */
1683 public function getFileStorages() {
1684 // Initializing file mounts after the groups are fetched
1685 if ($this->fileStorages === NULL) {
1686 $this->initializeFileStorages();
1687 }
1688 return $this->fileStorages;
1689 }
1690
1691 /**
1692 * Adds filters based on what the user has set
1693 * this should be done in this place, and called whenever needed,
1694 * but only when needed
1695 *
1696 * @return void
1697 */
1698 public function evaluateUserSpecificFileFilterSettings() {
1699 // Add the option for also displaying the non-hidden files
1700 if ($this->uc['showHiddenFilesAndFolders']) {
1701 \TYPO3\CMS\Core\Resource\Filter\FileNameFilter::setShowHiddenFilesAndFolders(TRUE);
1702 }
1703 }
1704
1705 /**
1706 * Returns the information about file permissions.
1707 * Previously, this was stored in the DB field fileoper_perms now it is file_permissions.
1708 * Besides it can be handled via userTSconfig
1709 *
1710 * permissions.file.default {
1711 * addFile = 1
1712 * readFile = 1
1713 * writeFile = 1
1714 * copyFile = 1
1715 * moveFile = 1
1716 * renameFile = 1
1717 * unzipFile = 1
1718 * deleteFile = 1
1719 *
1720 * addFolder = 1
1721 * readFolder = 1
1722 * writeFolder = 1
1723 * copyFolder = 1
1724 * moveFolder = 1
1725 * renameFolder = 1
1726 * deleteFolder = 1
1727 * recursivedeleteFolder = 1
1728 * }
1729 *
1730 * # overwrite settings for a specific storageObject
1731 * permissions.file.storage.StorageUid {
1732 * readFile = 1
1733 * recursivedeleteFolder = 0
1734 * }
1735 *
1736 * Please note that these permissions only apply, if the storage has the
1737 * capabilities (browseable, writable), and if the driver allows for writing etc
1738 *
1739 * @api
1740 * @return array
1741 */
1742 public function getFilePermissions() {
1743 if (!isset($this->filePermissions)) {
1744 $filePermissions = array(
1745 // File permissions
1746 'addFile' => FALSE,
1747 'readFile' => FALSE,
1748 'writeFile' => FALSE,
1749 'copyFile' => FALSE,
1750 'moveFile' => FALSE,
1751 'renameFile' => FALSE,
1752 'unzipFile' => FALSE,
1753 'deleteFile' => FALSE,
1754 // Folder permissions
1755 'addFolder' => FALSE,
1756 'readFolder' => FALSE,
1757 'writeFolder' => FALSE,
1758 'copyFolder' => FALSE,
1759 'moveFolder' => FALSE,
1760 'renameFolder' => FALSE,
1761 'deleteFolder' => FALSE,
1762 'recursivedeleteFolder' => FALSE
1763 );
1764 if ($this->isAdmin()) {
1765 $filePermissions = array_map('is_bool', $filePermissions);
1766 } else {
1767 $userGroupRecordPermissions = GeneralUtility::trimExplode(',', $this->groupData['file_permissions'], TRUE);
1768 array_walk(
1769 $userGroupRecordPermissions,
1770 function($permission) use (&$filePermissions) {
1771 $filePermissions[$permission] = TRUE;
1772 }
1773 );
1774
1775 // Finally overlay any userTSconfig
1776 $permissionsTsConfig = $this->getTSConfigProp('permissions.file.default');
1777 if (!empty($permissionsTsConfig)) {
1778 array_walk(
1779 $permissionsTsConfig,
1780 function($value, $permission) use (&$filePermissions) {
1781 $filePermissions[$permission] = (bool)$value;
1782 }
1783 );
1784 }
1785 }
1786 $this->filePermissions = $filePermissions;
1787 }
1788 return $this->filePermissions;
1789 }
1790
1791 /**
1792 * Gets the file permissions for a storage
1793 * by merging any storage-specific permissions for a
1794 * storage with the default settings.
1795 * Admin users will always get the default settings.
1796 *
1797 * @api
1798 * @param \TYPO3\CMS\Core\Resource\ResourceStorage $storageObject
1799 * @return array
1800 */
1801 public function getFilePermissionsForStorage(\TYPO3\CMS\Core\Resource\ResourceStorage $storageObject) {
1802 $finalUserPermissions = $this->getFilePermissions();
1803 if (!$this->isAdmin()) {
1804 $storageFilePermissions = $this->getTSConfigProp('permissions.file.storage.' . $storageObject->getUid());
1805 if (!empty($storageFilePermissions)) {
1806 array_walk(
1807 $storageFilePermissions,
1808 function($value, $permission) use (&$finalUserPermissions) {
1809 $finalUserPermissions[$permission] = (bool)$value;
1810 }
1811 );
1812 }
1813 }
1814 return $finalUserPermissions;
1815 }
1816
1817 /**
1818 * Returns a \TYPO3\CMS\Core\Resource\Folder object that is used for uploading
1819 * files by default.
1820 * This is used for RTE and its magic images, as well as uploads
1821 * in the TCEforms fields, unless otherwise configured (will be added in the future)
1822 *
1823 * the default upload folder for a user is the defaultFolder on the first
1824 * filestorage/filemount that the user can access
1825 * however, you can set the users' upload folder like this:
1826 *
1827 * options.defaultUploadFolder = 3:myfolder/yourfolder/
1828 *
1829 * @return \TYPO3\CMS\Core\Resource\Folder|boolean The default upload folder for this user
1830 */
1831 public function getDefaultUploadFolder() {
1832 $uploadFolder = $this->getTSConfigVal('options.defaultUploadFolder');
1833 if ($uploadFolder) {
1834 $uploadFolder = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($uploadFolder);
1835 } else {
1836 foreach($this->getFileStorages() as $storage) {
1837 if ($storage->isDefault()) {
1838 try {
1839 $uploadFolder = $storage->getDefaultFolder();
1840 break;
1841 } catch (\TYPO3\CMS\Core\Resource\Exception $folderAccessException) {
1842 // If the folder is not accessible (no permissions / does not exist) we skip this one.
1843 }
1844 break;
1845 }
1846 }
1847 if (!$uploadFolder instanceof \TYPO3\CMS\Core\Resource\Folder) {
1848 /** @var ResourceStorage $storage */
1849 foreach ($this->getFileStorages() as $storage) {
1850 try {
1851 $uploadFolder = $storage->getDefaultFolder();
1852 break;
1853 } catch (\TYPO3\CMS\Core\Resource\Exception $folderAccessException) {
1854 // If the folder is not accessible (no permissions / does not exist) try the next one.
1855 }
1856 }
1857 }
1858 }
1859 if ($uploadFolder instanceof \TYPO3\CMS\Core\Resource\Folder) {
1860 return $uploadFolder;
1861 } else {
1862 return FALSE;
1863 }
1864 }
1865
1866 /**
1867 * Returns a \TYPO3\CMS\Core\Resource\Folder object that could be used for uploading
1868 * temporary files in user context. The folder _temp_ below the default upload folder
1869 * of the user is used.
1870 *
1871 * @return NULL|\TYPO3\CMS\Core\Resource\Folder
1872 * @see \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::getDefaultUploadFolder();
1873 */
1874 public function getDefaultUploadTemporaryFolder() {
1875 $defaultTemporaryFolder = NULL;
1876 $defaultFolder = $this->getDefaultUploadFolder();
1877
1878 if ($defaultFolder !== FALSE) {
1879 $tempFolderName = '_temp_';
1880 $createFolder = !$defaultFolder->hasFolder($tempFolderName);
1881 if ($createFolder === TRUE) {
1882 try {
1883 $defaultTemporaryFolder = $defaultFolder->createFolder($tempFolderName);
1884 } catch (\TYPO3\CMS\Core\Resource\Exception $folderAccessException) {}
1885 } else {
1886 $defaultTemporaryFolder = $defaultFolder->getSubfolder($tempFolderName);
1887 }
1888 }
1889
1890 return $defaultTemporaryFolder;
1891 }
1892
1893 /**
1894 * Creates a TypoScript comment with the string text inside.
1895 *
1896 * @param string $str The text to wrap in comment prefixes and delimiters.
1897 * @return string TypoScript comment with the string text inside.
1898 */
1899 public function addTScomment($str) {
1900 $delimiter = '# ***********************************************';
1901 $out = $delimiter . LF;
1902 $lines = GeneralUtility::trimExplode(LF, $str);
1903 foreach ($lines as $v) {
1904 $out .= '# ' . $v . LF;
1905 }
1906 $out .= $delimiter . LF;
1907 return $out;
1908 }
1909
1910 /**
1911 * Initializing workspace.
1912 * Called from within this function, see fetchGroupData()
1913 *
1914 * @return void
1915 * @see fetchGroupData()
1916 */
1917 public function workspaceInit() {
1918 // Initializing workspace by evaluating and setting the workspace, possibly updating it in the user record!
1919 $this->setWorkspace($this->user['workspace_id']);
1920 // Limiting the DB mountpoints if there any selected in the workspace record
1921 $this->initializeDbMountpointsInWorkspace();
1922 if ($allowed_languages = $this->getTSConfigVal('options.workspaces.allowed_languages.' . $this->workspace)) {
1923 $this->groupData['allowed_languages'] = $allowed_languages;
1924 $this->groupData['allowed_languages'] = GeneralUtility::uniqueList($this->groupData['allowed_languages']);
1925 }
1926 }
1927
1928 /**
1929 * Limiting the DB mountpoints if there any selected in the workspace record
1930 *
1931 * @return void
1932 */
1933 protected function initializeDbMountpointsInWorkspace() {
1934 $dbMountpoints = trim($this->workspaceRec['db_mountpoints']);
1935 if ($this->workspace > 0 && $dbMountpoints != '') {
1936 $filteredDbMountpoints = array();
1937 // Notice: We cannot call $this->getPagePermsClause(1);
1938 // as usual because the group-list is not available at this point.
1939 // But bypassing is fine because all we want here is check if the
1940 // workspace mounts are inside the current webmounts rootline.
1941 // The actual permission checking on page level is done elsewhere
1942 // as usual anyway before the page tree is rendered.
1943 $readPerms = '1=1';
1944 // Traverse mount points of the
1945 $dbMountpoints = GeneralUtility::intExplode(',', $dbMountpoints);
1946 foreach ($dbMountpoints as $mpId) {
1947 if ($this->isInWebMount($mpId, $readPerms)) {
1948 $filteredDbMountpoints[] = $mpId;
1949 }
1950 }
1951 // Re-insert webmounts:
1952 $filteredDbMountpoints = array_unique($filteredDbMountpoints);
1953 $this->groupData['webmounts'] = implode(',', $filteredDbMountpoints);
1954 }
1955 }
1956
1957 /**
1958 * Checking if a workspace is allowed for backend user
1959 *
1960 * @param mixed $wsRec If integer, workspace record is looked up, if array it is seen as a Workspace record with at least uid, title, members and adminusers columns. Can be faked for workspaces uid 0 and -1 (online and offline)
1961 * @param string $fields List of fields to select. Default fields are: uid,title,adminusers,members,reviewers,publish_access,stagechg_notification
1962 * @return array Output will also show how access was granted. Admin users will have a true output regardless of input.
1963 */
1964 public function checkWorkspace($wsRec, $fields = 'uid,title,adminusers,members,reviewers,publish_access,stagechg_notification') {
1965 $retVal = FALSE;
1966 // If not array, look up workspace record:
1967 if (!is_array($wsRec)) {
1968 switch ((string)$wsRec) {
1969 case '0':
1970 $wsRec = array('uid' => $wsRec);
1971 break;
1972 default:
1973 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
1974 $wsRec = $this->db->exec_SELECTgetSingleRow($fields,
1975 'sys_workspace',
1976 'pid=0 AND uid=' . (int)$wsRec . BackendUtility::deleteClause('sys_workspace'),
1977 '',
1978 'title'
1979 );
1980 }
1981 }
1982 }
1983 // If wsRec is set to an array, evaluate it:
1984 if (is_array($wsRec)) {
1985 if ($this->isAdmin()) {
1986 return array_merge($wsRec, array('_ACCESS' => 'admin'));
1987 } else {
1988 switch ((string)$wsRec['uid']) {
1989 case '0':
1990 $retVal = $this->groupData['workspace_perms'] & 1
1991 ? array_merge($wsRec, array('_ACCESS' => 'online'))
1992 : FALSE;
1993 break;
1994 default:
1995 // Checking if the guy is admin:
1996 if (GeneralUtility::inList($wsRec['adminusers'], 'be_users_' . $this->user['uid'])) {
1997 return array_merge($wsRec, array('_ACCESS' => 'owner'));
1998 }
1999 // Checking if he is owner through a user group of his:
2000 foreach ($this->userGroupsUID as $groupUid) {
2001 if (GeneralUtility::inList($wsRec['adminusers'], 'be_groups_' . $groupUid)) {
2002 return array_merge($wsRec, array('_ACCESS' => 'owner'));
2003 }
2004 }
2005 // Checking if he is reviewer user:
2006 if (GeneralUtility::inList($wsRec['reviewers'], 'be_users_' . $this->user['uid'])) {
2007 return array_merge($wsRec, array('_ACCESS' => 'reviewer'));
2008 }
2009 // Checking if he is reviewer through a user group of his:
2010 foreach ($this->userGroupsUID as $groupUid) {
2011 if (GeneralUtility::inList($wsRec['reviewers'], 'be_groups_' . $groupUid)) {
2012 return array_merge($wsRec, array('_ACCESS' => 'reviewer'));
2013 }
2014 }
2015 // Checking if he is member as user:
2016 if (GeneralUtility::inList($wsRec['members'], 'be_users_' . $this->user['uid'])) {
2017 return array_merge($wsRec, array('_ACCESS' => 'member'));
2018 }
2019 // Checking if he is member through a user group of his:
2020 foreach ($this->userGroupsUID as $groupUid) {
2021 if (GeneralUtility::inList($wsRec['members'], 'be_groups_' . $groupUid)) {
2022 return array_merge($wsRec, array('_ACCESS' => 'member'));
2023 }
2024 }
2025 }
2026 }
2027 }
2028 return $retVal;
2029 }
2030
2031 /**
2032 * Uses checkWorkspace() to check if current workspace is available for user.
2033 * This function caches the result and so can be called many times with no performance loss.
2034 *
2035 * @return array See checkWorkspace()
2036 * @see checkWorkspace()
2037 */
2038 public function checkWorkspaceCurrent() {
2039 if (!isset($this->checkWorkspaceCurrent_cache)) {
2040 $this->checkWorkspaceCurrent_cache = $this->checkWorkspace($this->workspace);
2041 }
2042 return $this->checkWorkspaceCurrent_cache;
2043 }
2044
2045 /**
2046 * Setting workspace ID
2047 *
2048 * @param int $workspaceId ID of workspace to set for backend user. If not valid the default workspace for BE user is found and set.
2049 * @return void
2050 */
2051 public function setWorkspace($workspaceId) {
2052 // Check workspace validity and if not found, revert to default workspace.
2053 if (!$this->setTemporaryWorkspace($workspaceId)) {
2054 $this->setDefaultWorkspace();
2055 }
2056 // Unset access cache:
2057 $this->checkWorkspaceCurrent_cache = NULL;
2058 // If ID is different from the stored one, change it:
2059 if ((int)$this->workspace !== (int)$this->user['workspace_id']) {
2060 $this->user['workspace_id'] = $this->workspace;
2061 $this->db->exec_UPDATEquery('be_users', 'uid=' . (int)$this->user['uid'], array('workspace_id' => $this->user['workspace_id']));
2062 $this->simplelog('User changed workspace to "' . $this->workspace . '"');
2063 }
2064 }
2065
2066 /**
2067 * Sets a temporary workspace in the context of the current backend user.
2068 *
2069 * @param int $workspaceId
2070 * @return bool
2071 */
2072 public function setTemporaryWorkspace($workspaceId) {
2073 $result = FALSE;
2074 $workspaceRecord = $this->checkWorkspace($workspaceId, '*');
2075
2076 if ($workspaceRecord) {
2077 $this->workspaceRec = $workspaceRecord;
2078 $this->workspace = (int)$workspaceId;
2079 $result = TRUE;
2080 }
2081
2082 return $result;
2083 }
2084
2085 /**
2086 * Sets the default workspace in the context of the current backend user.
2087 *
2088 * @return void
2089 */
2090 public function setDefaultWorkspace() {
2091 $this->workspace = (int)$this->getDefaultWorkspace();
2092 $this->workspaceRec = $this->checkWorkspace($this->workspace, '*');
2093 }
2094
2095 /**
2096 * Setting workspace preview state for user:
2097 *
2098 * @param bool $previewState State of user preview.
2099 * @return void
2100 */
2101 public function setWorkspacePreview($previewState) {
2102 $this->user['workspace_preview'] = $previewState;
2103 $this->db->exec_UPDATEquery('be_users', 'uid=' . (int)$this->user['uid'], array('workspace_preview' => $this->user['workspace_preview']));
2104 }
2105
2106 /**
2107 * Return default workspace ID for user,
2108 * If EXT:workspaces is not installed the user will be pushed the the
2109 * Live workspace
2110 *
2111 * @return int Default workspace id. If no workspace is available it will be "-99
2112 */
2113 public function getDefaultWorkspace() {
2114 $defaultWorkspace = -99;
2115 if (!\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces') || $this->checkWorkspace(0)) {
2116 // Check online
2117 $defaultWorkspace = 0;
2118 } elseif ($this->checkWorkspace(-1)) {
2119 // Check offline
2120 $defaultWorkspace = -1;
2121 } elseif (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
2122 // Traverse custom workspaces:
2123 $workspaces = $this->db->exec_SELECTgetRows('uid,title,adminusers,members,reviewers', 'sys_workspace', 'pid=0' . BackendUtility::deleteClause('sys_workspace'), '', 'title');
2124 foreach ($workspaces as $rec) {
2125 if ($this->checkWorkspace($rec)) {
2126 $defaultWorkspace = $rec['uid'];
2127 break;
2128 }
2129 }
2130 }
2131 return $defaultWorkspace;
2132 }
2133
2134 /**
2135 * Writes an entry in the logfile/table
2136 * Documentation in "TYPO3 Core API"
2137 *
2138 * @param int $type Denotes which module that has submitted the entry. See "TYPO3 Core API". Use "4" for extensions.
2139 * @param int $action Denotes which specific operation that wrote the entry. Use "0" when no sub-categorizing applies
2140 * @param int $error Flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin)
2141 * @param int $details_nr The message number. Specific for each $type and $action. This will make it possible to translate errormessages to other languages
2142 * @param string $details Default text that follows the message (in english!). Possibly translated by identification through type/action/details_nr
2143 * @param array $data Data that follows the log. Might be used to carry special information. If an array the first 5 entries (0-4) will be sprintf'ed with the details-text
2144 * @param string $tablename Table name. Special field used by tce_main.php.
2145 * @param int|string $recuid Record UID. Special field used by tce_main.php.
2146 * @param int|string $recpid Record PID. Special field used by tce_main.php. OBSOLETE
2147 * @param int $event_pid The page_uid (pid) where the event occurred. Used to select log-content for specific pages.
2148 * @param string $NEWid Special field used by tce_main.php. NEWid string of newly created records.
2149 * @param int $userId Alternative Backend User ID (used for logging login actions where this is not yet known).
2150 * @return int Log entry ID.
2151 */
2152 public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename = '', $recuid = '', $recpid = '', $event_pid = -1, $NEWid = '', $userId = 0) {
2153 if (!$userId && isset($this->user['uid'])) {
2154 $userId = $this->user['uid'];
2155 }
2156
2157 $fields_values = array(
2158 'userid' => (int)$userId,
2159 'type' => (int)$type,
2160 'action' => (int)$action,
2161 'error' => (int)$error,
2162 'details_nr' => (int)$details_nr,
2163 'details' => $details,
2164 'log_data' => serialize($data),
2165 'tablename' => $tablename,
2166 'recuid' => (int)$recuid,
2167 'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
2168 'tstamp' => $GLOBALS['EXEC_TIME'],
2169 'event_pid' => (int)$event_pid,
2170 'NEWid' => $NEWid,
2171 'workspace' => $this->workspace
2172 );
2173 $this->db->exec_INSERTquery('sys_log', $fields_values);
2174 return $this->db->sql_insert_id();
2175 }
2176
2177 /**
2178 * Simple logging function
2179 *
2180 * @param string $message Log message
2181 * @param string $extKey Option extension key / module name
2182 * @param int $error Error level. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin)
2183 * @return int Log entry UID
2184 */
2185 public function simplelog($message, $extKey = '', $error = 0) {
2186 return $this->writelog(4, 0, $error, 0, ($extKey ? '[' . $extKey . '] ' : '') . $message, array());
2187 }
2188
2189 /**
2190 * Sends a warning to $email if there has been a certain amount of failed logins during a period.
2191 * If a login fails, this function is called. It will look up the sys_log to see if there
2192 * have been more than $max failed logins the last $secondsBack seconds (default 3600).
2193 * If so, an email with a warning is sent to $email.
2194 *
2195 * @param string $email Email address
2196 * @param int $secondsBack Number of sections back in time to check. This is a kind of limit for how many failures an hour for instance.
2197 * @param int $max Max allowed failures before a warning mail is sent
2198 * @return void
2199 * @access private
2200 */
2201 public function checkLogFailures($email, $secondsBack = 3600, $max = 3) {
2202 if ($email) {
2203 // Get last flag set in the log for sending
2204 $theTimeBack = $GLOBALS['EXEC_TIME'] - $secondsBack;
2205 $res = $this->db->exec_SELECTquery('tstamp', 'sys_log', 'type=255 AND action=4 AND tstamp>' . (int)$theTimeBack, '', 'tstamp DESC', '1');
2206 if ($testRow = $this->db->sql_fetch_assoc($res)) {
2207 $theTimeBack = $testRow['tstamp'];
2208 }
2209 $this->db->sql_free_result($res);
2210 // Check for more than $max number of error failures with the last period.
2211 $res = $this->db->exec_SELECTquery('*', 'sys_log', 'type=255 AND action=3 AND error<>0 AND tstamp>' . (int)$theTimeBack, '', 'tstamp');
2212 if ($this->db->sql_num_rows($res) > $max) {
2213 // OK, so there were more than the max allowed number of login failures - so we will send an email then.
2214 $subject = 'TYPO3 Login Failure Warning (at ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ')';
2215 $email_body = 'There have been some attempts (' . $this->db->sql_num_rows($res) . ') to login at the TYPO3
2216 site "' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '" (' . GeneralUtility::getIndpEnv('HTTP_HOST') . ').
2217
2218 This is a dump of the failures:
2219
2220 ';
2221 while ($testRows = $this->db->sql_fetch_assoc($res)) {
2222 $theData = unserialize($testRows['log_data']);
2223 $email_body .= date(
2224 $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
2225 $testRows['tstamp']
2226 ) . ': ' . @sprintf($testRows['details'], (string)$theData[0], (string)$theData[1], (string)$theData[2]);
2227 $email_body .= LF;
2228 }
2229 $from = \TYPO3\CMS\Core\Utility\MailUtility::getSystemFrom();
2230 /** @var $mail \TYPO3\CMS\Core\Mail\MailMessage */
2231 $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
2232 $mail->setTo($email)->setFrom($from)->setSubject($subject)->setBody($email_body);
2233 $mail->send();
2234 // Logout written to log
2235 $this->writelog(255, 4, 0, 3, 'Failure warning (%s failures within %s seconds) sent by email to %s', array($this->db->sql_num_rows($res), $secondsBack, $email));
2236 $this->db->sql_free_result($res);
2237 }
2238 }
2239 }
2240
2241 /**
2242 * Getter for the cookie name
2243 *
2244 * @static
2245 * @return string returns the configured cookie name
2246 */
2247 static public function getCookieName() {
2248 $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName']);
2249 if (empty($configuredCookieName)) {
2250 $configuredCookieName = 'be_typo_user';
2251 }
2252 return $configuredCookieName;
2253 }
2254
2255 /**
2256 * If TYPO3_CONF_VARS['BE']['enabledBeUserIPLock'] is enabled and
2257 * an IP-list is found in the User TSconfig objString "options.lockToIP",
2258 * then make an IP comparison with REMOTE_ADDR and return the outcome (TRUE/FALSE)
2259 *
2260 * @return bool TRUE, if IP address validates OK (or no check is done at all)
2261 */
2262 public function checkLockToIP() {
2263 $out = 1;
2264 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['enabledBeUserIPLock']) {
2265 $IPList = $this->getTSConfigVal('options.lockToIP');
2266 if (trim($IPList)) {
2267 $baseIP = GeneralUtility::getIndpEnv('REMOTE_ADDR');
2268 $out = GeneralUtility::cmpIP($baseIP, $IPList);
2269 }
2270 }
2271 return $out;
2272 }
2273
2274 /**
2275 * Check if user is logged in and if so, call ->fetchGroupData() to load group information and
2276 * access lists of all kind, further check IP, set the ->uc array and send login-notification email if required.
2277 * If no user is logged in the default behaviour is to exit with an error message,
2278 * but this will happen ONLY if the constant TYPO3_PROCEED_IF_NO_USER is set TRUE.
2279 * This function is called right after ->start() in fx. init.php
2280 *
2281 * @throws \RuntimeException
2282 * @return void
2283 */
2284 public function backendCheckLogin() {
2285 if (empty($this->user['uid'])) {
2286 if (!defined('TYPO3_PROCEED_IF_NO_USER') || !TYPO3_PROCEED_IF_NO_USER) {
2287 \TYPO3\CMS\Core\Utility\HttpUtility::redirect($GLOBALS['BACK_PATH']);
2288 }
2289 } else {
2290 // ...and if that's the case, call these functions
2291 $this->fetchGroupData();
2292 // The groups are fetched and ready for permission checking in this initialization.
2293 // Tables.php must be read before this because stuff like the modules has impact in this
2294 if ($this->checkLockToIP()) {
2295 if ($this->isUserAllowedToLogin()) {
2296 // Setting the UC array. It's needed with fetchGroupData first, due to default/overriding of values.
2297 $this->backendSetUC();
2298 // Email at login - if option set.
2299 $this->emailAtLogin();
2300 } else {
2301 throw new \RuntimeException('Login Error: TYPO3 is in maintenance mode at the moment. Only administrators are allowed access.', 1294585860);
2302 }
2303 } else {
2304 throw new \RuntimeException('Login Error: IP locking prevented you from being authorized. Can\'t proceed, sorry.', 1294585861);
2305 }
2306 }
2307 }
2308
2309 /**
2310 * If the backend script is in CLI mode, it will try to load a backend user named by the CLI module name (in lowercase)
2311 *
2312 * @return bool Returns TRUE if a CLI user was loaded, otherwise FALSE!
2313 */
2314 public function checkCLIuser() {
2315 // First, check if cliMode is enabled:
2316 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI) {
2317 if (!$this->user['uid']) {
2318 if (substr($GLOBALS['MCONF']['name'], 0, 5) == '_CLI_') {
2319 $userName = strtolower($GLOBALS['MCONF']['name']);
2320 $this->setBeUserByName($userName);
2321 if ($this->user['uid']) {
2322 if (!$this->isAdmin()) {
2323 return TRUE;
2324 } else {
2325 fwrite(STDERR, 'ERROR: CLI backend user "' . $userName . '" was ADMIN which is not allowed!' . LF . LF);
2326 die(3);
2327 }
2328 } else {
2329 fwrite(STDERR, 'ERROR: No backend user named "' . $userName . '" was found!' . LF . LF);
2330 die(3);
2331 }
2332 } else {
2333 fwrite(STDERR, 'ERROR: Module name, "' . $GLOBALS['MCONF']['name'] . '", was not prefixed with "_CLI_"' . LF . LF);
2334 die(3);
2335 }
2336 } else {
2337 fwrite(STDERR, 'ERROR: Another user was already loaded which is impossible in CLI mode!' . LF . LF);
2338 die(3);
2339 }
2340 }
2341 return FALSE;
2342 }
2343
2344 /**
2345 * Initialize the internal ->uc array for the backend user
2346 * Will make the overrides if necessary, and write the UC back to the be_users record if changes has happened
2347 *
2348 * @return void
2349 * @internal
2350 */
2351 public function backendSetUC() {
2352 // UC - user configuration is a serialized array inside the user object
2353 // If there is a saved uc we implement that instead of the default one.
2354 $temp_theSavedUC = unserialize($this->user['uc']);
2355 if (is_array($temp_theSavedUC)) {
2356 $this->unpack_uc($temp_theSavedUC);
2357 }
2358 // Setting defaults if uc is empty
2359 $updated = FALSE;
2360 if (!is_array($this->uc)) {
2361 $this->uc = array_merge(
2362 $this->uc_default,
2363 (array)$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUC'],
2364 GeneralUtility::removeDotsFromTS((array)$this->getTSConfigProp('setup.default'))
2365 );
2366 $this->overrideUC();
2367 $updated = TRUE;
2368 }
2369 // If TSconfig is updated, update the defaultUC.
2370 if ($this->userTSUpdated) {
2371 $this->overrideUC();
2372 $updated = TRUE;
2373 }
2374 // Setting default lang from be_user record.
2375 if (!isset($this->uc['lang'])) {
2376 $this->uc['lang'] = $this->user['lang'];
2377 $updated = TRUE;
2378 }
2379 // Setting the time of the first login:
2380 if (!isset($this->uc['firstLoginTimeStamp'])) {
2381 $this->uc['firstLoginTimeStamp'] = $GLOBALS['EXEC_TIME'];
2382 $updated = TRUE;
2383 }
2384 // Saving if updated.
2385 if ($updated) {
2386 $this->writeUC();
2387 }
2388 }
2389
2390 /**
2391 * Override: Call this function every time the uc is updated.
2392 * That is 1) by reverting to default values, 2) in the setup-module, 3) userTS changes (userauthgroup)
2393 *
2394 * @return void
2395 * @internal
2396 */
2397 public function overrideUC() {
2398 $this->uc = array_merge((array)$this->uc, (array)$this->getTSConfigProp('setup.override'));
2399 }
2400
2401 /**
2402 * Clears the user[uc] and ->uc to blank strings. Then calls ->backendSetUC() to fill it again with reset contents
2403 *
2404 * @return void
2405 * @internal
2406 */
2407 public function resetUC() {
2408 $this->user['uc'] = '';
2409 $this->uc = '';
2410 $this->backendSetUC();
2411 }
2412
2413 /**
2414 * Will send an email notification to warning_email_address/the login users email address when a login session is just started.
2415 * Depends on various parameters whether mails are send and to whom.
2416 *
2417 * @return void
2418 * @access private
2419 */
2420 private function emailAtLogin() {
2421 if ($this->loginSessionStarted) {
2422 // Send notify-mail
2423 $subject = 'At "' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '"' . ' from '
2424 . GeneralUtility::getIndpEnv('REMOTE_ADDR')
2425 . (GeneralUtility::getIndpEnv('REMOTE_HOST') ? ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')' : '');
2426 $msg = sprintf(
2427 'User "%s" logged in from %s (%s) at "%s" (%s)',
2428 $this->user['username'],
2429 GeneralUtility::getIndpEnv('REMOTE_ADDR'),
2430 GeneralUtility::getIndpEnv('REMOTE_HOST'),
2431 $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
2432 GeneralUtility::getIndpEnv('HTTP_HOST')
2433 );
2434 // Warning email address
2435 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr']) {
2436 $warn = 0;
2437 $prefix = '';
2438 if ((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] & 1) {
2439 // first bit: All logins
2440 $warn = 1;
2441 $prefix = $this->isAdmin() ? '[AdminLoginWarning]' : '[LoginWarning]';
2442 }
2443 if ($this->isAdmin() && (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] & 2) {
2444 // second bit: Only admin-logins
2445 $warn = 1;
2446 $prefix = '[AdminLoginWarning]';
2447 }
2448 if ($warn) {
2449 $from = \TYPO3\CMS\Core\Utility\MailUtility::getSystemFrom();
2450 /** @var $mail \TYPO3\CMS\Core\Mail\MailMessage */
2451 $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
2452 $mail->setTo($GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'])->setFrom($from)->setSubject($prefix . ' ' . $subject)->setBody($msg);
2453 $mail->send();
2454 }
2455 }
2456 // If An email should be sent to the current user, do that:
2457 if ($this->uc['emailMeAtLogin'] && strstr($this->user['email'], '@')) {
2458 $from = \TYPO3\CMS\Core\Utility\MailUtility::getSystemFrom();
2459 /** @var $mail \TYPO3\CMS\Core\Mail\MailMessage */
2460 $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
2461 $mail->setTo($this->user['email'])->setFrom($from)->setSubject($subject)->setBody($msg);
2462 $mail->send();
2463 }
2464 }
2465 }
2466
2467 /**
2468 * Determines whether a backend user is allowed to access the backend.
2469 *
2470 * The conditions are:
2471 * + backend user is a regular user and adminOnly is not defined
2472 * + backend user is an admin user
2473 * + backend user is used in CLI context and adminOnly is explicitly set to "2"
2474 * + backend user is being controlled by an admin user
2475 *
2476 * @return bool Whether a backend user is allowed to access the backend
2477 */
2478 protected function isUserAllowedToLogin() {
2479 $isUserAllowedToLogin = FALSE;
2480 $adminOnlyMode = $GLOBALS['TYPO3_CONF_VARS']['BE']['adminOnly'];
2481 // Backend user is allowed if adminOnly is not set or user is an admin:
2482 if (!$adminOnlyMode || $this->isAdmin()) {
2483 $isUserAllowedToLogin = TRUE;
2484 } elseif ($adminOnlyMode == 2 && TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI) {
2485 $isUserAllowedToLogin = TRUE;
2486 } elseif ($this->user['ses_backuserid']) {
2487 $backendUserId = (int)$this->user['ses_backuserid'];
2488 $whereAdmin = 'uid=' . $backendUserId . ' AND admin=1' . BackendUtility::BEenableFields('be_users');
2489 if ($this->db->exec_SELECTcountRows('uid', 'be_users', $whereAdmin) > 0) {
2490 $isUserAllowedToLogin = TRUE;
2491 }
2492 }
2493 return $isUserAllowedToLogin;
2494 }
2495
2496 /**
2497 * Logs out the current user and clears the form protection tokens.
2498 */
2499 public function logoff() {
2500 if (isset($GLOBALS['BE_USER'])) {
2501 \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get()->clean();
2502 }
2503 parent::logoff();
2504 }
2505
2506 }