Commit | Line | Data |
---|---|---|
11ade432 | 1 | <?php |
a9229942 | 2 | |
11ade432 | 3 | namespace wcf\data\user; |
a9229942 TD |
4 | |
5 | use wcf\data\DatabaseObject; | |
4132baef | 6 | use wcf\data\IPopoverObject; |
a9229942 | 7 | use wcf\data\IUserContent; |
7a23a706 | 8 | use wcf\data\language\Language; |
ec1b1daf | 9 | use wcf\data\user\group\UserGroup; |
ef37949e | 10 | use wcf\data\user\option\UserOption; |
b401cd0d | 11 | use wcf\system\cache\builder\UserOptionCacheBuilder; |
0d30adfb | 12 | use wcf\system\language\LanguageFactory; |
0602bb11 | 13 | use wcf\system\request\IRouteController; |
647741fd | 14 | use wcf\system\request\LinkHandler; |
4a2e041c | 15 | use wcf\system\user\authentication\password\algorithm\DoubleBcrypt; |
6724430f | 16 | use wcf\system\user\authentication\password\PasswordAlgorithmManager; |
c96ee721 | 17 | use wcf\system\user\storage\UserStorageHandler; |
2bc9f31d | 18 | use wcf\system\WCF; |
41be0d84 | 19 | use wcf\util\JSON; |
e904f3dc | 20 | use wcf\util\UserUtil; |
11ade432 AE |
21 | |
22 | /** | |
23 | * Represents a user. | |
a9229942 TD |
24 | * |
25 | * @author Alexander Ebert | |
26 | * @copyright 2001-2020 WoltLab GmbH | |
27 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
a9229942 TD |
28 | * |
29 | * @property-read int $userID unique id of the user | |
30 | * @property-read string $username name of the user | |
31 | * @property-read string $email email address of the user | |
32 | * @property-read string $password double salted hash of the user's password | |
33 | * @property-read string $accessToken token used for access authentication, for example used by feed pages | |
34 | * @property-read int $languageID id of the interface language used by the user | |
35 | * @property-read int $registrationDate timestamp at which the user has registered/has been created | |
36 | * @property-read int $styleID id of the style used by the user | |
37 | * @property-read int $banned is `1` if the user is banned, otherwise `0` | |
38 | * @property-read string $banReason reason why the user is banned | |
39 | * @property-read int $banExpires timestamp at which the banned user is automatically unbanned | |
40 | * @property-read int $activationCode flag which determines, whether the user is activated (for legacy reasons an random integer, if the user is *not* activated) | |
41 | * @property-read string $emailConfirmed code sent to the user's email address used for account activation or null if the email is confirmed | |
42 | * @property-read int $lastLostPasswordRequestTime timestamp at which the user has reported that they lost their password or 0 if password has not been reported as lost | |
43 | * @property-read string $lostPasswordKey code used for authenticating setting new password after password loss or empty if password has not been reported as lost | |
44 | * @property-read int $lastUsernameChange timestamp at which the user changed their name the last time or 0 if username has not been changed | |
45 | * @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 | |
46 | * @property-read string $oldUsername previous name of the user or empty if they have had no previous name | |
47 | * @property-read int $quitStarted timestamp at which the user terminated their account | |
48 | * @property-read int $reactivationCode code used for authenticating setting new email address or empty if no new email address has been set | |
49 | * @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 | |
50 | * @property-read int|null $avatarID id of the user's avatar or null if they have no avatar | |
51 | * @property-read int $disableAvatar is `1` if the user's avatar has been disabled, otherwise `0` | |
52 | * @property-read string $disableAvatarReason reason why the user's avatar is disabled | |
53 | * @property-read int $disableAvatarExpires timestamp at which the user's avatar will automatically be enabled again | |
a9229942 TD |
54 | * @property-read string $signature text of the user's signature |
55 | * @property-read int $signatureEnableHtml is `1` if HTML will rendered in the user's signature, otherwise `0` | |
56 | * @property-read int $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 int $disableSignatureExpires timestamp at which the user's signature will automatically be enabled again | |
59 | * @property-read int $lastActivityTime timestamp of the user's last activity | |
60 | * @property-read int $profileHits number of times the user's profile has been visited | |
61 | * @property-read int|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 int|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 int $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 int $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 | |
d4cf0997 | 70 | * @property-read int $coverPhotoHasWebP is `1` if a webp variant of the cover photo and its thumbnail exists, otherwise `0` |
a9229942 TD |
71 | * @property-read int $disableCoverPhoto is `1` if the user's cover photo has been disabled, otherwise `0` |
72 | * @property-read string $disableCoverPhotoReason reason why the user's cover photo is disabled | |
73 | * @property-read int $disableCoverPhotoExpires timestamp at which the user's cover photo will automatically be enabled again | |
74 | * @property-read int $articles number of articles written by the user | |
75 | * @property-read string $blacklistMatches JSON string of an array with all matches in the blacklist, otherwise an empty string | |
76 | * @property-read int $multifactorActive is `1` if the use has enabled a second factor, otherwise `0` | |
f23c1c22 | 77 | * @property-read int $trophyPoints total number of user's trophies in active categories |
11ade432 | 78 | */ |
a9229942 TD |
79 | final class User extends DatabaseObject implements IPopoverObject, IRouteController, IUserContent |
80 | { | |
81 | /** | |
82 | * list of group ids | |
83 | * @var int[] | |
84 | */ | |
85 | protected $groupIDs; | |
86 | ||
87 | /** | |
88 | * true, if user has access to the ACP | |
89 | * @var bool | |
90 | */ | |
91 | protected $hasAdministrativePermissions; | |
92 | ||
93 | /** | |
94 | * list of language ids | |
95 | * @var int[] | |
96 | */ | |
97 | protected $languageIDs; | |
98 | ||
99 | /** | |
100 | * date time zone object | |
101 | * @var \DateTimeZone | |
102 | */ | |
103 | protected $timezoneObj; | |
104 | ||
105 | /** | |
106 | * list of user options | |
107 | * @var UserOption[] | |
108 | */ | |
109 | protected static $userOptions; | |
110 | ||
111 | const REGISTER_ACTIVATION_NONE = 0; | |
112 | ||
113 | const REGISTER_ACTIVATION_USER = 1; | |
114 | ||
115 | const REGISTER_ACTIVATION_ADMIN = 2; | |
116 | ||
117 | const REGISTER_ACTIVATION_USER_AND_ADMIN = self::REGISTER_ACTIVATION_USER | self::REGISTER_ACTIVATION_ADMIN; | |
118 | ||
119 | /** @noinspection PhpMissingParentConstructorInspection */ | |
120 | ||
121 | /** | |
122 | * @inheritDoc | |
123 | */ | |
124 | public function __construct($id, $row = null, ?DatabaseObject $object = null) | |
125 | { | |
126 | if ($id !== null) { | |
127 | $sql = "SELECT user_option_value.*, user_table.* | |
128 | FROM wcf" . WCF_N . "_user user_table | |
129 | LEFT JOIN wcf" . WCF_N . "_user_option_value user_option_value | |
c240c98a | 130 | ON user_option_value.userID = user_table.userID |
a9229942 TD |
131 | WHERE user_table.userID = ?"; |
132 | $statement = WCF::getDB()->prepareStatement($sql); | |
133 | $statement->execute([$id]); | |
134 | $row = $statement->fetchArray(); | |
135 | ||
136 | // enforce data type 'array' | |
137 | if ($row === false) { | |
138 | $row = []; | |
139 | } | |
140 | } elseif ($object !== null) { | |
141 | $row = $object->data; | |
142 | } | |
143 | ||
144 | $this->handleData($row); | |
145 | } | |
146 | ||
147 | /** | |
148 | * Returns true if the given password is the correct password for this user. | |
149 | * | |
150 | * @param string $password | |
151 | * @return bool password correct | |
152 | */ | |
2dcb1b34 | 153 | public function checkPassword( |
ef0cb290 | 154 | #[\SensitiveParameter] |
2dcb1b34 TD |
155 | $password |
156 | ) { | |
a9229942 TD |
157 | $isValid = false; |
158 | ||
159 | $manager = PasswordAlgorithmManager::getInstance(); | |
160 | ||
161 | // Compatibility for WoltLab Suite < 5.4. | |
162 | if (DoubleBcrypt::isLegacyDoubleBcrypt($this->password)) { | |
163 | $algorithmName = 'DoubleBcrypt'; | |
164 | $hash = $this->password; | |
165 | } else { | |
166 | [$algorithmName, $hash] = \explode(':', $this->password, 2); | |
167 | } | |
168 | ||
169 | $algorithm = $manager->getAlgorithmFromName($algorithmName); | |
170 | ||
171 | $isValid = $algorithm->verify($password, $hash); | |
172 | ||
173 | if (!$isValid) { | |
174 | return false; | |
175 | } | |
176 | ||
177 | $defaultAlgorithm = $manager->getDefaultAlgorithm(); | |
178 | if (\get_class($algorithm) !== \get_class($defaultAlgorithm) || $algorithm->needsRehash($hash)) { | |
179 | $userEditor = new UserEditor($this); | |
180 | $userEditor->update([ | |
181 | 'password' => $password, | |
182 | ]); | |
183 | } | |
184 | ||
185 | // $isValid is always true at this point. However we intentionally use a variable | |
186 | // that defaults to false to prevent accidents during refactoring. | |
187 | \assert($isValid); | |
188 | ||
189 | return $isValid; | |
190 | } | |
191 | ||
192 | /** | |
193 | * @deprecated 5.4 - This method always returns false, as user sessions are long-lived now. | |
194 | */ | |
195 | public function checkCookiePassword($passwordHash) | |
196 | { | |
197 | return false; | |
198 | } | |
199 | ||
200 | /** | |
201 | * Returns an array with all the groups in which the actual user is a member. | |
202 | * | |
203 | * @param bool $skipCache | |
204 | * @return int[] | |
205 | */ | |
206 | public function getGroupIDs($skipCache = false) | |
207 | { | |
208 | if ($this->groupIDs === null || $skipCache) { | |
209 | if (!$this->userID) { | |
210 | // user is a guest, use default guest group | |
211 | $this->groupIDs = UserGroup::getGroupIDsByType([UserGroup::GUESTS, UserGroup::EVERYONE]); | |
212 | } else { | |
213 | // get group ids | |
214 | $data = UserStorageHandler::getInstance()->getField('groupIDs', $this->userID); | |
215 | ||
216 | // cache does not exist or is outdated | |
217 | if ($data === null || $skipCache) { | |
218 | $sql = "SELECT groupID | |
219 | FROM wcf" . WCF_N . "_user_to_group | |
220 | WHERE userID = ?"; | |
221 | $statement = WCF::getDB()->prepareStatement($sql); | |
222 | $statement->execute([$this->userID]); | |
223 | $this->groupIDs = $statement->fetchAll(\PDO::FETCH_COLUMN); | |
224 | ||
225 | // update storage data | |
226 | if (!$skipCache) { | |
227 | UserStorageHandler::getInstance()->update( | |
228 | $this->userID, | |
229 | 'groupIDs', | |
230 | \serialize($this->groupIDs) | |
231 | ); | |
232 | } | |
233 | } else { | |
234 | $this->groupIDs = \unserialize($data); | |
235 | } | |
236 | } | |
237 | ||
238 | \sort($this->groupIDs, \SORT_NUMERIC); | |
239 | } | |
240 | ||
241 | return $this->groupIDs; | |
242 | } | |
243 | ||
244 | /** | |
245 | * Returns a list of language ids for this user. | |
246 | * | |
247 | * @return int[] | |
248 | */ | |
249 | public function getLanguageIDs() | |
250 | { | |
251 | if ($this->languageIDs === null) { | |
252 | $this->languageIDs = []; | |
253 | ||
254 | if ($this->userID) { | |
255 | // get language ids | |
256 | $data = UserStorageHandler::getInstance()->getField('languageIDs', $this->userID); | |
257 | ||
258 | // cache does not exist or is outdated | |
259 | if ($data === null) { | |
260 | $sql = "SELECT languageID | |
261 | FROM wcf" . WCF_N . "_user_to_language | |
262 | WHERE userID = ?"; | |
263 | $statement = WCF::getDB()->prepareStatement($sql); | |
264 | $statement->execute([$this->userID]); | |
265 | $this->languageIDs = $statement->fetchAll(\PDO::FETCH_COLUMN); | |
266 | ||
267 | // update storage data | |
268 | UserStorageHandler::getInstance()->update( | |
269 | $this->userID, | |
270 | 'languageIDs', | |
271 | \serialize($this->languageIDs) | |
272 | ); | |
273 | } else { | |
274 | $this->languageIDs = \unserialize($data); | |
275 | } | |
c862f249 MW |
276 | } else { |
277 | $this->languageIDs = LanguageFactory::getInstance()->getContentLanguageIDs(); | |
a9229942 TD |
278 | } |
279 | } | |
280 | ||
281 | return $this->languageIDs; | |
282 | } | |
283 | ||
284 | /** | |
285 | * Returns the value of the user option with the given name. | |
286 | * | |
287 | * @param string $name user option name | |
288 | * @param bool $filterDisabled suppress values for disabled options | |
289 | * @return mixed user option value | |
290 | */ | |
291 | public function getUserOption($name, $filterDisabled = false) | |
292 | { | |
293 | $optionID = self::getUserOptionID($name); | |
294 | if ($optionID === null) { | |
295 | return; | |
296 | } elseif ($filterDisabled && self::$userOptions[$name]->isDisabled) { | |
297 | return; | |
298 | } | |
299 | ||
dee6b280 | 300 | return $this->data['userOption' . $optionID] ?? null; |
a9229942 TD |
301 | } |
302 | ||
303 | /** | |
304 | * Fetches all user options from cache. | |
305 | */ | |
306 | protected static function getUserOptionCache() | |
307 | { | |
308 | self::$userOptions = UserOptionCacheBuilder::getInstance()->getData([], 'options'); | |
309 | } | |
310 | ||
311 | /** | |
312 | * Returns the id of a user option. | |
313 | * | |
314 | * @param string $name | |
c0b28aa2 | 315 | * @return int|null |
a9229942 TD |
316 | */ |
317 | public static function getUserOptionID($name) | |
318 | { | |
319 | // get user option cache if necessary | |
320 | if (self::$userOptions === null) { | |
321 | self::getUserOptionCache(); | |
322 | } | |
323 | ||
324 | if (!isset(self::$userOptions[$name])) { | |
c0b28aa2 | 325 | return null; |
a9229942 TD |
326 | } |
327 | ||
328 | return self::$userOptions[$name]->optionID; | |
329 | } | |
330 | ||
331 | /** | |
332 | * @inheritDoc | |
333 | */ | |
334 | public function __get($name) | |
335 | { | |
53d2a48c | 336 | return $this->data[$name] ?? $this->getUserOption($name); |
a9229942 TD |
337 | } |
338 | ||
339 | /** | |
340 | * Returns the user with the given username. | |
341 | * | |
342 | * @param string $username | |
343 | * @return User | |
344 | */ | |
345 | public static function getUserByUsername($username) | |
346 | { | |
347 | $sql = "SELECT user_option_value.*, user_table.* | |
348 | FROM wcf" . WCF_N . "_user user_table | |
349 | LEFT JOIN wcf" . WCF_N . "_user_option_value user_option_value | |
c240c98a | 350 | ON user_option_value.userID = user_table.userID |
a9229942 TD |
351 | WHERE user_table.username = ?"; |
352 | $statement = WCF::getDB()->prepareStatement($sql); | |
353 | $statement->execute([$username]); | |
354 | $row = $statement->fetchArray(); | |
355 | if (!$row) { | |
356 | $row = []; | |
357 | } | |
358 | ||
359 | return new self(null, $row); | |
360 | } | |
361 | ||
362 | /** | |
363 | * Returns the user with the given email. | |
364 | * | |
365 | * @param string $email | |
366 | * @return User | |
367 | */ | |
368 | public static function getUserByEmail($email) | |
369 | { | |
370 | $sql = "SELECT user_option_value.*, user_table.* | |
371 | FROM wcf" . WCF_N . "_user user_table | |
372 | LEFT JOIN wcf" . WCF_N . "_user_option_value user_option_value | |
c240c98a | 373 | ON user_option_value.userID = user_table.userID |
a9229942 TD |
374 | WHERE user_table.email = ?"; |
375 | $statement = WCF::getDB()->prepareStatement($sql); | |
376 | $statement->execute([$email]); | |
377 | $row = $statement->fetchArray(); | |
378 | if (!$row) { | |
379 | $row = []; | |
380 | } | |
381 | ||
382 | return new self(null, $row); | |
383 | } | |
384 | ||
385 | /** | |
386 | * Returns the user with the given authData. | |
387 | * | |
388 | * @param string $authData | |
389 | * @return User | |
390 | */ | |
391 | public static function getUserByAuthData($authData) | |
392 | { | |
393 | $sql = "SELECT user_option_value.*, user_table.* | |
394 | FROM wcf" . WCF_N . "_user user_table | |
395 | LEFT JOIN wcf" . WCF_N . "_user_option_value user_option_value | |
c240c98a | 396 | ON user_option_value.userID = user_table.userID |
a9229942 TD |
397 | WHERE user_table.authData = ?"; |
398 | $statement = WCF::getDB()->prepareStatement($sql); | |
399 | $statement->execute([$authData]); | |
400 | $row = $statement->fetchArray(); | |
401 | if (!$row) { | |
402 | $row = []; | |
403 | } | |
404 | ||
405 | return new self(null, $row); | |
406 | } | |
407 | ||
408 | /** | |
409 | * Returns 3rd party auth provider name. | |
410 | * | |
411 | * @return string | |
412 | * @since 5.2 | |
413 | */ | |
414 | public function getAuthProvider() | |
415 | { | |
416 | if (!$this->authData) { | |
417 | return ''; | |
418 | } | |
419 | ||
420 | return \mb_substr($this->authData, 0, \mb_strpos($this->authData, ':')); | |
421 | } | |
422 | ||
423 | /** | |
424 | * Returns true if this user is marked. | |
425 | * | |
426 | * @return bool | |
427 | */ | |
428 | public function isMarked() | |
429 | { | |
430 | $markedUsers = WCF::getSession()->getVar('markedUsers'); | |
431 | if ($markedUsers !== null) { | |
432 | if (\in_array($this->userID, $markedUsers)) { | |
433 | return 1; | |
434 | } | |
435 | } | |
436 | ||
437 | return 0; | |
438 | } | |
439 | ||
440 | /** | |
441 | * Returns true if the email is confirmed. | |
442 | * | |
443 | * @return bool | |
444 | * @since 5.3 | |
445 | */ | |
446 | public function isEmailConfirmed() | |
447 | { | |
448 | return $this->emailConfirmed === null; | |
449 | } | |
450 | ||
451 | /** | |
452 | * Returns the time zone of this user. | |
453 | * | |
454 | * @return \DateTimeZone | |
455 | */ | |
456 | public function getTimeZone() | |
457 | { | |
458 | if ($this->timezoneObj === null) { | |
459 | if ($this->timezone) { | |
460 | $this->timezoneObj = new \DateTimeZone($this->timezone); | |
461 | } else { | |
462 | $this->timezoneObj = new \DateTimeZone(TIMEZONE); | |
463 | } | |
464 | } | |
465 | ||
466 | return $this->timezoneObj; | |
467 | } | |
468 | ||
38b18b4a MW |
469 | /** |
470 | * Applies the user's timezone to the given timestamp. | |
471 | */ | |
472 | public function getLocalDate(int $timestamp): \DateTimeImmutable | |
473 | { | |
474 | $dateTime = (new \DateTimeImmutable('@' . $timestamp)); | |
475 | $dateTime = $dateTime->setTimezone($this->getTimeZone()); | |
476 | ||
477 | return $dateTime; | |
478 | } | |
479 | ||
a9229942 TD |
480 | /** |
481 | * Returns a list of users. | |
482 | * | |
483 | * @param array $userIDs | |
484 | * @return User[] | |
485 | */ | |
486 | public static function getUsers(array $userIDs) | |
487 | { | |
488 | $userList = new UserList(); | |
489 | $userList->setObjectIDs($userIDs); | |
490 | $userList->readObjects(); | |
491 | ||
492 | return $userList->getObjects(); | |
493 | } | |
494 | ||
495 | /** | |
496 | * Returns username. | |
a9229942 | 497 | */ |
83c324d3 | 498 | public function __toString(): string |
a9229942 | 499 | { |
eaba5c14 | 500 | return $this->getTitle(); |
a9229942 TD |
501 | } |
502 | ||
503 | /** | |
504 | * @inheritDoc | |
505 | */ | |
506 | public static function getDatabaseTableAlias() | |
507 | { | |
508 | return 'user_table'; | |
509 | } | |
510 | ||
511 | /** | |
512 | * @inheritDoc | |
513 | */ | |
a4a634aa | 514 | public function getTitle(): string |
a9229942 | 515 | { |
eaba5c14 | 516 | return $this->username ?: ''; |
a9229942 TD |
517 | } |
518 | ||
519 | /** | |
520 | * Returns the language of this user. | |
521 | * | |
522 | * @return Language | |
523 | */ | |
524 | public function getLanguage() | |
525 | { | |
526 | $language = LanguageFactory::getInstance()->getLanguage($this->languageID); | |
527 | if ($language === null) { | |
528 | $language = LanguageFactory::getInstance()->getLanguage(LanguageFactory::getInstance()->getDefaultLanguageID()); | |
529 | } | |
530 | ||
531 | return $language; | |
532 | } | |
533 | ||
534 | /** | |
535 | * Returns true if the active user can edit this user. | |
536 | * | |
537 | * @return bool | |
538 | */ | |
539 | public function canEdit() | |
540 | { | |
541 | return WCF::getSession()->getPermission('admin.user.canEditUser') && UserGroup::isAccessibleGroup($this->getGroupIDs()); | |
542 | } | |
543 | ||
544 | /** | |
545 | * Returns true, if this user has access to the ACP. | |
546 | * | |
547 | * @return bool | |
548 | */ | |
549 | public function hasAdministrativeAccess() | |
550 | { | |
551 | if ($this->hasAdministrativePermissions === null) { | |
552 | $this->hasAdministrativePermissions = false; | |
553 | ||
554 | if ($this->userID) { | |
555 | foreach (UserGroup::getGroupsByIDs($this->getGroupIDs()) as $group) { | |
556 | if ($group->isAdminGroup()) { | |
557 | $this->hasAdministrativePermissions = true; | |
558 | break; | |
559 | } | |
560 | } | |
561 | } | |
562 | } | |
563 | ||
564 | return $this->hasAdministrativePermissions; | |
565 | } | |
566 | ||
567 | /** | |
568 | * Returns true, if this user is a member of the owner group. | |
569 | * | |
570 | * @return bool | |
571 | * @since 5.2 | |
572 | */ | |
573 | public function hasOwnerAccess() | |
574 | { | |
52e95d8e TD |
575 | foreach (UserGroup::getGroupsByIDs($this->getGroupIDs()) as $group) { |
576 | if ($group->isOwner()) { | |
577 | return true; | |
a9229942 TD |
578 | } |
579 | } | |
580 | ||
52e95d8e | 581 | return false; |
a9229942 TD |
582 | } |
583 | ||
584 | /** | |
585 | * @inheritDoc | |
586 | */ | |
587 | public function getUserID() | |
588 | { | |
589 | return $this->userID; | |
590 | } | |
591 | ||
592 | /** | |
593 | * @inheritDoc | |
594 | */ | |
595 | public function getUsername() | |
596 | { | |
597 | return $this->username; | |
598 | } | |
599 | ||
600 | /** | |
601 | * @inheritDoc | |
602 | */ | |
603 | public function getTime() | |
604 | { | |
605 | return $this->registrationDate; | |
606 | } | |
607 | ||
608 | /** | |
609 | * @inheritDoc | |
610 | */ | |
51cb3c5e | 611 | public function getLink(): string |
a9229942 TD |
612 | { |
613 | return LinkHandler::getInstance()->getLink('User', [ | |
614 | 'application' => 'wcf', | |
615 | 'object' => $this, | |
616 | 'forceFrontend' => true, | |
617 | ]); | |
618 | } | |
619 | ||
a9229942 TD |
620 | /** |
621 | * Returns the registration ip address, attempts to convert to IPv4. | |
622 | * | |
623 | * @return string | |
624 | */ | |
625 | public function getRegistrationIpAddress() | |
626 | { | |
627 | if ($this->registrationIpAddress) { | |
628 | return UserUtil::convertIPv6To4($this->registrationIpAddress); | |
629 | } | |
630 | ||
631 | return ''; | |
632 | } | |
633 | ||
634 | /** | |
635 | * Returns true, if this user can purchase paid subscriptions. | |
636 | * | |
637 | * @return bool | |
638 | */ | |
639 | public function canPurchasePaidSubscriptions() | |
640 | { | |
641 | return WCF::getUser()->userID && !$this->pendingActivation(); | |
642 | } | |
643 | ||
644 | /** | |
645 | * Returns the list of fields that had matches in the blacklist. An empty list is | |
646 | * returned if the user has been approved, regardless of any known matches. | |
647 | * | |
648 | * @return string[] | |
649 | * @since 5.2 | |
650 | */ | |
651 | public function getBlacklistMatches() | |
652 | { | |
653 | if ($this->pendingActivation() && $this->blacklistMatches) { | |
654 | $matches = JSON::decode($this->blacklistMatches); | |
655 | if (\is_array($matches)) { | |
656 | return $matches; | |
657 | } | |
658 | } | |
659 | ||
660 | return []; | |
661 | } | |
662 | ||
663 | /** | |
664 | * Returns a human readable list of fields that have positive matches against the | |
665 | * blacklist. If you require the raw field names, please use `getBlacklistMatches()` | |
666 | * instead. | |
667 | * | |
668 | * @return string[] | |
669 | * @since 5.2 | |
670 | */ | |
671 | public function getBlacklistMatchesTitle() | |
672 | { | |
673 | return \array_map(static function ($field) { | |
674 | if ($field === 'ip') { | |
675 | $field = 'ipAddress'; | |
676 | } | |
677 | ||
678 | return WCF::getLanguage()->get('wcf.user.' . $field); | |
679 | }, $this->getBlacklistMatches()); | |
680 | } | |
681 | ||
682 | /** | |
683 | * Returns true if this user is not activated. | |
684 | * | |
685 | * @return bool | |
686 | * @since 5.3 | |
687 | */ | |
688 | public function pendingActivation() | |
689 | { | |
690 | return $this->activationCode != 0; | |
691 | } | |
692 | ||
693 | /** | |
694 | * Returns true if this user requires activation by the user. | |
695 | * | |
696 | * @return bool | |
697 | * @since 5.3 | |
698 | */ | |
699 | public function requiresEmailActivation() | |
700 | { | |
701 | return REGISTER_ACTIVATION_METHOD & self::REGISTER_ACTIVATION_USER && $this->pendingActivation() && !$this->isEmailConfirmed(); | |
702 | } | |
703 | ||
704 | /** | |
705 | * Returns true if this user requires the activation by an admin. | |
706 | * | |
707 | * @return bool | |
708 | * @since 5.3 | |
709 | */ | |
710 | public function requiresAdminActivation() | |
711 | { | |
712 | return REGISTER_ACTIVATION_METHOD & self::REGISTER_ACTIVATION_ADMIN && $this->pendingActivation(); | |
713 | } | |
714 | ||
715 | /** | |
716 | * Returns true if this user can confirm the email themself. | |
717 | * | |
718 | * @return bool | |
719 | * @since 5.3 | |
720 | */ | |
721 | public function canEmailConfirm() | |
722 | { | |
723 | return REGISTER_ACTIVATION_METHOD & self::REGISTER_ACTIVATION_USER && !$this->isEmailConfirmed(); | |
724 | } | |
725 | ||
726 | /** | |
727 | * Returns true, if the user must confirm his email by themself. | |
728 | * | |
729 | * @return bool | |
730 | * @since 5.3 | |
731 | */ | |
732 | public function mustSelfEmailConfirm() | |
733 | { | |
734 | return REGISTER_ACTIVATION_METHOD & self::REGISTER_ACTIVATION_USER; | |
735 | } | |
736 | ||
ef052a1d TD |
737 | /** |
738 | * Returns true if the user is a member of a user group that requires | |
739 | * multi-factor authentication to be enabled. | |
56da9452 MS |
740 | * |
741 | * @since 5.4 | |
ef052a1d TD |
742 | */ |
743 | public function requiresMultifactor(): bool | |
744 | { | |
745 | foreach (UserGroup::getGroupsByIDs($this->getGroupIDs()) as $group) { | |
746 | if ($group->requireMultifactor) { | |
747 | return true; | |
748 | } | |
749 | } | |
750 | ||
751 | return false; | |
752 | } | |
753 | ||
a9229942 TD |
754 | /** |
755 | * @inheritDoc | |
756 | */ | |
757 | public function getPopoverLinkClass() | |
758 | { | |
759 | return 'userLink'; | |
760 | } | |
11ade432 | 761 | } |