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