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