Merge branch '2.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / user / User.class.php
1 <?php
2 namespace wcf\data\user;
3 use wcf\data\user\group\UserGroup;
4 use wcf\data\user\UserList;
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-2014 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 final class User extends DatabaseObject implements IRouteController, IUserContent {
26 /**
27 * @see \wcf\data\DatabaseObject::$databaseTableName
28 */
29 protected static $databaseTableName = 'user';
30
31 /**
32 * @see \wcf\data\DatabaseObject::$databaseTableIndexName
33 */
34 protected static $databaseTableIndexName = 'userID';
35
36 /**
37 * list of group ids
38 * @var array<integer>
39 */
40 protected $groupIDs = null;
41
42 /**
43 * true, if user has access to the ACP
44 * @var boolean
45 */
46 protected $hasAdministrativePermissions = null;
47
48 /**
49 * list of language ids
50 * @var array<integer>
51 */
52 protected $languageIDs = null;
53
54 /**
55 * date time zone object
56 * @var DateTimeZone
57 */
58 protected $timezoneObj = null;
59
60 /**
61 * list of user options
62 * @var array<string>
63 */
64 protected static $userOptions = null;
65
66 /**
67 * @see \wcf\data\DatabaseObject::__construct()
68 */
69 public function __construct($id, $row = null, DatabaseObject $object = null) {
70 if ($id !== null) {
71 $sql = "SELECT user_option_value.*, user_table.*
72 FROM wcf".WCF_N."_user user_table
73 LEFT JOIN wcf".WCF_N."_user_option_value user_option_value
74 ON (user_option_value.userID = user_table.userID)
75 WHERE user_table.userID = ?";
76 $statement = WCF::getDB()->prepareStatement($sql);
77 $statement->execute(array($id));
78 $row = $statement->fetchArray();
79
80 // enforce data type 'array'
81 if ($row === false) $row = array();
82 }
83 else if ($object !== null) {
84 $row = $object->data;
85 }
86
87 $this->handleData($row);
88 }
89
90 /**
91 * Returns true if the given password is the correct password for this user.
92 *
93 * @param string $password
94 * @return boolean password correct
95 */
96 public function checkPassword($password) {
97 $isValid = false;
98 $rebuild = false;
99
100 // check if password is a valid bcrypt hash
101 if (PasswordUtil::isBlowfish($this->password)) {
102 if (PasswordUtil::isDifferentBlowfish($this->password)) {
103 $rebuild = true;
104 }
105
106 // password is correct
107 if (PasswordUtil::secureCompare($this->password, PasswordUtil::getDoubleSaltedHash($password, $this->password))) {
108 $isValid = true;
109 }
110 }
111 else {
112 // different encryption type
113 if (PasswordUtil::checkPassword($this->username, $password, $this->password)) {
114 $isValid = true;
115 $rebuild = true;
116 }
117 }
118
119 // create new password hash, either different encryption or different blowfish cost factor
120 if ($rebuild && $isValid) {
121 $userEditor = new UserEditor($this);
122 $userEditor->update(array(
123 'password' => $password
124 ));
125 }
126
127 return $isValid;
128 }
129
130 /**
131 * Returns true if the given password hash from a cookie is the correct password for this user.
132 *
133 * @param string $passwordHash
134 * @return boolean password correct
135 */
136 public function checkCookiePassword($passwordHash) {
137 if (PasswordUtil::isBlowfish($this->password) && PasswordUtil::secureCompare($this->password, PasswordUtil::getSaltedHash($passwordHash, $this->password))) {
138 return true;
139 }
140
141 return false;
142 }
143
144 /**
145 * Returns an array with all the groups in which the actual user is a member.
146 *
147 * @param boolean $skipCache
148 * @return array $groupIDs
149 */
150 public function getGroupIDs($skipCache = false) {
151 if ($this->groupIDs === null || $skipCache) {
152 if (!$this->userID) {
153 // user is a guest, use default guest group
154 $this->groupIDs = UserGroup::getGroupIDsByType(array(UserGroup::GUESTS, UserGroup::EVERYONE));
155 }
156 else {
157 // load storage data
158 UserStorageHandler::getInstance()->loadStorage(array($this->userID));
159
160 // get group ids
161 $data = UserStorageHandler::getInstance()->getStorage(array($this->userID), 'groupIDs');
162
163 // cache does not exist or is outdated
164 if ($data[$this->userID] === null || $skipCache) {
165 $this->groupIDs = array();
166 $sql = "SELECT groupID
167 FROM wcf".WCF_N."_user_to_group
168 WHERE userID = ?";
169 $statement = WCF::getDB()->prepareStatement($sql);
170 $statement->execute(array($this->userID));
171 while ($row = $statement->fetchArray()) {
172 $this->groupIDs[] = $row['groupID'];
173 }
174
175 // update storage data
176 if (!$skipCache) {
177 UserStorageHandler::getInstance()->update($this->userID, 'groupIDs', serialize($this->groupIDs));
178 }
179 }
180 else {
181 $this->groupIDs = unserialize($data[$this->userID]);
182 }
183 }
184
185 sort($this->groupIDs, SORT_NUMERIC);
186 }
187
188 return $this->groupIDs;
189 }
190
191 /**
192 * Returns a list of language ids for this user.
193 *
194 * @return array<integer>
195 */
196 public function getLanguageIDs() {
197 if ($this->languageIDs === null) {
198 $this->languageIDs = array();
199
200 if ($this->userID) {
201 // load storage data
202 UserStorageHandler::getInstance()->loadStorage(array($this->userID));
203
204 // get language ids
205 $data = UserStorageHandler::getInstance()->getStorage(array($this->userID), 'languageIDs');
206
207 // cache does not exist or is outdated
208 if ($data[$this->userID] === null) {
209 $sql = "SELECT languageID
210 FROM wcf".WCF_N."_user_to_language
211 WHERE userID = ?";
212 $statement = WCF::getDB()->prepareStatement($sql);
213 $statement->execute(array($this->userID));
214 while ($row = $statement->fetchArray()) {
215 $this->languageIDs[] = $row['languageID'];
216 }
217
218 // update storage data
219 UserStorageHandler::getInstance()->update($this->userID, 'languageIDs', serialize($this->languageIDs));
220 }
221 else {
222 $this->languageIDs = unserialize($data[$this->userID]);
223 }
224 }
225 else if (!WCF::getSession()->spiderID) {
226 $this->languageIDs[] = WCF::getLanguage()->languageID;
227 }
228 }
229
230 return $this->languageIDs;
231 }
232
233 /**
234 * Returns the value of the user option with the given name.
235 *
236 * @param string $name user option name
237 * @return mixed user option value
238 */
239 public function getUserOption($name) {
240 $optionID = self::getUserOptionID($name);
241 if ($optionID === null) {
242 return null;
243 }
244
245 if (!isset($this->data['userOption'.$optionID])) return null;
246 return $this->data['userOption'.$optionID];
247 }
248
249 /**
250 * Gets all user options from cache.
251 */
252 protected static function getUserOptionCache() {
253 self::$userOptions = UserOptionCacheBuilder::getInstance()->getData(array(), 'options');
254 }
255
256 /**
257 * Returns the id of a user option.
258 *
259 * @param string $name
260 * @return integer id
261 */
262 public static function getUserOptionID($name) {
263 // get user option cache if necessary
264 if (self::$userOptions === null) {
265 self::getUserOptionCache();
266 }
267
268 if (!isset(self::$userOptions[$name])) {
269 return null;
270 }
271
272 return self::$userOptions[$name]->optionID;
273 }
274
275 /**
276 * @see \wcf\data\DatabaseObject::__get()
277 */
278 public function __get($name) {
279 $value = parent::__get($name);
280 if ($value === null) $value = $this->getUserOption($name);
281 return $value;
282 }
283
284 /**
285 * Returns the user with the given username.
286 *
287 * @param string $username
288 * @return \wcf\data\user\User
289 */
290 public static function getUserByUsername($username) {
291 $sql = "SELECT *
292 FROM wcf".WCF_N."_user
293 WHERE username = ?";
294 $statement = WCF::getDB()->prepareStatement($sql);
295 $statement->execute(array($username));
296 $row = $statement->fetchArray();
297 if (!$row) $row = array();
298
299 return new User(null, $row);
300 }
301
302 /**
303 * Returns the user with the given email.
304 *
305 * @param string $email
306 * @return \wcf\data\user\User
307 */
308 public static function getUserByEmail($email) {
309 $sql = "SELECT *
310 FROM wcf".WCF_N."_user
311 WHERE email = ?";
312 $statement = WCF::getDB()->prepareStatement($sql);
313 $statement->execute(array($email));
314 $row = $statement->fetchArray();
315 if (!$row) $row = array();
316
317 return new User(null, $row);
318 }
319
320 /**
321 * Returns true if this user is marked.
322 *
323 * @return boolean
324 */
325 public function isMarked() {
326 $markedUsers = WCF::getSession()->getVar('markedUsers');
327 if ($markedUsers !== null) {
328 if (in_array($this->userID, $markedUsers)) return 1;
329 }
330
331 return 0;
332 }
333
334 /**
335 * Returns the time zone of this user.
336 *
337 * @return DateTimeZone
338 */
339 public function getTimeZone() {
340 if ($this->timezoneObj === null) {
341 if ($this->timezone) {
342 $this->timezoneObj = new \DateTimeZone($this->timezone);
343 }
344 else {
345 $this->timezoneObj = new \DateTimeZone(TIMEZONE);
346 }
347 }
348
349 return $this->timezoneObj;
350 }
351
352 /**
353 * Returns a list of users.
354 *
355 * @param array $userIDs
356 * @return array<\wcf\data\user\User>
357 */
358 public static function getUsers(array $userIDs) {
359 $userList = new UserList();
360 $userList->getConditionBuilder()->add("user_table.userID IN (?)", array($userIDs));
361 $userList->readObjects();
362
363 return $userList->getObjects();
364 }
365
366 /**
367 * Returns username.
368 *
369 * @return string
370 */
371 public function __toString() {
372 return $this->username;
373 }
374
375 /**
376 * @see \wcf\data\IStorableObject::getDatabaseTableAlias()
377 */
378 public static function getDatabaseTableAlias() {
379 return 'user_table';
380 }
381
382 /**
383 * @see \wcf\system\request\IRouteController::getTitle()
384 */
385 public function getTitle() {
386 return $this->username;
387 }
388
389 /**
390 * Returns the language of this user.
391 *
392 * @return \wcf\data\language\Language
393 */
394 public function getLanguage() {
395 $language = LanguageFactory::getInstance()->getLanguage($this->languageID);
396 if ($language === null) {
397 $language = LanguageFactory::getInstance()->getLanguage(LanguageFactory::getInstance()->getDefaultLanguageID());
398 }
399
400 return $language;
401 }
402
403 /**
404 * Returns true if the active user can edit this user.
405 *
406 * @return boolean
407 */
408 public function canEdit() {
409 return (WCF::getSession()->getPermission('admin.user.canEditUser') && UserGroup::isAccessibleGroup($this->getGroupIDs()));
410 }
411
412 /**
413 * Returns true, if this user has access to the ACP.
414 *
415 * @return boolean
416 */
417 public function hasAdministrativeAccess() {
418 if ($this->hasAdministrativePermissions === null) {
419 $this->hasAdministrativePermissions = false;
420
421 if ($this->userID) {
422 foreach ($this->getGroupIDs() as $groupID) {
423 $group = UserGroup::getGroupByID($groupID);
424 if ($group->isAdminGroup()) {
425 $this->hasAdministrativePermissions = true;
426 break;
427 }
428 }
429 }
430 }
431
432 return $this->hasAdministrativePermissions;
433 }
434
435 /**
436 * @see \wcf\data\IMessage::getUserID()
437 */
438 public function getUserID() {
439 return $this->userID;
440 }
441
442 /**
443 * @see \wcf\data\IMessage::getUsername()
444 */
445 public function getUsername() {
446 return $this->username;
447 }
448
449 /**
450 * @see \wcf\data\IMessage::getTime()
451 */
452 public function getTime() {
453 return $this->registrationDate;
454 }
455
456 /**
457 * @see \wcf\data\ILinkableObject::getLink()
458 */
459 public function getLink() {
460 return LinkHandler::getInstance()->getLink('User', array(
461 'application' => 'wcf',
462 'object' => $this,
463 'forceFrontend' => true
464 ));
465 }
466 }