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