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