Use \PDO::fetchAll() instead of PreparedStatement::fetchColumns()
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / user / User.class.php
1 <?php
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;
12 use wcf\system\WCF;
13 use wcf\util\PasswordUtil;
14
15 /**
16 * Represents a user.
17 *
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
24 *
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
68 */
69 final class User extends DatabaseObject implements IRouteController, IUserContent {
70 /**
71 * @inheritDoc
72 */
73 protected static $databaseTableName = 'user';
74
75 /**
76 * @inheritDoc
77 */
78 protected static $databaseTableIndexName = 'userID';
79
80 /**
81 * list of group ids
82 * @var integer[]
83 */
84 protected $groupIDs = null;
85
86 /**
87 * true, if user has access to the ACP
88 * @var boolean
89 */
90 protected $hasAdministrativePermissions = null;
91
92 /**
93 * list of language ids
94 * @var integer[]
95 */
96 protected $languageIDs = null;
97
98 /**
99 * date time zone object
100 * @var \DateTimeZone
101 */
102 protected $timezoneObj = null;
103
104 /**
105 * list of user options
106 * @var string[]
107 */
108 protected static $userOptions = null;
109
110 /**
111 * @inheritDoc
112 */
113 public function __construct($id, $row = null, DatabaseObject $object = null) {
114 if ($id !== 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();
123
124 // enforce data type 'array'
125 if ($row === false) $row = [];
126 }
127 else if ($object !== null) {
128 $row = $object->data;
129 }
130
131 $this->handleData($row);
132 }
133
134 /**
135 * Returns true if the given password is the correct password for this user.
136 *
137 * @param string $password
138 * @return boolean password correct
139 */
140 public function checkPassword($password) {
141 $isValid = false;
142 $rebuild = false;
143
144 // check if password is a valid bcrypt hash
145 if (PasswordUtil::isBlowfish($this->password)) {
146 if (PasswordUtil::isDifferentBlowfish($this->password)) {
147 $rebuild = true;
148 }
149
150 // password is correct
151 if (PasswordUtil::secureCompare($this->password, PasswordUtil::getDoubleSaltedHash($password, $this->password))) {
152 $isValid = true;
153 }
154 }
155 else {
156 // different encryption type
157 if (PasswordUtil::checkPassword($this->username, $password, $this->password)) {
158 $isValid = true;
159 $rebuild = true;
160 }
161 }
162
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
168 ]);
169 }
170
171 return $isValid;
172 }
173
174 /**
175 * Returns true if the given password hash from a cookie is the correct password for this user.
176 *
177 * @param string $passwordHash
178 * @return boolean password correct
179 */
180 public function checkCookiePassword($passwordHash) {
181 if (PasswordUtil::isBlowfish($this->password) && PasswordUtil::secureCompare($this->password, PasswordUtil::getSaltedHash($passwordHash, $this->password))) {
182 return true;
183 }
184
185 return false;
186 }
187
188 /**
189 * Returns an array with all the groups in which the actual user is a member.
190 *
191 * @param boolean $skipCache
192 * @return integer[]
193 */
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]);
199 }
200 else {
201 // get group ids
202 $data = UserStorageHandler::getInstance()->getField('groupIDs', $this->userID);
203
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
208 WHERE userID = ?";
209 $statement = WCF::getDB()->prepareStatement($sql);
210 $statement->execute([$this->userID]);
211 $this->groupIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
212
213 // update storage data
214 if (!$skipCache) {
215 UserStorageHandler::getInstance()->update($this->userID, 'groupIDs', serialize($this->groupIDs));
216 }
217 }
218 else {
219 $this->groupIDs = unserialize($data);
220 }
221 }
222
223 sort($this->groupIDs, SORT_NUMERIC);
224 }
225
226 return $this->groupIDs;
227 }
228
229 /**
230 * Returns a list of language ids for this user.
231 *
232 * @return integer[]
233 */
234 public function getLanguageIDs() {
235 if ($this->languageIDs === null) {
236 $this->languageIDs = [];
237
238 if ($this->userID) {
239 // get language ids
240 $data = UserStorageHandler::getInstance()->getField('languageIDs', $this->userID);
241
242 // cache does not exist or is outdated
243 if ($data === null) {
244 $sql = "SELECT languageID
245 FROM wcf".WCF_N."_user_to_language
246 WHERE userID = ?";
247 $statement = WCF::getDB()->prepareStatement($sql);
248 $statement->execute([$this->userID]);
249 $this->languageIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
250
251 // update storage data
252 UserStorageHandler::getInstance()->update($this->userID, 'languageIDs', serialize($this->languageIDs));
253 }
254 else {
255 $this->languageIDs = unserialize($data);
256 }
257 }
258 else if (!WCF::getSession()->spiderID) {
259 $this->languageIDs[] = WCF::getLanguage()->languageID;
260 }
261 }
262
263 return $this->languageIDs;
264 }
265
266 /**
267 * Returns the value of the user option with the given name.
268 *
269 * @param string $name user option name
270 * @return mixed user option value
271 */
272 public function getUserOption($name) {
273 $optionID = self::getUserOptionID($name);
274 if ($optionID === null) {
275 return null;
276 }
277
278 if (!isset($this->data['userOption'.$optionID])) return null;
279 return $this->data['userOption'.$optionID];
280 }
281
282 /**
283 * Gets all user options from cache.
284 */
285 protected static function getUserOptionCache() {
286 self::$userOptions = UserOptionCacheBuilder::getInstance()->getData([], 'options');
287 }
288
289 /**
290 * Returns the id of a user option.
291 *
292 * @param string $name
293 * @return integer id
294 */
295 public static function getUserOptionID($name) {
296 // get user option cache if necessary
297 if (self::$userOptions === null) {
298 self::getUserOptionCache();
299 }
300
301 if (!isset(self::$userOptions[$name])) {
302 return null;
303 }
304
305 return self::$userOptions[$name]->optionID;
306 }
307
308 /**
309 * @inheritDoc
310 */
311 public function __get($name) {
312 $value = parent::__get($name);
313 if ($value === null) $value = $this->getUserOption($name);
314 return $value;
315 }
316
317 /**
318 * Returns the user with the given username.
319 *
320 * @param string $username
321 * @return User
322 */
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 = [];
333
334 return new User(null, $row);
335 }
336
337 /**
338 * Returns the user with the given email.
339 *
340 * @param string $email
341 * @return User
342 */
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 = [];
353
354 return new User(null, $row);
355 }
356
357 /**
358 * Returns the user with the given authData.
359 *
360 * @param string $authData
361 * @return User
362 */
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 = [];
373
374 return new User(null, $row);
375 }
376
377 /**
378 * Returns true if this user is marked.
379 *
380 * @return boolean
381 */
382 public function isMarked() {
383 $markedUsers = WCF::getSession()->getVar('markedUsers');
384 if ($markedUsers !== null) {
385 if (in_array($this->userID, $markedUsers)) return 1;
386 }
387
388 return 0;
389 }
390
391 /**
392 * Returns the time zone of this user.
393 *
394 * @return \DateTimeZone
395 */
396 public function getTimeZone() {
397 if ($this->timezoneObj === null) {
398 if ($this->timezone) {
399 $this->timezoneObj = new \DateTimeZone($this->timezone);
400 }
401 else {
402 $this->timezoneObj = new \DateTimeZone(TIMEZONE);
403 }
404 }
405
406 return $this->timezoneObj;
407 }
408
409 /**
410 * Returns a list of users.
411 *
412 * @param array $userIDs
413 * @return User[]
414 */
415 public static function getUsers(array $userIDs) {
416 $userList = new UserList();
417 $userList->setObjectIDs($userIDs);
418 $userList->readObjects();
419
420 return $userList->getObjects();
421 }
422
423 /**
424 * Returns username.
425 *
426 * @return string
427 */
428 public function __toString() {
429 return $this->username;
430 }
431
432 /**
433 * @inheritDoc
434 */
435 public static function getDatabaseTableAlias() {
436 return 'user_table';
437 }
438
439 /**
440 * @inheritDoc
441 */
442 public function getTitle() {
443 return $this->username;
444 }
445
446 /**
447 * Returns the language of this user.
448 *
449 * @return Language
450 */
451 public function getLanguage() {
452 $language = LanguageFactory::getInstance()->getLanguage($this->languageID);
453 if ($language === null) {
454 $language = LanguageFactory::getInstance()->getLanguage(LanguageFactory::getInstance()->getDefaultLanguageID());
455 }
456
457 return $language;
458 }
459
460 /**
461 * Returns true if the active user can edit this user.
462 *
463 * @return boolean
464 */
465 public function canEdit() {
466 return (WCF::getSession()->getPermission('admin.user.canEditUser') && UserGroup::isAccessibleGroup($this->getGroupIDs()));
467 }
468
469 /**
470 * Returns true, if this user has access to the ACP.
471 *
472 * @return boolean
473 */
474 public function hasAdministrativeAccess() {
475 if ($this->hasAdministrativePermissions === null) {
476 $this->hasAdministrativePermissions = false;
477
478 if ($this->userID) {
479 foreach ($this->getGroupIDs() as $groupID) {
480 $group = UserGroup::getGroupByID($groupID);
481 if ($group->isAdminGroup()) {
482 $this->hasAdministrativePermissions = true;
483 break;
484 }
485 }
486 }
487 }
488
489 return $this->hasAdministrativePermissions;
490 }
491
492 /**
493 * @inheritDoc
494 */
495 public function getUserID() {
496 return $this->userID;
497 }
498
499 /**
500 * @inheritDoc
501 */
502 public function getUsername() {
503 return $this->username;
504 }
505
506 /**
507 * @inheritDoc
508 */
509 public function getTime() {
510 return $this->registrationDate;
511 }
512
513 /**
514 * @inheritDoc
515 */
516 public function getLink() {
517 return LinkHandler::getInstance()->getLink('User', [
518 'application' => 'wcf',
519 'object' => $this,
520 'forceFrontend' => true
521 ]);
522 }
523
524 /**
525 * Returns the social network privacy settings of the user.
526 *
527 * @return boolean[]
528 */
529 public function getSocialNetworkPrivacySettings() {
530 $settings = false;
531 if ($this->userID && WCF::getUser()->socialNetworkPrivacySettings) {
532 $settings = @unserialize(WCF::getUser()->socialNetworkPrivacySettings);
533 }
534
535 if ($settings === false) {
536 $settings = [
537 'facebook' => false,
538 'google' => false,
539 'reddit' => false,
540 'twitter' => false
541 ];
542 }
543
544 return $settings;
545 }
546 }