2 namespace wcf\system\session
;
3 use wcf\data\acp\session\virtual\ACPSessionVirtual
;
4 use wcf\data\acp\session\virtual\ACPSessionVirtualAction
;
5 use wcf\data\acp\session\virtual\ACPSessionVirtualEditor
;
6 use wcf\data\session\virtual\SessionVirtual
;
7 use wcf\data\session\virtual\SessionVirtualAction
;
8 use wcf\data\session\virtual\SessionVirtualEditor
;
9 use wcf\data\session\SessionEditor
;
10 use wcf\data\user\User
;
11 use wcf\data\user\UserEditor
;
12 use wcf\system\cache\builder\SpiderCacheBuilder
;
13 use wcf\system\cache\builder\UserGroupOptionCacheBuilder
;
14 use wcf\system\cache\builder\UserGroupPermissionCacheBuilder
;
15 use wcf\system\database\DatabaseException
;
16 use wcf\system\event\EventHandler
;
17 use wcf\system\exception\PermissionDeniedException
;
18 use wcf\system\page\PageLocationManager
;
19 use wcf\system\user\authentication\UserAuthenticationFactory
;
20 use wcf\system\user\storage\UserStorageHandler
;
21 use wcf\system\SingletonFactory
;
23 use wcf\system\WCFACP
;
24 use wcf\util\HeaderUtil
;
25 use wcf\util\UserUtil
;
30 * @author Alexander Ebert
31 * @copyright 2001-2019 WoltLab GmbH
32 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
33 * @package WoltLabSuite\Core\System\Session
35 * @property-read string $sessionID unique textual identifier of the session
36 * @property-read integer|null $userID id of the user the session belongs to or `null` if the acp session belongs to a guest
37 * @property-read string $ipAddress id of the user whom the session belongs to
38 * @property-read string $userAgent user agent of the user whom the session belongs to
39 * @property-read integer $lastActivityTime timestamp at which the latest activity occurred
40 * @property-read string $requestURI uri of the latest request
41 * @property-read string $requestMethod used request method of the latest request (`GET`, `POST`)
42 * @property-read integer|null $pageID id of the latest page visited
43 * @property-read integer|null $pageObjectID id of the object the latest page visited belongs to
44 * @property-read integer|null $parentPageID id of the parent page of latest page visited
45 * @property-read integer|null $parentPageObjectID id of the object the parent page of latest page visited belongs to
46 * @property-read integer $spiderID id of the spider the session belongs to
48 class SessionHandler
extends SingletonFactory
{
50 * suffix used to tell ACP and frontend cookies apart
53 protected $cookieSuffix = '';
56 * prevents update on shutdown
59 protected $doNotUpdate = false;
62 * disables page tracking
65 protected $disableTracking = false;
68 * various environment variables
71 protected $environment = [];
74 * group data and permissions
77 protected $groupData = null;
80 * true if client provided a valid session cookie
83 protected $hasValidCookie = false;
86 * true if within ACP or WCFSetup
89 protected $isACP = false;
92 * language id for active user
95 protected $languageID = 0;
98 * language ids for active user
101 protected $languageIDs = null;
105 * @var \wcf\data\acp\session\ACPSession
107 protected $session = null;
113 protected $sessionClassName = '';
116 * session editor class name
119 protected $sessionEditorClassName = '';
122 * virtual session support
125 protected $supportsVirtualSessions = false;
131 protected $styleID = null;
137 protected $user = null;
143 protected $variables = null;
146 * indicates if session variables changed and must be saved upon shutdown
149 protected $variablesChanged = false;
152 * virtual session object, null for guests
153 * @var \wcf\data\session\virtual\SessionVirtual
155 protected $virtualSession = false;
158 * true if this is a new session
161 protected $firstVisit = false;
164 * list of names of permissions only available for users
167 protected $usersOnlyPermissions = [];
170 * Provides access to session data.
175 public function __get($key) {
176 if (isset($this->environment
[$key])) {
177 return $this->environment
[$key];
180 return $this->session
->{$key};
186 protected function init() {
187 $this->isACP
= (class_exists(WCFACP
::class, false) ||
!PACKAGE_ID
);
188 $this->usersOnlyPermissions
= UserGroupOptionCacheBuilder
::getInstance()->getData([], 'usersOnlyOptions');
192 * Suffix used to tell ACP and frontend cookies apart
194 * @param string $cookieSuffix cookie suffix
196 public function setCookieSuffix($cookieSuffix) {
197 $this->cookieSuffix
= $cookieSuffix;
201 * Sets a boolean value to determine if the client provided a valid session cookie.
203 * @param boolean $hasValidCookie
206 public function setHasValidCookie($hasValidCookie) {
207 $this->hasValidCookie
= $hasValidCookie;
211 * Returns true if client provided a valid session cookie.
216 public function hasValidCookie() {
217 return $this->hasValidCookie
;
221 * Loads an existing session or creates a new one.
223 * @param string $sessionEditorClassName
224 * @param string $sessionID
226 public function load($sessionEditorClassName, $sessionID) {
227 $this->sessionEditorClassName
= $sessionEditorClassName;
228 $this->sessionClassName
= call_user_func([$sessionEditorClassName, 'getBaseClass']);
229 $this->supportsVirtualSessions
= call_user_func([$this->sessionClassName
, 'supportsVirtualSessions']);
231 // try to get existing session
232 if (!empty($sessionID)) {
233 $this->getExistingSession($sessionID);
236 // create new session
237 if ($this->session
=== null) {
243 * Initializes session system.
245 public function initSession() {
246 // init session environment
247 $this->loadVariables();
248 $this->initSecurityToken();
250 // session id change was delayed to the next request
251 // as the SID constants already were defined
252 if ($this->getVar('__changeSessionID')) {
253 $this->unregister('__changeSessionID');
254 $this->changeSessionID();
256 $this->defineConstants();
258 // assign language and style id
259 $this->languageID
= ($this->getVar('languageID') === null) ?
$this->user
->languageID
: $this->getVar('languageID');
260 $this->styleID
= ($this->getVar('styleID') === null) ?
$this->user
->styleID
: $this->getVar('styleID');
262 // init environment variables
263 $this->initEnvironment();
265 // https://github.com/WoltLab/WCF/issues/2568
266 if ($this->getVar('__wcfIsFirstVisit') === true) {
267 $this->firstVisit
= true;
268 $this->unregister('__wcfIsFirstVisit');
273 * Changes the session id to a new random one.
275 * Usually a change is requested after login to ensure
276 * that the user is not running a fixated session by an
279 protected function changeSessionID() {
280 $newSessionID = bin2hex(\random_bytes
(20));
282 /** @var \wcf\data\DatabaseObjectEditor $sessionEditor */
283 $sessionEditor = new $this->sessionEditorClassName($this->session
);
284 $sessionEditor->update([
285 'sessionID' => $newSessionID
288 // fetch new session data from database
289 $this->session
= new $this->sessionClassName($newSessionID);
291 HeaderUtil
::setCookie('cookieHash'.$this->cookieSuffix
, $newSessionID);
295 * Initializes environment variables.
297 protected function initEnvironment() {
298 $this->environment
= [
299 'lastRequestURI' => $this->session
->requestURI
,
300 'lastRequestMethod' => $this->session
->requestMethod
,
301 'ipAddress' => UserUtil
::getIpAddress(),
302 'userAgent' => UserUtil
::getUserAgent(),
303 'requestURI' => UserUtil
::getRequestURI(),
304 'requestMethod' => !empty($_SERVER['REQUEST_METHOD']) ?
substr($_SERVER['REQUEST_METHOD'], 0, 7) : ''
309 * Disables update on shutdown.
311 public function disableUpdate() {
312 $this->doNotUpdate
= true;
316 * Disables page tracking.
318 public function disableTracking() {
319 $this->disableTracking
= true;
323 * Defines global wcf constants related to session.
325 protected function defineConstants() {
326 /* the SID*-constants below are deprecated since 3.0 */
327 if (!defined('SID_ARG_1ST')) define('SID_ARG_1ST', '');
328 if (!defined('SID_ARG_2ND')) define('SID_ARG_2ND', '');
329 if (!defined('SID_ARG_2ND_NOT_ENCODED')) define('SID_ARG_2ND_NOT_ENCODED', '');
330 if (!defined('SID')) define('SID', '');
331 if (!defined('SID_INPUT_TAG')) define('SID_INPUT_TAG', '');
334 if (!defined('SECURITY_TOKEN')) define('SECURITY_TOKEN', $this->getSecurityToken());
335 if (!defined('SECURITY_TOKEN_INPUT_TAG')) define('SECURITY_TOKEN_INPUT_TAG', '<input type="hidden" name="t" value="'.$this->getSecurityToken().'">');
339 * Initializes security token.
341 protected function initSecurityToken() {
342 if ($this->getVar('__SECURITY_TOKEN') === null) {
343 $this->register('__SECURITY_TOKEN', bin2hex(\random_bytes
(20)));
348 * Returns security token.
352 public function getSecurityToken() {
353 return $this->getVar('__SECURITY_TOKEN');
357 * Validates the given security token, returns false if
358 * given token is invalid.
360 * @param string $token
363 public function checkSecurityToken($token) {
364 return \
hash_equals($this->getSecurityToken(), $token);
368 * Registers a session variable.
371 * @param mixed $value
373 public function register($key, $value) {
374 $this->variables
[$key] = $value;
375 $this->variablesChanged
= true;
379 * Unsets a session variable.
383 public function unregister($key) {
384 unset($this->variables
[$key]);
385 $this->variablesChanged
= true;
389 * Returns the value of a session variable or `null` if the session
390 * variable does not exist.
395 public function getVar($key) {
396 if (isset($this->variables
[$key])) {
397 return $this->variables
[$key];
404 * Initializes session variables.
406 protected function loadVariables() {
407 if ($this->session
->sessionVariables
!== null) {
408 $this->variables
= @unserialize
($this->session
->sessionVariables
);
409 if (!is_array($this->variables
)) {
410 $this->variables
= [];
414 $this->variables
= [];
419 * Returns the user object of this session.
423 public function getUser() {
428 * Tries to read existing session identified by the given session id.
430 * @param string $sessionID
432 protected function getExistingSession($sessionID) {
433 $this->session
= new $this->sessionClassName($sessionID);
434 if (!$this->session
->sessionID
) {
435 $this->session
= null;
439 $this->user
= new User($this->session
->userID
);
441 $this->virtualSession
= ACPSessionVirtual
::getExistingSession($sessionID);
444 $this->virtualSession
= SessionVirtual
::getExistingSession($sessionID);
447 if (!$this->validate()) {
448 $this->session
= null;
450 $this->virtualSession
= false;
455 $this->loadVirtualSession();
459 * Loads the virtual session object unless the user is not logged in or the session
460 * does not support virtual sessions. If there is no virtual session yet, it will be
461 * created on-the-fly.
463 * @param boolean $forceReload
465 protected function loadVirtualSession($forceReload = false) {
466 if ($this->virtualSession
=== null ||
$forceReload) {
467 $this->virtualSession
= null;
469 $virtualSessionAction = new ACPSessionVirtualAction([], 'create', ['data' => ['sessionID' => $this->session
->sessionID
]]);
472 $virtualSessionAction = new SessionVirtualAction([], 'create', ['data' => ['sessionID' => $this->session
->sessionID
]]);
476 $returnValues = $virtualSessionAction->executeAction();
477 $this->virtualSession
= $returnValues['returnValues'];
479 catch (DatabaseException
$e) {
480 // MySQL error 23000 = unique key
481 // do not check against the message itself, some weird systems localize them
482 if ($e->getCode() == 23000) {
484 $this->virtualSession
= ACPSessionVirtual
::getExistingSession($this->session
->sessionID
);
487 $this->virtualSession
= SessionVirtual
::getExistingSession($this->session
->sessionID
);
495 * Validates the ip address and the user agent of this session.
499 protected function validate() {
500 if (SESSION_VALIDATE_IP_ADDRESS
) {
501 if ($this->virtualSession
instanceof ACPSessionVirtual
) {
502 if ($this->virtualSession
->ipAddress
!= UserUtil
::getIpAddress()) {
506 else if ($this->session
->ipAddress
!= UserUtil
::getIpAddress()) {
511 if (SESSION_VALIDATE_USER_AGENT
) {
512 if ($this->virtualSession
instanceof ACPSessionVirtual
) {
513 if ($this->virtualSession
->userAgent
!= UserUtil
::getUserAgent()) {
517 else if ($this->session
->userAgent
!= UserUtil
::getUserAgent()) {
526 * Creates a new session.
528 protected function create() {
530 if ($this->sessionEditorClassName
== SessionEditor
::class) {
531 // get spider information
532 $spiderID = $this->getSpiderID(UserUtil
::getUserAgent());
533 if ($spiderID !== null) {
534 // try to use existing session
535 if (($session = $this->getExistingSpiderSession($spiderID)) !== null) {
536 $this->user
= new User(null);
537 $this->session
= $session;
543 // create new session hash
544 $sessionID = bin2hex(\random_bytes
(20));
546 // get user automatically
547 $this->user
= UserAuthenticationFactory
::getInstance()->getUserAuthentication()->loginAutomatically(call_user_func([$this->sessionClassName
, 'supportsPersistentLogins']));
550 if ($this->user
=== null) {
551 // no valid user found
553 $this->user
= new User(null);
555 else if (!$this->supportsVirtualSessions
) {
556 // delete all other sessions of this user
557 call_user_func([$this->sessionEditorClassName
, 'deleteUserSessions'], [$this->user
->userID
]);
560 $createNewSession = true;
561 // find existing session
562 $session = call_user_func([$this->sessionClassName
, 'getSessionByUserID'], $this->user
->userID
);
564 if ($session !== null) {
565 // inherit existing session
566 $this->session
= $session;
567 $this->loadVirtualSession(true);
569 $createNewSession = false;
572 if ($createNewSession) {
575 'sessionID' => $sessionID,
576 'userID' => $this->user
->userID
,
577 'ipAddress' => UserUtil
::getIpAddress(),
578 'userAgent' => UserUtil
::getUserAgent(),
579 'lastActivityTime' => TIME_NOW
,
580 'requestURI' => UserUtil
::getRequestURI(),
581 'requestMethod' => !empty($_SERVER['REQUEST_METHOD']) ?
substr($_SERVER['REQUEST_METHOD'], 0, 7) : ''
584 if ($spiderID !== null) $sessionData['spiderID'] = $spiderID;
587 $this->session
= call_user_func([$this->sessionEditorClassName
, 'create'], $sessionData);
589 catch (DatabaseException
$e) {
590 // MySQL error 23000 = unique key
591 // do not check against the message itself, some weird systems localize them
592 if ($e->getCode() == 23000) {
593 // find existing session
594 $session = call_user_func([$this->sessionClassName
, 'getSessionByUserID'], $this->user
->userID
);
596 if ($session === null) {
597 // MySQL reported a unique key error, but no corresponding session exists, rethrow exception
601 // inherit existing session
602 $this->session
= $session;
603 $this->loadVirtualSession(true);
607 // unrelated to user id
612 $this->firstVisit
= true;
613 $this->loadVirtualSession(true);
618 * Returns the value of the permission with the given name.
620 * @param string $permission
621 * @return mixed permission value
623 public function getPermission($permission) {
624 // check if a users only permission is checked for a guest and return
625 // false if that is the case
626 if (!$this->user
->userID
&& in_array($permission, $this->usersOnlyPermissions
)) {
630 $this->loadGroupData();
632 if (!isset($this->groupData
[$permission])) return false;
633 return $this->groupData
[$permission];
637 * Returns true if a permission was set to 'Never'. This is required to preserve
638 * compatibility, while preventing ACLs from overruling a 'Never' setting.
640 * @param string $permission
643 public function getNeverPermission($permission) {
644 $this->loadGroupData();
646 return (isset($this->groupData
['__never'][$permission]));
650 * Checks if the active user has the given permissions and throws a
651 * PermissionDeniedException if that isn't the case.
653 * @param string[] $permissions list of permissions where each one must pass
654 * @throws PermissionDeniedException
656 public function checkPermissions(array $permissions) {
657 foreach ($permissions as $permission) {
658 if (!$this->getPermission($permission)) {
659 throw new PermissionDeniedException();
665 * Loads group data from cache.
667 protected function loadGroupData() {
668 if ($this->groupData
!== null) return;
670 // work-around for setup process (package wcf does not exist yet)
672 $sql = "SELECT groupID
673 FROM wcf".WCF_N
."_user_to_group
675 $statement = WCF
::getDB()->prepareStatement($sql);
676 $statement->execute([$this->user
->userID
]);
677 $groupIDs = $statement->fetchAll(\PDO
::FETCH_COLUMN
);
680 $groupIDs = $this->user
->getGroupIDs();
683 // get group data from cache
684 $this->groupData
= UserGroupPermissionCacheBuilder
::getInstance()->getData($groupIDs);
685 if (isset($this->groupData
['groupIDs']) && $this->groupData
['groupIDs'] != $groupIDs) {
686 $this->groupData
= [];
691 * Returns language ids for active user.
695 public function getLanguageIDs() {
696 $this->loadLanguageIDs();
698 return $this->languageIDs
;
702 * Loads language ids for active user.
704 protected function loadLanguageIDs() {
705 if ($this->languageIDs
!== null) return;
707 $this->languageIDs
= [];
709 if (!$this->user
->userID
) {
713 // work-around for setup process (package wcf does not exist yet)
715 $sql = "SELECT languageID
716 FROM wcf".WCF_N
."_user_to_language
718 $statement = WCF
::getDB()->prepareStatement($sql);
719 $statement->execute([$this->user
->userID
]);
720 $this->languageIDs
= $statement->fetchAll(\PDO
::FETCH_COLUMN
);
723 $this->languageIDs
= $this->user
->getLanguageIDs();
728 * Stores a new user object in this session, e.g. a user was guest because not
729 * logged in, after the login his old session is used to store his full data.
732 * @param boolean $hideSession if true, database won't be updated
734 public function changeUser(User
$user, $hideSession = false) {
735 $eventParameters = ['user' => $user, 'hideSession' => $hideSession];
737 EventHandler
::getInstance()->fireAction($this, 'beforeChangeUser', $eventParameters);
739 $user = $eventParameters['user'];
740 $hideSession = $eventParameters['hideSession'];
742 // skip changeUserVirtual, if session will not be persistent anyway
744 $this->changeUserVirtual($user);
747 // update user reference
749 $this->userID
= $this->user
->userID ?
: 0;
752 $this->groupData
= null;
753 $this->languageIDs
= null;
754 $this->languageID
= $this->user
->languageID
;
755 $this->styleID
= $this->user
->styleID
;
758 WCF
::setLanguage($this->languageID ?
: 0);
760 // in some cases the language id can be stuck in the session variables
761 $this->unregister('languageID');
763 EventHandler
::getInstance()->fireAction($this, 'afterChangeUser');
767 * Changes the user stored in the session.
770 * @throws DatabaseException
772 protected function changeUserVirtual(User
$user) {
773 /** @var \wcf\data\DatabaseObjectEditor $sessionEditor */
775 switch ($user->userID
) {
777 // user -> guest (logout)
780 // delete virtual session
781 if ($this->virtualSession
) {
783 $virtualSessionEditor = new ACPSessionVirtualEditor($this->virtualSession
);
786 $virtualSessionEditor = new SessionVirtualEditor($this->virtualSession
);
788 $virtualSessionEditor->delete();
792 $sessionCount = ACPSessionVirtual
::countVirtualSessions($this->session
->sessionID
);
795 $sessionCount = SessionVirtual
::countVirtualSessions($this->session
->sessionID
);
798 // there are still other virtual sessions, create a new session
802 'sessionID' => bin2hex(\random_bytes
(20)),
803 'userID' => $user->userID
,
804 'ipAddress' => UserUtil
::getIpAddress(),
805 'userAgent' => UserUtil
::getUserAgent(),
806 'lastActivityTime' => TIME_NOW
,
807 'requestURI' => UserUtil
::getRequestURI(),
808 'requestMethod' => !empty($_SERVER['REQUEST_METHOD']) ?
substr($_SERVER['REQUEST_METHOD'], 0, 7) : ''
811 $this->session
= call_user_func([$this->sessionEditorClassName
, 'create'], $sessionData);
813 HeaderUtil
::setCookie('cookieHash'.$this->cookieSuffix
, $this->session
->sessionID
);
816 // this was the last virtual session, re-use current session
818 $sessionEditor = new $this->sessionEditorClassName($this->session
);
819 $sessionEditor->update([
820 'userID' => $user->userID
826 // guest -> user (login)
829 if (!$this->supportsVirtualSessions
) {
830 // delete all other sessions of this user
831 call_user_func([$this->sessionEditorClassName
, 'deleteUserSessions'], [$user->userID
]);
834 // find existing session for this user
835 $session = call_user_func([$this->sessionClassName
, 'getSessionByUserID'], $user->userID
);
837 // no session exists, re-use current session
838 if ($session === null) {
840 $sessionEditor = new $this->sessionEditorClassName($this->session
);
843 $this->register('__changeSessionID', true);
845 $sessionEditor->update([
846 'userID' => $user->userID
849 catch (DatabaseException
$e) {
850 // MySQL error 23000 = unique key
851 // do not check against the message itself, some weird systems localize them
852 if ($e->getCode() == 23000) {
853 // delete guest session
854 $sessionEditor = new $this->sessionEditorClassName($this->session
);
855 $sessionEditor->delete();
857 // inherit existing session
858 $this->session
= $session;
867 // delete guest session
868 $sessionEditor = new $this->sessionEditorClassName($this->session
);
869 $sessionEditor->delete();
871 // inherit existing session
872 $this->session
= $session;
874 // inherit security token
875 $variables = @unserialize
($this->session
->sessionVariables
);
876 if (is_array($variables) && !empty($variables['__SECURITY_TOKEN'])) {
877 $this->register('__SECURITY_TOKEN', $variables['__SECURITY_TOKEN']);
880 HeaderUtil
::setCookie('cookieHash'.$this->cookieSuffix
, $this->session
->sessionID
);
885 $this->loadVirtualSession(true);
889 * Updates user session on shutdown.
891 public function update() {
892 if ($this->doNotUpdate
) return;
896 'ipAddress' => UserUtil
::getIpAddress(),
897 'userAgent' => $this->userAgent
,
898 'requestURI' => $this->requestURI
,
899 'requestMethod' => $this->requestMethod
,
900 'lastActivityTime' => TIME_NOW
902 if ($this->variablesChanged
) {
903 $data['sessionVariables'] = serialize($this->variables
);
905 if (!class_exists('wcf\system\CLIWCF', false) && !$this->isACP
&& !$this->disableTracking
) {
906 $pageLocations = PageLocationManager
::getInstance()->getLocations();
907 if (isset($pageLocations[0])) {
908 $data['pageID'] = $pageLocations[0]['pageID'];
909 $data['pageObjectID'] = ($pageLocations[0]['pageObjectID'] ?
: null);
910 $data['parentPageID'] = null;
911 $data['parentPageObjectID'] = null;
913 for ($i = 1, $length = count($pageLocations); $i < $length; $i++
) {
914 if (!empty($pageLocations[$i]['useAsParentLocation'])) {
915 $data['parentPageID'] = $pageLocations[$i]['pageID'];
916 $data['parentPageObjectID'] = ($pageLocations[$i]['pageObjectID'] ?
: null);
924 /** @var \wcf\data\DatabaseObjectEditor $sessionEditor */
925 $sessionEditor = new $this->sessionEditorClassName($this->session
);
926 $sessionEditor->update($data);
928 if ($this->virtualSession
instanceof ACPSessionVirtual
) {
930 $virtualSessionEditor = new ACPSessionVirtualEditor($this->virtualSession
);
933 $virtualSessionEditor = new SessionVirtualEditor($this->virtualSession
);
936 $virtualSessionEditor->updateLastActivityTime();
941 * Updates last activity time to protect session from expiring.
943 public function keepAlive() {
944 $this->disableUpdate();
946 // update last activity time
947 /** @var \wcf\data\DatabaseObjectEditor $sessionEditor */
948 $sessionEditor = new $this->sessionEditorClassName($this->session
);
949 $sessionEditor->update([
950 'lastActivityTime' => TIME_NOW
953 if ($this->virtualSession
instanceof ACPSessionVirtual
) {
955 $virtualSessionEditor = new ACPSessionVirtualEditor($this->virtualSession
);
958 $virtualSessionEditor = new SessionVirtualEditor($this->virtualSession
);
960 $virtualSessionEditor->updateLastActivityTime();
965 * Deletes this session and it's related data.
967 public function delete() {
969 if ($this->user
->userID
) {
970 self
::resetSessions([$this->user
->userID
]);
972 // update last activity time
974 $editor = new UserEditor($this->user
);
975 $editor->update(['lastActivityTime' => TIME_NOW
]);
979 // 1st: Change user to guest, otherwise other the entire session, including
980 // all virtual sessions of the user will be deleted
981 $this->changeUser(new User(null));
983 // 2nd: Actually remove session
984 /** @var \wcf\data\DatabaseObjectEditor $sessionEditor */
985 $sessionEditor = new $this->sessionEditorClassName($this->session
);
986 $sessionEditor->delete();
989 $this->disableUpdate();
993 * Deletes this session if:
994 * - it is newly created in this request, and
995 * - it belongs to a guest.
997 * This method is useful if you have controllers that are likely to be
998 * accessed by a user agent that is not going to re-use sessions (e.g.
999 * curl in a cronjob). It immediately remove the session that was created
1000 * just for that request and that is not going to be used ever again.
1004 public function deleteIfNew() {
1005 if ($this->isFirstVisit() && !$this->getUser()->userID
) {
1011 * Returns currently active language id.
1015 public function getLanguageID() {
1016 return $this->languageID
;
1020 * Sets the currently active language id.
1022 * @param integer $languageID
1024 public function setLanguageID($languageID) {
1025 $this->languageID
= $languageID;
1026 $this->register('languageID', $this->languageID
);
1030 * Returns currently active style id.
1034 public function getStyleID() {
1035 return $this->styleID
;
1039 * Sets the currently active style id.
1041 * @param integer $styleID
1043 public function setStyleID($styleID) {
1044 $this->styleID
= $styleID;
1045 $this->register('styleID', $this->styleID
);
1049 * Resets session-specific storage data.
1051 * @param integer[] $userIDs
1053 public static function resetSessions(array $userIDs = []) {
1054 if (!empty($userIDs)) {
1055 UserStorageHandler
::getInstance()->reset($userIDs, 'groupIDs');
1056 UserStorageHandler
::getInstance()->reset($userIDs, 'languageIDs');
1059 UserStorageHandler
::getInstance()->resetAll('groupIDs');
1060 UserStorageHandler
::getInstance()->resetAll('languageIDs');
1065 * Returns the spider id for given user agent.
1067 * @param string $userAgent
1070 protected function getSpiderID($userAgent) {
1071 $spiderList = SpiderCacheBuilder
::getInstance()->getData();
1072 $userAgent = strtolower($userAgent);
1074 foreach ($spiderList as $spider) {
1075 if (strpos($userAgent, $spider->spiderIdentifier
) !== false) {
1076 return $spider->spiderID
;
1084 * Searches for existing session of a search spider.
1086 * @param integer $spiderID
1087 * @return \wcf\data\session\Session
1089 protected function getExistingSpiderSession($spiderID) {
1091 FROM wcf".WCF_N
."_session
1093 AND userID IS NULL";
1094 $statement = WCF
::getDB()->prepareStatement($sql);
1095 $statement->execute([$spiderID]);
1096 $row = $statement->fetchArray();
1097 if ($row !== false) {
1098 // fix session validation
1099 $row['ipAddress'] = UserUtil
::getIpAddress();
1100 $row['userAgent'] = UserUtil
::getUserAgent();
1102 // return session object
1103 return new $this->sessionClassName(null, $row);
1110 * Returns true if this is a new session.
1114 public function isFirstVisit() {
1115 return $this->firstVisit
;