2 namespace wcf\data\user
;
3 use wcf\data\language\Language
;
4 use wcf\data\user\group\UserGroup
;
5 use wcf\data\DatabaseObject
;
6 use wcf\data\IUserContent
;
7 use wcf\data\user\option\UserOption
;
8 use wcf\system\cache\builder\UserOptionCacheBuilder
;
9 use wcf\system\language\LanguageFactory
;
10 use wcf\system\request\IRouteController
;
11 use wcf\system\request\LinkHandler
;
12 use wcf\system\user\storage\UserStorageHandler
;
14 use wcf\util\CryptoUtil
;
15 use wcf\util\PasswordUtil
;
16 use wcf\util\UserUtil
;
21 * @author Alexander Ebert
22 * @copyright 2001-2018 WoltLab GmbH
23 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
24 * @package WoltLabSuite\Core\Data\User
26 * @property-read integer $userID unique id of the user
27 * @property-read string $username name of the user
28 * @property-read string $email email address of the user
29 * @property-read string $password double salted hash of the user's password
30 * @property-read string $accessToken token used for access authentication, for example used by feed pages
31 * @property-read integer $languageID id of the interface language used by the user
32 * @property-read integer $registrationDate timestamp at which the user has registered/has been created
33 * @property-read integer $styleID id of the style used by the user
34 * @property-read integer $banned is `1` if the user is banned, otherwise `0`
35 * @property-read string $banReason reason why the user is banned
36 * @property-read integer $banExpires timestamp at which the banned user is automatically unbanned
37 * @property-read integer $activationCode code sent to the user's email address used for account activation
38 * @property-read integer $lastLostPasswordRequestTime timestamp at which the user has reported that they lost their password or 0 if password has not been reported as lost
39 * @property-read string $lostPasswordKey code used for authenticating setting new password after password loss or empty if password has not been reported as lost
40 * @property-read integer $lastUsernameChange timestamp at which the user changed their name the last time or 0 if username has not been changed
41 * @property-read string $newEmail new email address of the user that has to be manually confirmed or empty if no new email address has been set
42 * @property-read string $oldUsername previous name of the user or empty if they have had no previous name
43 * @property-read integer $quitStarted timestamp at which the user terminated their account
44 * @property-read integer $reactivationCode code used for authenticating setting new email address or empty if no new email address has been set
45 * @property-read string $registrationIpAddress ip address of the user at the time of registration or empty if user has been created manually or if no ip address are logged
46 * @property-read integer|null $avatarID id of the user's avatar or null if they have no avatar
47 * @property-read integer $disableAvatar is `1` if the user's avatar has been disabled, otherwise `0`
48 * @property-read string $disableAvatarReason reason why the user's avatar is disabled
49 * @property-read integer $disableAvatarExpires timestamp at which the user's avatar will automatically be enabled again
50 * @property-read integer $enableGravatar is `1` if the user uses a gravatar as avatar, otherwise `0`
51 * @property-read string $gravatarFileExtension extension of the user's gravatar file
52 * @property-read string $signature text of the user's signature
53 * @property-read integer $signatureEnableBBCodes is `1` if BBCodes will rendered in the user's signature, otherwise `0`
54 * @property-read integer $signatureEnableHtml is `1` if HTML will rendered in the user's signature, otherwise `0`
55 * @property-read integer $signatureEnableSmilies is `1` if smilies will rendered in the user's signature, otherwise `0`
56 * @property-read integer $disableSignature is `1` if the user's signature has been disabled, otherwise `0`
57 * @property-read string $disableSignatureReason reason why the user's signature is disabled
58 * @property-read integer $disableSignatureExpires timestamp at which the user's signature will automatically be enabled again
59 * @property-read integer $lastActivityTime timestamp of the user's last activity
60 * @property-read integer $profileHits number of times the user's profile has been visited
61 * @property-read integer|null $rankID id of the user's rank or null if they have no rank
62 * @property-read string $userTitle custom user title used instead of rank title or empty if user has no custom title
63 * @property-read integer|null $userOnlineGroupID id of the user group whose online marking is used when printing the user's formatted name or null if no special marking is used
64 * @property-read integer $activityPoints total number of the user's activity points
65 * @property-read string $notificationMailToken token used for authenticating requests by the user to disable notification emails
66 * @property-read string $authData data of the third party used for authentication
67 * @property-read integer $likesReceived cumulative result of likes (counting +1) the user's contents have received
68 * @property-read string $coverPhotoHash hash of the user's cover photo
69 * @property-read string $coverPhotoExtension extension of the user's cover photo file
70 * @property-read integer $disableCoverPhoto is `1` if the user's cover photo has been disabled, otherwise `0`
71 * @property-read string $disableCoverPhotoReason reason why the user's cover photo is disabled
72 * @property-read integer $disableCoverPhotoExpires timestamp at which the user's cover photo will automatically be enabled again
74 final class User
extends DatabaseObject
implements IRouteController
, IUserContent
{
82 * true, if user has access to the ACP
85 protected $hasAdministrativePermissions;
88 * list of language ids
91 protected $languageIDs;
94 * date time zone object
97 protected $timezoneObj;
100 * list of user options
103 protected static $userOptions;
105 /** @noinspection PhpMissingParentConstructorInspection */
109 public function __construct($id, $row = null, DatabaseObject
$object = null) {
111 $sql = "SELECT user_option_value.*, user_table.*
112 FROM wcf".WCF_N
."_user user_table
113 LEFT JOIN wcf".WCF_N
."_user_option_value user_option_value
114 ON (user_option_value.userID = user_table.userID)
115 WHERE user_table.userID = ?";
116 $statement = WCF
::getDB()->prepareStatement($sql);
117 $statement->execute([$id]);
118 $row = $statement->fetchArray();
120 // enforce data type 'array'
121 if ($row === false) $row = [];
123 else if ($object !== null) {
124 $row = $object->data
;
127 $this->handleData($row);
131 * Returns true if the given password is the correct password for this user.
133 * @param string $password
134 * @return boolean password correct
136 public function checkPassword($password) {
140 // check if password is a valid bcrypt hash
141 if (PasswordUtil
::isBlowfish($this->password
)) {
142 if (PasswordUtil
::isDifferentBlowfish($this->password
)) {
146 // password is correct
147 if (CryptoUtil
::secureCompare($this->password
, PasswordUtil
::getDoubleSaltedHash($password, $this->password
))) {
152 // different encryption type
153 if (PasswordUtil
::checkPassword($this->username
, $password, $this->password
)) {
159 // create new password hash, either different encryption or different blowfish cost factor
160 if ($rebuild && $isValid) {
161 $userEditor = new UserEditor($this);
162 $userEditor->update([
163 'password' => $password
171 * Returns true if the given password hash from a cookie is the correct password for this user.
173 * @param string $passwordHash
174 * @return boolean password correct
176 public function checkCookiePassword($passwordHash) {
177 if (PasswordUtil
::isBlowfish($this->password
) && CryptoUtil
::secureCompare($this->password
, PasswordUtil
::getSaltedHash($passwordHash, $this->password
))) {
185 * Returns an array with all the groups in which the actual user is a member.
187 * @param boolean $skipCache
190 public function getGroupIDs($skipCache = false) {
191 if ($this->groupIDs
=== null ||
$skipCache) {
192 if (!$this->userID
) {
193 // user is a guest, use default guest group
194 $this->groupIDs
= UserGroup
::getGroupIDsByType([UserGroup
::GUESTS
, UserGroup
::EVERYONE
]);
198 $data = UserStorageHandler
::getInstance()->getField('groupIDs', $this->userID
);
200 // cache does not exist or is outdated
201 if ($data === null ||
$skipCache) {
202 $sql = "SELECT groupID
203 FROM wcf".WCF_N
."_user_to_group
205 $statement = WCF
::getDB()->prepareStatement($sql);
206 $statement->execute([$this->userID
]);
207 $this->groupIDs
= $statement->fetchAll(\PDO
::FETCH_COLUMN
);
209 // update storage data
211 UserStorageHandler
::getInstance()->update($this->userID
, 'groupIDs', serialize($this->groupIDs
));
215 $this->groupIDs
= unserialize($data);
219 sort($this->groupIDs
, SORT_NUMERIC
);
222 return $this->groupIDs
;
226 * Returns a list of language ids for this user.
230 public function getLanguageIDs() {
231 if ($this->languageIDs
=== null) {
232 $this->languageIDs
= [];
236 $data = UserStorageHandler
::getInstance()->getField('languageIDs', $this->userID
);
238 // cache does not exist or is outdated
239 if ($data === null) {
240 $sql = "SELECT languageID
241 FROM wcf".WCF_N
."_user_to_language
243 $statement = WCF
::getDB()->prepareStatement($sql);
244 $statement->execute([$this->userID
]);
245 $this->languageIDs
= $statement->fetchAll(\PDO
::FETCH_COLUMN
);
247 // update storage data
248 UserStorageHandler
::getInstance()->update($this->userID
, 'languageIDs', serialize($this->languageIDs
));
251 $this->languageIDs
= unserialize($data);
254 else if (!WCF
::getSession()->spiderID
) {
255 $this->languageIDs
[] = WCF
::getLanguage()->languageID
;
259 return $this->languageIDs
;
263 * Returns the value of the user option with the given name.
265 * @param string $name user option name
266 * @param boolean $filterDisabled suppress values for disabled options
267 * @return mixed user option value
269 public function getUserOption($name, $filterDisabled = false) {
270 $optionID = self
::getUserOptionID($name);
271 if ($optionID === null) {
274 else if ($filterDisabled && self
::$userOptions[$name]->isDisabled
) {
278 if (!isset($this->data
['userOption'.$optionID])) return null;
279 return $this->data
['userOption'.$optionID];
283 * Fetches all user options from cache.
285 protected static function getUserOptionCache() {
286 self
::$userOptions = UserOptionCacheBuilder
::getInstance()->getData([], 'options');
290 * Returns the id of a user option.
292 * @param string $name
295 public static function getUserOptionID($name) {
296 // get user option cache if necessary
297 if (self
::$userOptions === null) {
298 self
::getUserOptionCache();
301 if (!isset(self
::$userOptions[$name])) {
305 return self
::$userOptions[$name]->optionID
;
311 public function __get($name) {
312 $value = parent
::__get($name);
313 if ($value === null) $value = $this->getUserOption($name);
318 * Returns the user with the given username.
320 * @param string $username
323 public static function getUserByUsername($username) {
324 $sql = "SELECT user_option_value.*, user_table.*
325 FROM wcf".WCF_N
."_user user_table
326 LEFT JOIN wcf".WCF_N
."_user_option_value user_option_value
327 ON (user_option_value.userID = user_table.userID)
328 WHERE user_table.username = ?";
329 $statement = WCF
::getDB()->prepareStatement($sql);
330 $statement->execute([$username]);
331 $row = $statement->fetchArray();
332 if (!$row) $row = [];
334 return new User(null, $row);
338 * Returns the user with the given email.
340 * @param string $email
343 public static function getUserByEmail($email) {
344 $sql = "SELECT user_option_value.*, user_table.*
345 FROM wcf".WCF_N
."_user user_table
346 LEFT JOIN wcf".WCF_N
."_user_option_value user_option_value
347 ON (user_option_value.userID = user_table.userID)
348 WHERE user_table.email = ?";
349 $statement = WCF
::getDB()->prepareStatement($sql);
350 $statement->execute([$email]);
351 $row = $statement->fetchArray();
352 if (!$row) $row = [];
354 return new User(null, $row);
358 * Returns the user with the given authData.
360 * @param string $authData
363 public static function getUserByAuthData($authData) {
364 $sql = "SELECT user_option_value.*, user_table.*
365 FROM wcf".WCF_N
."_user user_table
366 LEFT JOIN wcf".WCF_N
."_user_option_value user_option_value
367 ON (user_option_value.userID = user_table.userID)
368 WHERE user_table.authData = ?";
369 $statement = WCF
::getDB()->prepareStatement($sql);
370 $statement->execute([$authData]);
371 $row = $statement->fetchArray();
372 if (!$row) $row = [];
374 return new User(null, $row);
378 * Returns true if this user is marked.
382 public function isMarked() {
383 $markedUsers = WCF
::getSession()->getVar('markedUsers');
384 if ($markedUsers !== null) {
385 if (in_array($this->userID
, $markedUsers)) return 1;
392 * Returns the time zone of this user.
394 * @return \DateTimeZone
396 public function getTimeZone() {
397 if ($this->timezoneObj
=== null) {
398 if ($this->timezone
) {
399 $this->timezoneObj
= new \
DateTimeZone($this->timezone
);
402 $this->timezoneObj
= new \
DateTimeZone(TIMEZONE
);
406 return $this->timezoneObj
;
410 * Returns a list of users.
412 * @param array $userIDs
415 public static function getUsers(array $userIDs) {
416 $userList = new UserList();
417 $userList->setObjectIDs($userIDs);
418 $userList->readObjects();
420 return $userList->getObjects();
428 public function __toString() {
429 return ($this->username ?
: '');
435 public static function getDatabaseTableAlias() {
442 public function getTitle() {
443 return $this->username
;
447 * Returns the language of this user.
451 public function getLanguage() {
452 $language = LanguageFactory
::getInstance()->getLanguage($this->languageID
);
453 if ($language === null) {
454 $language = LanguageFactory
::getInstance()->getLanguage(LanguageFactory
::getInstance()->getDefaultLanguageID());
461 * Returns true if the active user can edit this user.
465 public function canEdit() {
466 return (WCF
::getSession()->getPermission('admin.user.canEditUser') && UserGroup
::isAccessibleGroup($this->getGroupIDs()));
470 * Returns true, if this user has access to the ACP.
474 public function hasAdministrativeAccess() {
475 if ($this->hasAdministrativePermissions
=== null) {
476 $this->hasAdministrativePermissions
= false;
479 foreach ($this->getGroupIDs() as $groupID) {
480 $group = UserGroup
::getGroupByID($groupID);
481 if ($group->isAdminGroup()) {
482 $this->hasAdministrativePermissions
= true;
489 return $this->hasAdministrativePermissions
;
495 public function getUserID() {
496 return $this->userID
;
502 public function getUsername() {
503 return $this->username
;
509 public function getTime() {
510 return $this->registrationDate
;
516 public function getLink() {
517 return LinkHandler
::getInstance()->getLink('User', [
518 'application' => 'wcf',
520 'forceFrontend' => true
525 * Returns the social network privacy settings of the user.
530 public function getSocialNetworkPrivacySettings() {
540 * Returns the registration ip address, attempts to convert to IPv4.
544 public function getRegistrationIpAddress() {
545 if ($this->registrationIpAddress
) {
546 return UserUtil
::convertIPv6To4($this->registrationIpAddress
);