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\system\cache\builder\UserOptionCacheBuilder
;
8 use wcf\system\language\LanguageFactory
;
9 use wcf\system\request\IRouteController
;
10 use wcf\system\request\LinkHandler
;
11 use wcf\system\user\storage\UserStorageHandler
;
13 use wcf\util\PasswordUtil
;
18 * @author Alexander Ebert
19 * @copyright 2001-2016 WoltLab GmbH
20 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
21 * @package com.woltlab.wcf
22 * @subpackage data.user
23 * @category Community Framework
25 * @property-read integer $userID
26 * @property-read string $username
27 * @property-read string $email
28 * @property-read string $password
29 * @property-read string $accessToken
30 * @property-read integer $languageID
31 * @property-read string $registrationDate
32 * @property-read integer $styleID
33 * @property-read integer $banned
34 * @property-read string $banReason
35 * @property-read integer $banExpires
36 * @property-read integer $activationCode
37 * @property-read integer $lastLostPasswordRequestTime
38 * @property-read string $lostPasswordKey
39 * @property-read integer $lastUsernameChange
40 * @property-read string $newEmail
41 * @property-read string $oldUsername
42 * @property-read integer $quitStarted
43 * @property-read integer $reactivationCode
44 * @property-read string $registrationIpAddress
45 * @property-read integer|null $avatarID
46 * @property-read integer $disableAvatar
47 * @property-read string $disableAvatarReason
48 * @property-read integer $disableAvatarExpires
49 * @property-read integer $enableGravatar
50 * @property-read string $gravatarFileExtension
51 * @property-read string $signature
52 * @property-read integer $signatureEnableBBCodes
53 * @property-read integer $signatureEnableHtml
54 * @property-read integer $signatureEnableSmilies
55 * @property-read integer $disableSignature
56 * @property-read string $disableSignatureReason
57 * @property-read integer $disableSignatureExpires
58 * @property-read integer $lastActivityTime
59 * @property-read integer $profileHits
60 * @property-read integer|null $rankID
61 * @property-read string $userTitle
62 * @property-read integer|null $userOnlineGroupID
63 * @property-read integer $activityPoints
64 * @property-read string $notificationMailToken
65 * @property-read string $authData
66 * @property-read integer $likesReceived
67 * @property-read string $socialNetworkPrivacySettings
69 final class User
extends DatabaseObject
implements IRouteController
, IUserContent
{
73 protected static $databaseTableName = 'user';
78 protected static $databaseTableIndexName = 'userID';
84 protected $groupIDs = null;
87 * true, if user has access to the ACP
90 protected $hasAdministrativePermissions = null;
93 * list of language ids
96 protected $languageIDs = null;
99 * date time zone object
102 protected $timezoneObj = null;
105 * list of user options
108 protected static $userOptions = null;
113 public function __construct($id, $row = null, DatabaseObject
$object = null) {
115 $sql = "SELECT user_option_value.*, user_table.*
116 FROM wcf".WCF_N
."_user user_table
117 LEFT JOIN wcf".WCF_N
."_user_option_value user_option_value
118 ON (user_option_value.userID = user_table.userID)
119 WHERE user_table.userID = ?";
120 $statement = WCF
::getDB()->prepareStatement($sql);
121 $statement->execute([$id]);
122 $row = $statement->fetchArray();
124 // enforce data type 'array'
125 if ($row === false) $row = [];
127 else if ($object !== null) {
128 $row = $object->data
;
131 $this->handleData($row);
135 * Returns true if the given password is the correct password for this user.
137 * @param string $password
138 * @return boolean password correct
140 public function checkPassword($password) {
144 // check if password is a valid bcrypt hash
145 if (PasswordUtil
::isBlowfish($this->password
)) {
146 if (PasswordUtil
::isDifferentBlowfish($this->password
)) {
150 // password is correct
151 if (PasswordUtil
::secureCompare($this->password
, PasswordUtil
::getDoubleSaltedHash($password, $this->password
))) {
156 // different encryption type
157 if (PasswordUtil
::checkPassword($this->username
, $password, $this->password
)) {
163 // create new password hash, either different encryption or different blowfish cost factor
164 if ($rebuild && $isValid) {
165 $userEditor = new UserEditor($this);
166 $userEditor->update([
167 'password' => $password
175 * Returns true if the given password hash from a cookie is the correct password for this user.
177 * @param string $passwordHash
178 * @return boolean password correct
180 public function checkCookiePassword($passwordHash) {
181 if (PasswordUtil
::isBlowfish($this->password
) && PasswordUtil
::secureCompare($this->password
, PasswordUtil
::getSaltedHash($passwordHash, $this->password
))) {
189 * Returns an array with all the groups in which the actual user is a member.
191 * @param boolean $skipCache
194 public function getGroupIDs($skipCache = false) {
195 if ($this->groupIDs
=== null ||
$skipCache) {
196 if (!$this->userID
) {
197 // user is a guest, use default guest group
198 $this->groupIDs
= UserGroup
::getGroupIDsByType([UserGroup
::GUESTS
, UserGroup
::EVERYONE
]);
202 $data = UserStorageHandler
::getInstance()->getField('groupIDs', $this->userID
);
204 // cache does not exist or is outdated
205 if ($data === null ||
$skipCache) {
206 $sql = "SELECT groupID
207 FROM wcf".WCF_N
."_user_to_group
209 $statement = WCF
::getDB()->prepareStatement($sql);
210 $statement->execute([$this->userID
]);
211 $this->groupIDs
= $statement->fetchAll(\PDO
::FETCH_COLUMN
);
213 // update storage data
215 UserStorageHandler
::getInstance()->update($this->userID
, 'groupIDs', serialize($this->groupIDs
));
219 $this->groupIDs
= unserialize($data);
223 sort($this->groupIDs
, SORT_NUMERIC
);
226 return $this->groupIDs
;
230 * Returns a list of language ids for this user.
234 public function getLanguageIDs() {
235 if ($this->languageIDs
=== null) {
236 $this->languageIDs
= [];
240 $data = UserStorageHandler
::getInstance()->getField('languageIDs', $this->userID
);
242 // cache does not exist or is outdated
243 if ($data === null) {
244 $sql = "SELECT languageID
245 FROM wcf".WCF_N
."_user_to_language
247 $statement = WCF
::getDB()->prepareStatement($sql);
248 $statement->execute([$this->userID
]);
249 $this->languageIDs
= $statement->fetchAll(\PDO
::FETCH_COLUMN
);
251 // update storage data
252 UserStorageHandler
::getInstance()->update($this->userID
, 'languageIDs', serialize($this->languageIDs
));
255 $this->languageIDs
= unserialize($data);
258 else if (!WCF
::getSession()->spiderID
) {
259 $this->languageIDs
[] = WCF
::getLanguage()->languageID
;
263 return $this->languageIDs
;
267 * Returns the value of the user option with the given name.
269 * @param string $name user option name
270 * @return mixed user option value
272 public function getUserOption($name) {
273 $optionID = self
::getUserOptionID($name);
274 if ($optionID === null) {
278 if (!isset($this->data
['userOption'.$optionID])) return null;
279 return $this->data
['userOption'.$optionID];
283 * Gets 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.
529 public function getSocialNetworkPrivacySettings() {
531 if ($this->userID
&& WCF
::getUser()->socialNetworkPrivacySettings
) {
532 $settings = @unserialize
(WCF
::getUser()->socialNetworkPrivacySettings
);
535 if ($settings === false) {