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