Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
39.32% covered (danger)
39.32%
462 / 1175
42.65% covered (danger)
42.65%
58 / 136
CRAP
0.00% covered (danger)
0.00%
0 / 1
User
39.35% covered (danger)
39.35%
462 / 1174
42.65% covered (danger)
42.65%
58 / 136
31575.75
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWikiId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __get
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 __set
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 __sleep
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 isSafeToLoad
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
20
 load
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 1
342
 loadFromId
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 purge
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getCacheKey
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 loadFromCache
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
72
 newFromName
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
4.05
 newFromId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 newFromActorId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 newFromIdentity
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 newFromAnyId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 newFromConfirmationCode
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 newFromSession
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 newFromRow
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 newSystemUser
96.23% covered (success)
96.23%
51 / 53
0.00% covered (danger)
0.00%
0 / 1
10
 whoIs
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 whoIsReal
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 findUsersByGroup
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 isValidPassword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 checkPasswordValidity
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 loadDefaults
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
4.00
 isItemLoaded
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
4
 setItemLoaded
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 loadFromSession
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 loadFromDatabase
90.00% covered (success)
90.00%
18 / 20
0.00% covered (danger)
0.00%
0 / 1
3.01
 loadFromRow
85.71% covered (warning)
85.71%
48 / 56
0.00% covered (danger)
0.00%
0 / 1
21.17
 loadFromUserObject
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 makeUpdateConditions
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 checkAndSetTouched
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
3.01
 clearInstanceCache
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
132
 isPingLimitable
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 pingLimiter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toRateLimitSubject
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getBlock
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
5.01
 isBlockedGlobally
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getGlobalBlock
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
90
 isLocked
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 isHidden
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getId
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
6.07
 setId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 setName
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getActorId
25.00% covered (danger)
25.00%
4 / 16
0.00% covered (danger)
0.00%
0 / 1
21.19
 setActorId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getTitleKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newTouchedTimestamp
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 clearSharedCache
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 invalidateCache
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 touch
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 validateCache
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTouched
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 getDBTouched
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 changeAuthenticationData
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getToken
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 setToken
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getEmail
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getEmailAuthenticationTimestamp
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 setEmail
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 setEmailWithConfirmation
25.00% covered (danger)
25.00%
7 / 28
0.00% covered (danger)
0.00%
0 / 1
62.05
 getRealName
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setRealName
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTokenFromOption
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 resetTokenFromOption
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 getDatePreference
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 requiresHTTPS
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 getEditCount
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 isRegistered
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isAnon
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isBot
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 isSystemUser
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 isAllowedAny
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isAllowedAll
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isAllowed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 useRCPatrol
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 useNPPatrol
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 useFilePatrol
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getRequest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExperienceLevel
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
7
 setCookies
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
56
 logout
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 doLogout
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
 saveSettings
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
42
 idForName
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 createNew
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 insertNewUser
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
30
 addToDatabase
73.08% covered (warning)
73.08%
38 / 52
0.00% covered (danger)
0.00%
0 / 1
10.58
 spreadAnyEditBlock
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 spreadBlock
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
3.10
 isBlockedFromEmailuser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isBlockedFromUpload
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 isAllowedToCreateAccount
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTalkPage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isNewbie
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEditTokenObject
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getEditToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 matchEditToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 sendConfirmationMail
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
20
 sendMail
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
12
 confirmationToken
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 isWellFormedConfirmationToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 confirmationTokenUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 invalidationTokenUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTokenUrl
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 confirmEmail
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 invalidateEmail
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 setEmailAuthenticationTimestamp
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 canSendEmail
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 canReceiveEmail
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 isEmailConfirmed
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 isEmailConfirmationPending
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 getRegistration
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getRightDescription
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getRightDescriptionHtml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
2
 newQueryBuilder
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
2
 newFatalPermissionDeniedStatus
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getInstanceForUpdate
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 equals
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 probablyCan
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 definitelyCan
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDefinitelyAllowed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 authorizeAction
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 authorizeRead
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 authorizeWrite
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getThisAsAuthority
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 isGlobalSessionUser
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 isTemp
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 isNamed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\User;
22
23use AllowDynamicProperties;
24use ArrayIterator;
25use InvalidArgumentException;
26use MailAddress;
27use MediaWiki\Auth\AuthenticationRequest;
28use MediaWiki\Auth\AuthManager;
29use MediaWiki\Block\AbstractBlock;
30use MediaWiki\Block\Block;
31use MediaWiki\Block\SystemBlock;
32use MediaWiki\Context\RequestContext;
33use MediaWiki\DAO\WikiAwareEntityTrait;
34use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
35use MediaWiki\Logger\LoggerFactory;
36use MediaWiki\Mail\UserEmailContact;
37use MediaWiki\MainConfigNames;
38use MediaWiki\MainConfigSchema;
39use MediaWiki\MediaWikiServices;
40use MediaWiki\Page\PageIdentity;
41use MediaWiki\Parser\Sanitizer;
42use MediaWiki\Password\PasswordFactory;
43use MediaWiki\Password\UserPasswordPolicy;
44use MediaWiki\Permissions\Authority;
45use MediaWiki\Permissions\PermissionStatus;
46use MediaWiki\Permissions\RateLimitSubject;
47use MediaWiki\Permissions\UserAuthority;
48use MediaWiki\Request\WebRequest;
49use MediaWiki\Session\SessionManager;
50use MediaWiki\Session\Token;
51use MediaWiki\Status\Status;
52use MediaWiki\Title\Title;
53use MWCryptHash;
54use MWCryptRand;
55use MWExceptionHandler;
56use RuntimeException;
57use stdClass;
58use Stringable;
59use UnexpectedValueException;
60use Wikimedia\Assert\Assert;
61use Wikimedia\Assert\PreconditionException;
62use Wikimedia\DebugInfo\DebugInfoTrait;
63use Wikimedia\IPUtils;
64use Wikimedia\ObjectCache\WANObjectCache;
65use Wikimedia\Rdbms\Database;
66use Wikimedia\Rdbms\DBAccessObjectUtils;
67use Wikimedia\Rdbms\DBExpectedError;
68use Wikimedia\Rdbms\IDatabase;
69use Wikimedia\Rdbms\IDBAccessObject;
70use Wikimedia\Rdbms\IReadableDatabase;
71use Wikimedia\Rdbms\SelectQueryBuilder;
72use Wikimedia\ScopedCallback;
73use Wikimedia\Timestamp\ConvertibleTimestamp;
74
75/**
76 * @defgroup User User management
77 */
78
79/**
80 * User class for the %MediaWiki software.
81 *
82 * User objects manage reading and writing of user-specific storage, including:
83 * - `user` table (user_id, user_name, email, password, last login, etc.)
84 * - `user_properties` table (user options)
85 * - `user_groups` table (user rights and permissions)
86 * - `user_newtalk` table (last-seen for your own user talk page)
87 * - `watchlist` table (watched page titles by user, and their last-seen marker)
88 * - `block` table, formerly known as `ipblocks` (user blocks)
89 *
90 * Callers use getter methods (getXXX) to read these fields. These getter functions
91 * manage all higher-level responsibilities such as expanding default user options,
92 * interpreting user groups into specific rights. Most user data needed when
93 * rendering page views are cached (or stored in the session) to minimize repeat
94 * database queries.
95 *
96 * New code is encouraged to use the following narrower classes instead.
97 * If no replacement exist, and the User class method is not deprecated, feel
98 * free to use it in new code (instead of duplicating business logic).
99 *
100 * - UserIdentityValue, to represent a user name/id.
101 * - UserOptionsManager service, to read-write user options.
102 * - Authority via RequestContext::getAuthority, to represent the current user
103 *   with a easy shortcuts to interpret user permissions (can user X do Y on page Z)
104 *   without needing te call low-level PermissionManager and RateLimiter services.
105 *   Authority replaces methods like User::isAllowed, User::definitelyCan,
106 *   and User::pingLimiter.
107 * - PermissionManager service, to interpret rights and permissions of any user.
108 * - TalkPageNotificationManager service, replacing User::getNewtalk.
109 * - WatchlistManager service, replacing methods like User::isWatched,
110 *   User::addWatch, and User::clearNotification.
111 * - BlockManager service, replacing User::getBlock.
112 *
113 * @note User implements Authority to ease transition. Always prefer
114 * using existing Authority or obtaining a proper Authority implementation.
115 *
116 * @ingroup User
117 */
118#[AllowDynamicProperties]
119class User implements Stringable, Authority, UserIdentity, UserEmailContact {
120    use DebugInfoTrait;
121    use ProtectedHookAccessorTrait;
122    use WikiAwareEntityTrait;
123
124    /**
125     * @see IDBAccessObject::READ_EXCLUSIVE
126     */
127    public const READ_EXCLUSIVE = IDBAccessObject::READ_EXCLUSIVE;
128
129    /**
130     * @see IDBAccessObject::READ_LOCKING
131     */
132    public const READ_LOCKING = IDBAccessObject::READ_LOCKING;
133
134    /**
135     * Number of characters required for the user_token field.
136     */
137    public const TOKEN_LENGTH = 32;
138
139    /**
140     * An invalid string value for the user_token field.
141     */
142    public const INVALID_TOKEN = '*** INVALID ***';
143
144    /**
145     * Version number to tag cached versions of serialized User objects. Should be increased when
146     * {@link $mCacheVars} or one of its members changes.
147     */
148    private const VERSION = 17;
149
150    /**
151     * Username used for various maintenance scripts.
152     * @since 1.37
153     */
154    public const MAINTENANCE_SCRIPT_USER = 'Maintenance script';
155
156    /**
157     * List of member variables which are saved to the
158     * shared cache (memcached). Any operation which changes the
159     * corresponding database fields must call a cache-clearing function.
160     * @showinitializer
161     * @var string[]
162     */
163    protected static $mCacheVars = [
164        // user table
165        'mId',
166        'mName',
167        'mRealName',
168        'mEmail',
169        'mTouched',
170        'mToken',
171        'mEmailAuthenticated',
172        'mEmailToken',
173        'mEmailTokenExpires',
174        'mRegistration',
175        // actor table
176        'mActorId',
177    ];
178
179    /** Cache variables */
180    // Some of these are public, including for use by the UserFactory, but they generally
181    // should not be set manually
182    // @{
183    /** @var int */
184    public $mId;
185    /** @var string */
186    public $mName;
187    /**
188     * Switched from protected to public for use in UserFactory
189     *
190     * @var int|null
191     */
192    public $mActorId;
193    /** @var string */
194    public $mRealName;
195
196    /** @var string */
197    public $mEmail;
198    /** @var string TS_MW timestamp from the DB */
199    public $mTouched;
200    /** @var string|null TS_MW timestamp from cache */
201    protected $mQuickTouched;
202    /** @var string|null */
203    protected $mToken;
204    /** @var string|null */
205    public $mEmailAuthenticated;
206    /** @var string|null */
207    protected $mEmailToken;
208    /** @var string|null */
209    protected $mEmailTokenExpires;
210    /** @var string|null */
211    protected $mRegistration;
212    // @}
213
214    // @{
215    /**
216     * @var array|bool Array with already loaded items or true if all items have been loaded.
217     */
218    protected $mLoadedItems = [];
219    // @}
220
221    /**
222     * @var string Initialization data source if mLoadedItems!==true. May be one of:
223     *  - 'defaults'   anonymous user initialised from class defaults
224     *  - 'name'       initialise from mName
225     *  - 'id'         initialise from mId
226     *  - 'actor'      initialise from mActorId
227     *  - 'session'    log in from session if possible
228     *
229     * Use the User::newFrom*() family of functions to set this.
230     */
231    public $mFrom;
232
233    /**
234     * Lazy-initialized variables, invalidated with clearInstanceCache
235     */
236    /** @var string|null */
237    protected $mDatePreference;
238    /** @var string|false */
239    protected $mHash;
240    /** @var AbstractBlock */
241    protected $mGlobalBlock;
242    /** @var bool */
243    protected $mLocked;
244
245    /** @var WebRequest|null */
246    private $mRequest;
247
248    /** @var int IDBAccessObject::READ_* constant bitfield used to load data */
249    protected $queryFlagsUsed = IDBAccessObject::READ_NORMAL;
250
251    /**
252     * @var UserAuthority|null lazy-initialized Authority of this user
253     * @noVarDump
254     */
255    private $mThisAsAuthority;
256
257    /** @var bool|null */
258    private $isTemp;
259
260    /**
261     * @internal since 1.36, use the UserFactory service instead
262     *
263     * @see MediaWiki\User\UserFactory
264     *
265     * @see newFromName()
266     * @see newFromId()
267     * @see newFromActorId()
268     * @see newFromConfirmationCode()
269     * @see newFromSession()
270     * @see newFromRow()
271     */
272    public function __construct() {
273        // By default, this is a lightweight constructor representing
274        // an anonymous user from the current web request and IP.
275        $this->clearInstanceCache( 'defaults' );
276    }
277
278    /**
279     * Returns self::LOCAL to indicate the user is associated with the local wiki.
280     *
281     * @since 1.36
282     * @return string|false
283     */
284    public function getWikiId() {
285        return self::LOCAL;
286    }
287
288    /**
289     * @return string
290     */
291    public function __toString() {
292        return $this->getName();
293    }
294
295    public function &__get( $name ) {
296        // A shortcut for $mRights deprecation phase
297        if ( $name === 'mRights' ) {
298            // hard deprecated since 1.40
299            wfDeprecated( 'User::$mRights', '1.34' );
300            $copy = MediaWikiServices::getInstance()
301                ->getPermissionManager()
302                ->getUserPermissions( $this );
303            return $copy;
304        } elseif ( !property_exists( $this, $name ) ) {
305            // T227688 - do not break $u->foo['bar'] = 1
306            wfLogWarning( 'tried to get non-existent property' );
307            $this->$name = null;
308            return $this->$name;
309        } else {
310            wfLogWarning( 'tried to get non-visible property' );
311            $null = null;
312            return $null;
313        }
314    }
315
316    public function __set( $name, $value ) {
317        // A shortcut for $mRights deprecation phase, only known legitimate use was for
318        // testing purposes, other uses seem bad in principle
319        if ( $name === 'mRights' ) {
320            // hard deprecated since 1.40
321            wfDeprecated( 'User::$mRights', '1.34' );
322            MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
323                $this,
324                $value ?? []
325            );
326        } elseif ( !property_exists( $this, $name ) ) {
327            $this->$name = $value;
328        } else {
329            wfLogWarning( 'tried to set non-visible property' );
330        }
331    }
332
333    public function __sleep(): array {
334        return array_diff(
335            array_keys( get_object_vars( $this ) ),
336            [
337                'mThisAsAuthority' // memoization, will be recreated on demand.
338            ]
339        );
340    }
341
342    /**
343     * Test if it's safe to load this User object.
344     *
345     * You should typically check this before using $wgUser or
346     * RequestContext::getUser in a method that might be called before the
347     * system has been fully initialized. If the object is unsafe, you should
348     * use an anonymous user:
349     * \code
350     * $user = $wgUser->isSafeToLoad() ? $wgUser : new User;
351     * \endcode
352     *
353     * @since 1.27
354     * @return bool
355     */
356    public function isSafeToLoad() {
357        global $wgFullyInitialised;
358
359        // The user is safe to load if:
360        // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
361        // * mLoadedItems === true (already loaded)
362        // * mFrom !== 'session' (sessions not involved at all)
363
364        return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
365            $this->mLoadedItems === true || $this->mFrom !== 'session';
366    }
367
368    /**
369     * Load the user table data for this object from the source given by mFrom.
370     *
371     * @param int $flags IDBAccessObject::READ_* constant bitfield
372     */
373    public function load( $flags = IDBAccessObject::READ_NORMAL ) {
374        global $wgFullyInitialised;
375
376        if ( $this->mLoadedItems === true ) {
377            return;
378        }
379
380        // Set it now to avoid infinite recursion in accessors
381        $oldLoadedItems = $this->mLoadedItems;
382        $this->mLoadedItems = true;
383        $this->queryFlagsUsed = $flags;
384
385        // If this is called too early, things are likely to break.
386        if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
387            LoggerFactory::getInstance( 'session' )
388                ->warning( 'User::loadFromSession called before the end of Setup.php', [
389                    'exception' => new RuntimeException(
390                        'User::loadFromSession called before the end of Setup.php'
391                    ),
392                ] );
393            $this->loadDefaults();
394            $this->mLoadedItems = $oldLoadedItems;
395            return;
396        }
397
398        switch ( $this->mFrom ) {
399            case 'defaults':
400                $this->loadDefaults();
401                break;
402            case 'id':
403                // Make sure this thread sees its own changes, if the ID isn't 0
404                if ( $this->mId != 0 ) {
405                    $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
406                    if ( $lb->hasOrMadeRecentPrimaryChanges() ) {
407                        $flags |= IDBAccessObject::READ_LATEST;
408                        $this->queryFlagsUsed = $flags;
409                    }
410                }
411
412                $this->loadFromId( $flags );
413                break;
414            case 'actor':
415            case 'name':
416                // Make sure this thread sees its own changes
417                $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
418                if ( $lb->hasOrMadeRecentPrimaryChanges() ) {
419                    $flags |= IDBAccessObject::READ_LATEST;
420                    $this->queryFlagsUsed = $flags;
421                }
422
423                $dbr = DBAccessObjectUtils::getDBFromRecency(
424                    MediaWikiServices::getInstance()->getDBLoadBalancerFactory(),
425                    $flags
426                );
427                $queryBuilder = $dbr->newSelectQueryBuilder()
428                    ->select( [ 'actor_id', 'actor_user', 'actor_name' ] )
429                    ->from( 'actor' )
430                    ->recency( $flags );
431                if ( $this->mFrom === 'name' ) {
432                    // make sure to use normalized form of IP for anonymous users
433                    $queryBuilder->where( [ 'actor_name' => IPUtils::sanitizeIP( $this->mName ) ] );
434                } else {
435                    $queryBuilder->where( [ 'actor_id' => $this->mActorId ] );
436                }
437                $row = $queryBuilder->caller( __METHOD__ )->fetchRow();
438
439                if ( !$row ) {
440                    // Ugh.
441                    $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
442                } elseif ( $row->actor_user ) {
443                    $this->mId = $row->actor_user;
444                    $this->loadFromId( $flags );
445                } else {
446                    $this->loadDefaults( $row->actor_name, $row->actor_id );
447                }
448                break;
449            case 'session':
450                if ( !$this->loadFromSession() ) {
451                    // Loading from session failed. Load defaults.
452                    $this->loadDefaults();
453                }
454                $this->getHookRunner()->onUserLoadAfterLoadFromSession( $this );
455                break;
456            default:
457                throw new UnexpectedValueException(
458                    "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
459        }
460    }
461
462    /**
463     * Load user table data, given mId has already been set.
464     * @param int $flags IDBAccessObject::READ_* constant bitfield
465     * @return bool False if the ID does not exist, true otherwise
466     */
467    public function loadFromId( $flags = IDBAccessObject::READ_NORMAL ) {
468        if ( $this->mId == 0 ) {
469            // Anonymous users are not in the database (don't need cache)
470            $this->loadDefaults();
471            return false;
472        }
473
474        // Try cache (unless this needs data from the primary DB).
475        // NOTE: if this thread called saveSettings(), the cache was cleared.
476        $latest = DBAccessObjectUtils::hasFlags( $flags, IDBAccessObject::READ_LATEST );
477        if ( $latest ) {
478            if ( !$this->loadFromDatabase( $flags ) ) {
479                // Can't load from ID
480                return false;
481            }
482        } else {
483            $this->loadFromCache();
484        }
485
486        $this->mLoadedItems = true;
487        $this->queryFlagsUsed = $flags;
488
489        return true;
490    }
491
492    /**
493     * @since 1.27
494     * @param string $dbDomain
495     * @param int $userId
496     */
497    public static function purge( $dbDomain, $userId ) {
498        $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
499        $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
500        $cache->delete( $key );
501    }
502
503    /**
504     * @since 1.27
505     * @param WANObjectCache $cache
506     * @return string
507     */
508    protected function getCacheKey( WANObjectCache $cache ) {
509        $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
510
511        return $cache->makeGlobalKey( 'user', 'id',
512            $lbFactory->getLocalDomainID(), $this->mId );
513    }
514
515    /**
516     * Load user data from shared cache, given mId has already been set.
517     *
518     * @return bool True
519     * @since 1.25
520     */
521    protected function loadFromCache() {
522        global $wgFullyInitialised;
523
524        $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
525        $data = $cache->getWithSetCallback(
526            $this->getCacheKey( $cache ),
527            $cache::TTL_HOUR,
528            function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache, $wgFullyInitialised ) {
529                $setOpts += Database::getCacheSetOptions(
530                    MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase()
531                );
532                wfDebug( "User: cache miss for user {$this->mId}" );
533
534                $this->loadFromDatabase( IDBAccessObject::READ_NORMAL );
535
536                $data = [];
537                foreach ( self::$mCacheVars as $name ) {
538                    $data[$name] = $this->$name;
539                }
540
541                $ttl = $cache->adaptiveTTL(
542                    (int)wfTimestamp( TS_UNIX, $this->mTouched ),
543                    $ttl
544                );
545
546                if ( $wgFullyInitialised ) {
547                    $groupMemberships = MediaWikiServices::getInstance()
548                        ->getUserGroupManager()
549                        ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
550
551                    // if a user group membership is about to expire, the cache needs to
552                    // expire at that time (T163691)
553                    foreach ( $groupMemberships as $ugm ) {
554                        if ( $ugm->getExpiry() ) {
555                            $secondsUntilExpiry =
556                                (int)wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
557
558                            if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
559                                $ttl = $secondsUntilExpiry;
560                            }
561                        }
562                    }
563                }
564
565                return $data;
566            },
567            [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
568        );
569
570        // Restore from cache
571        foreach ( self::$mCacheVars as $name ) {
572            $this->$name = $data[$name];
573        }
574
575        return true;
576    }
577
578    /***************************************************************************/
579    // region   newFrom*() static factory methods
580    /** @name   newFrom*() static factory methods
581     * @{
582     */
583
584    /**
585     * @see UserFactory::newFromName
586     *
587     * @deprecated since 1.36, use a UserFactory instead
588     *
589     * This is slightly less efficient than newFromId(), so use newFromId() if
590     * you have both an ID and a name handy.
591     *
592     * @param string $name Username, validated by Title::newFromText()
593     * @param string|bool $validate Validate username.Type of validation to use:
594     *   - false        No validation
595     *   - 'valid'      Valid for batch processes
596     *   - 'usable'     Valid for batch processes and login
597     *   - 'creatable'  Valid for batch processes, login and account creation,
598     *  except that true is accepted as an alias for 'valid', for BC.
599     *
600     * @return User|false User object, or false if the username is invalid
601     *  (e.g. if it contains illegal characters or is an IP address). If the
602     *  username is not present in the database, the result will be a user object
603     *  with a name, zero user ID and default settings.
604     */
605    public static function newFromName( $name, $validate = 'valid' ) {
606        // Backwards compatibility with strings / false
607        $validationLevels = [
608            'valid' => UserRigorOptions::RIGOR_VALID,
609            'usable' => UserRigorOptions::RIGOR_USABLE,
610            'creatable' => UserRigorOptions::RIGOR_CREATABLE
611        ];
612        if ( $validate === true ) {
613            $validate = 'valid';
614        }
615        if ( $validate === false ) {
616            $validation = UserRigorOptions::RIGOR_NONE;
617        } elseif ( array_key_exists( $validate, $validationLevels ) ) {
618            $validation = $validationLevels[ $validate ];
619        } else {
620            // Not a recognized value, probably a test for unsupported validation
621            // levels, regardless, just pass it along
622            $validation = $validate;
623        }
624
625        return MediaWikiServices::getInstance()->getUserFactory()
626            ->newFromName( (string)$name, $validation ) ?? false;
627    }
628
629    /**
630     * Static factory method for creation from a given user ID.
631     *
632     * @see UserFactory::newFromId
633     *
634     * @deprecated since 1.36, use a UserFactory instead
635     *
636     * @param int $id Valid user ID
637     * @return User
638     */
639    public static function newFromId( $id ) {
640        return MediaWikiServices::getInstance()
641            ->getUserFactory()
642            ->newFromId( (int)$id );
643    }
644
645    /**
646     * Static factory method for creation from a given actor ID.
647     *
648     * @see UserFactory::newFromActorId
649     *
650     * @deprecated since 1.36, use a UserFactory instead
651     *
652     * @since 1.31
653     * @param int $id Valid actor ID
654     * @return User
655     */
656    public static function newFromActorId( $id ) {
657        return MediaWikiServices::getInstance()
658            ->getUserFactory()
659            ->newFromActorId( (int)$id );
660    }
661
662    /**
663     * Returns a User object corresponding to the given UserIdentity.
664     *
665     * @see UserFactory::newFromUserIdentity
666     *
667     * @deprecated since 1.36, use a UserFactory instead
668     *
669     * @since 1.32
670     *
671     * @param UserIdentity $identity
672     *
673     * @return User
674     */
675    public static function newFromIdentity( UserIdentity $identity ) {
676        // Don't use the service if we already have a User object,
677        // so that User::newFromIdentity calls don't break things in unit tests.
678        if ( $identity instanceof User ) {
679            return $identity;
680        }
681
682        return MediaWikiServices::getInstance()
683            ->getUserFactory()
684            ->newFromUserIdentity( $identity );
685    }
686
687    /**
688     * Static factory method for creation from an ID, name, and/or actor ID
689     *
690     * This does not check that the ID, name, and actor ID all correspond to
691     * the same user.
692     *
693     * @see UserFactory::newFromAnyId
694     *
695     * @deprecated since 1.36, use a UserFactory instead
696     *
697     * @since 1.31
698     * @param int|null $userId User ID, if known
699     * @param string|null $userName User name, if known
700     * @param int|null $actorId Actor ID, if known
701     * @param string|false $dbDomain remote wiki to which the User/Actor ID
702     *   applies, or false if none
703     * @return User
704     */
705    public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
706        return MediaWikiServices::getInstance()
707            ->getUserFactory()
708            ->newFromAnyId( $userId, $userName, $actorId, $dbDomain );
709    }
710
711    /**
712     * Factory method to fetch whichever user has a given email confirmation code.
713     * This code is generated when an account is created or its e-mail address
714     * has changed.
715     *
716     * If the code is invalid or has expired, returns NULL.
717     *
718     * @see UserFactory::newFromConfirmationCode
719     *
720     * @deprecated since 1.36, use a UserFactory instead
721     *
722     * @param string $code Confirmation code
723     * @param int $flags IDBAccessObject::READ_* bitfield
724     * @return User|null
725     */
726    public static function newFromConfirmationCode( $code, $flags = IDBAccessObject::READ_NORMAL ) {
727        return MediaWikiServices::getInstance()
728            ->getUserFactory()
729            ->newFromConfirmationCode( (string)$code, $flags );
730    }
731
732    /**
733     * Create a new user object using data from session. If the login
734     * credentials are invalid, the result is an anonymous user.
735     *
736     * @param WebRequest|null $request Object to use; the global request will be used if omitted.
737     * @return User
738     */
739    public static function newFromSession( ?WebRequest $request = null ) {
740        $user = new User;
741        $user->mFrom = 'session';
742        $user->mRequest = $request;
743        return $user;
744    }
745
746    /**
747     * Create a new user object from a user row.
748     * The row should have the following fields from the user table in it:
749     * - either user_name or user_id to load further data if needed (or both)
750     * - user_real_name
751     * - all other fields (email, etc.)
752     * It is useless to provide the remaining fields if either user_id,
753     * user_name and user_real_name are not provided because the whole row
754     * will be loaded once more from the database when accessing them.
755     *
756     * @param stdClass $row A row from the user table
757     * @param array|null $data Further data to load into the object
758     *  (see User::loadFromRow for valid keys)
759     * @return User
760     */
761    public static function newFromRow( $row, $data = null ) {
762        $user = new User;
763        $user->loadFromRow( $row, $data );
764        return $user;
765    }
766
767    /**
768     * Static factory method for creation of a "system" user from username.
769     *
770     * A "system" user is an account that's used to attribute logged actions
771     * taken by MediaWiki itself, as opposed to a bot or human user. Examples
772     * might include the 'Maintenance script' or 'Conversion script' accounts
773     * used by various scripts in the maintenance/ directory or accounts such
774     * as 'MediaWiki message delivery' used by the MassMessage extension.
775     *
776     * This can optionally create the user if it doesn't exist, and "steal" the
777     * account if it does exist.
778     *
779     * "Stealing" an existing user is intended to make it impossible for normal
780     * authentication processes to use the account, effectively disabling the
781     * account for normal use:
782     * - Email is invalidated, to prevent account recovery by emailing a
783     *   temporary password and to disassociate the account from the existing
784     *   human.
785     * - The token is set to a magic invalid value, to kill existing sessions
786     *   and to prevent $this->setToken() calls from resetting the token to a
787     *   valid value.
788     * - SessionManager is instructed to prevent new sessions for the user, to
789     *   do things like deauthorizing OAuth consumers.
790     * - AuthManager is instructed to revoke access, to invalidate or remove
791     *   passwords and other credentials.
792     *
793     * System users should usually be listed in $wgReservedUsernames.
794     *
795     * @param string $name Username
796     * @param array $options Options are:
797     *  - validate: Type of validation to use:
798     *    - false        No validation
799     *    - 'valid'      Valid for batch processes
800     *    - 'usable'     Valid for batch processes and login
801     *    - 'creatable'  Valid for batch processes, login and account creation,
802     *    default 'valid'. Deprecated since 1.36.
803     *  - create: Whether to create the user if it doesn't already exist, default true
804     *  - steal: Whether to "disable" the account for normal use if it already
805     *    exists, default false
806     * @return User|null
807     * @since 1.27
808     * @see self::isSystemUser()
809     * @see MainConfigSchema::ReservedUsernames
810     */
811    public static function newSystemUser( $name, $options = [] ) {
812        $options += [
813            'validate' => UserRigorOptions::RIGOR_VALID,
814            'create' => true,
815            'steal' => false,
816        ];
817
818        // Username validation
819        // Backwards compatibility with strings / false
820        $validationLevels = [
821            'valid' => UserRigorOptions::RIGOR_VALID,
822            'usable' => UserRigorOptions::RIGOR_USABLE,
823            'creatable' => UserRigorOptions::RIGOR_CREATABLE
824        ];
825        $validate = $options['validate'];
826
827        // @phan-suppress-next-line PhanSuspiciousValueComparison
828        if ( $validate === false ) {
829            $validation = UserRigorOptions::RIGOR_NONE;
830        } elseif ( array_key_exists( $validate, $validationLevels ) ) {
831            $validation = $validationLevels[ $validate ];
832        } else {
833            // Not a recognized value, probably a test for unsupported validation
834            // levels, regardless, just pass it along
835            $validation = $validate;
836        }
837
838        if ( $validation !== UserRigorOptions::RIGOR_VALID ) {
839            wfDeprecatedMsg(
840                __METHOD__ . ' options["validation"] parameter must be omitted or set to "valid".',
841                '1.36'
842            );
843        }
844        $services = MediaWikiServices::getInstance();
845        $userNameUtils = $services->getUserNameUtils();
846
847        $name = $userNameUtils->getCanonical( (string)$name, $validation );
848        if ( $name === false ) {
849            return null;
850        }
851
852        $dbProvider = $services->getDBLoadBalancerFactory();
853        $dbr = $dbProvider->getReplicaDatabase();
854
855        $userQuery = self::newQueryBuilder( $dbr )
856            ->where( [ 'user_name' => $name ] )
857            ->caller( __METHOD__ );
858        $row = $userQuery->fetchRow();
859        if ( !$row ) {
860            // Try the primary database
861            $userQuery->connection( $dbProvider->getPrimaryDatabase() );
862            // Lock the row to prevent insertNewUser() returning null due to race conditions
863            $userQuery->forUpdate();
864            $row = $userQuery->fetchRow();
865        }
866
867        if ( !$row ) {
868            // No user. Create it?
869            // @phan-suppress-next-line PhanImpossibleCondition
870            if ( !$options['create'] ) {
871                // No.
872                return null;
873            }
874
875            // If it's a reserved user that had an anonymous actor created for it at
876            // some point, we need special handling.
877            return self::insertNewUser( static function ( UserIdentity $actor, IDatabase $dbw ) {
878                return MediaWikiServices::getInstance()->getActorStore()
879                    ->acquireSystemActorId( $actor, $dbw );
880            }, $name, [ 'token' => self::INVALID_TOKEN ] );
881        }
882
883        $user = self::newFromRow( $row );
884
885        if ( !$user->isSystemUser() ) {
886            // User exists. Steal it?
887            // @phan-suppress-next-line PhanRedundantCondition
888            if ( !$options['steal'] ) {
889                return null;
890            }
891
892            $services->getAuthManager()->revokeAccessForUser( $name );
893
894            $user->invalidateEmail();
895            $user->mToken = self::INVALID_TOKEN;
896            $user->saveSettings();
897            SessionManager::singleton()->preventSessionsForUser( $user->getName() );
898        }
899
900        return $user;
901    }
902
903    /** @} */
904    // endregion -- end of newFrom*() static factory methods
905
906    /**
907     * Get the username corresponding to a given user ID
908     * @deprecated since 1.43, Use UserIdentityLookup to get name from id
909     * @param int $id User ID
910     * @return string|false The corresponding username
911     */
912    public static function whoIs( $id ) {
913        return MediaWikiServices::getInstance()->getUserCache()
914            ->getProp( $id, 'name' );
915    }
916
917    /**
918     * Get the real name of a user given their user ID
919     *
920     * @deprecated since 1.43, Use UserFactory to get user instance and use User::getRealName
921     * @param int $id User ID
922     * @return string|false The corresponding user's real name
923     */
924    public static function whoIsReal( $id ) {
925        return MediaWikiServices::getInstance()->getUserCache()
926            ->getProp( $id, 'real_name' );
927    }
928
929    /**
930     * Return the users who are members of the given group(s). In case of multiple groups,
931     * users who are members of at least one of them are returned.
932     *
933     * @param string|array $groups A single group name or an array of group names
934     * @param int $limit Max number of users to return. The actual limit will never exceed 5000
935     *   records; larger values are ignored.
936     * @param int|null $after ID the user to start after
937     * @return UserArray|ArrayIterator
938     */
939    public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
940        if ( $groups === [] ) {
941            return UserArrayFromResult::newFromIDs( [] );
942        }
943        $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
944        $queryBuilder = $dbr->newSelectQueryBuilder()
945            ->select( 'ug_user' )
946            ->distinct()
947            ->from( 'user_groups' )
948            ->where( [ 'ug_group' => array_unique( (array)$groups ) ] )
949            ->orderBy( 'ug_user' )
950            ->limit( min( 5000, $limit ) );
951
952        if ( $after !== null ) {
953            $queryBuilder->andWhere( $dbr->expr( 'ug_user', '>', (int)$after ) );
954        }
955
956        $ids = $queryBuilder->caller( __METHOD__ )->fetchFieldValues() ?: [];
957        return UserArray::newFromIDs( $ids );
958    }
959
960    /**
961     * Is the input a valid password for this user?
962     *
963     * @param string $password Desired password
964     * @return bool
965     */
966    public function isValidPassword( $password ) {
967        // simple boolean wrapper for checkPasswordValidity
968        return $this->checkPasswordValidity( $password )->isGood();
969    }
970
971    /**
972     * Check if this is a valid password for this user
973     *
974     * Returns a Status object with a set of messages describing
975     * problems with the password. If the return status is fatal,
976     * the action should be refused and the password should not be
977     * checked at all (this is mainly meant for DoS mitigation).
978     * If the return value is OK but not good, the password can be checked,
979     * but the user should not be able to set their password to this.
980     * The value of the returned Status object will be an array which
981     * can have the following fields:
982     * - forceChange (bool): if set to true, the user should not be
983     *   allowed to log with this password unless they change it during
984     *   the login process (see ResetPasswordSecondaryAuthenticationProvider).
985     * - suggestChangeOnLogin (bool): if set to true, the user should be prompted for
986     *   a password change on login.
987     *
988     * @param string $password Desired password
989     * @return Status
990     * @since 1.23
991     */
992    public function checkPasswordValidity( $password ) {
993        $passwordPolicy = MediaWikiServices::getInstance()->getMainConfig()
994            ->get( MainConfigNames::PasswordPolicy );
995
996        $upp = new UserPasswordPolicy(
997            $passwordPolicy['policies'],
998            $passwordPolicy['checks']
999        );
1000
1001        $status = Status::newGood( [] );
1002        $result = false; // init $result to false for the internal checks
1003
1004        if ( !$this->getHookRunner()->onIsValidPassword( $password, $result, $this ) ) {
1005            $status->error( $result );
1006            return $status;
1007        }
1008
1009        if ( $result === false ) {
1010            $status->merge( $upp->checkUserPassword( $this, $password ), true );
1011            return $status;
1012        }
1013
1014        if ( $result === true ) {
1015            return $status;
1016        }
1017
1018        $status->error( $result );
1019        return $status; // the isValidPassword hook set a string $result and returned true
1020    }
1021
1022    /**
1023     * Set cached properties to default.
1024     *
1025     * @note This no longer clears uncached lazy-initialised properties;
1026     *       the constructor does that instead.
1027     *
1028     * @param string|false $name
1029     * @param int|null $actorId
1030     */
1031    public function loadDefaults( $name = false, $actorId = null ) {
1032        $this->mId = 0;
1033        $this->mName = $name;
1034        $this->mActorId = $actorId;
1035        $this->mRealName = '';
1036        $this->mEmail = '';
1037        $this->isTemp = null;
1038
1039        $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1040            ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1041        if ( $loggedOut !== 0 ) {
1042            $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1043        } else {
1044            $this->mTouched = '1'; # Allow any pages to be cached
1045        }
1046
1047        $this->mToken = null; // Don't run cryptographic functions till we need a token
1048        $this->mEmailAuthenticated = null;
1049        $this->mEmailToken = '';
1050        $this->mEmailTokenExpires = null;
1051        $this->mRegistration = wfTimestamp( TS_MW );
1052
1053        $this->getHookRunner()->onUserLoadDefaults( $this, $name );
1054    }
1055
1056    /**
1057     * Return whether an item has been loaded.
1058     *
1059     * @param string $item Item to check. Current possibilities:
1060     *   - id
1061     *   - name
1062     *   - realname
1063     * @param string $all 'all' to check if the whole object has been loaded
1064     *   or any other string to check if only the item is available (e.g.
1065     *   for optimisation)
1066     * @return bool
1067     */
1068    public function isItemLoaded( $item, $all = 'all' ) {
1069        return ( $this->mLoadedItems === true && $all === 'all' ) ||
1070            ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1071    }
1072
1073    /**
1074     * Set that an item has been loaded
1075     *
1076     * @internal Only public for use in UserFactory
1077     *
1078     * @param string $item
1079     */
1080    public function setItemLoaded( $item ) {
1081        if ( is_array( $this->mLoadedItems ) ) {
1082            $this->mLoadedItems[$item] = true;
1083        }
1084    }
1085
1086    /**
1087     * Load user data from the session.
1088     *
1089     * @return bool True if the user is logged in, false otherwise.
1090     */
1091    private function loadFromSession() {
1092        // MediaWiki\Session\Session already did the necessary authentication of the user
1093        // returned here, so just use it if applicable.
1094        $session = $this->getRequest()->getSession();
1095        $user = $session->getUser();
1096        if ( $user->isRegistered() ) {
1097            $this->loadFromUserObject( $user );
1098
1099            // Other code expects these to be set in the session, so set them.
1100            $session->set( 'wsUserID', $this->getId() );
1101            $session->set( 'wsUserName', $this->getName() );
1102            $session->set( 'wsToken', $this->getToken() );
1103
1104            return true;
1105        }
1106
1107        return false;
1108    }
1109
1110    /**
1111     * Load user data from the database.
1112     * $this->mId must be set, this is how the user is identified.
1113     *
1114     * @param int $flags IDBAccessObject::READ_* constant bitfield
1115     * @return bool True if the user exists, false if the user is anonymous
1116     */
1117    public function loadFromDatabase( $flags = IDBAccessObject::READ_LATEST ) {
1118        // Paranoia
1119        $this->mId = intval( $this->mId );
1120
1121        if ( !$this->mId ) {
1122            // Anonymous users are not in the database
1123            $this->loadDefaults();
1124            return false;
1125        }
1126
1127        $db = DBAccessObjectUtils::getDBFromRecency(
1128            MediaWikiServices::getInstance()->getDBLoadBalancerFactory(),
1129            $flags
1130        );
1131        $row = self::newQueryBuilder( $db )
1132            ->where( [ 'user_id' => $this->mId ] )
1133            ->recency( $flags )
1134            ->caller( __METHOD__ )
1135            ->fetchRow();
1136
1137        $this->queryFlagsUsed = $flags;
1138
1139        if ( $row !== false ) {
1140            // Initialise user table data
1141            $this->loadFromRow( $row );
1142            return true;
1143        }
1144
1145        // Invalid user_id
1146        $this->mId = 0;
1147        $this->loadDefaults( 'Unknown user' );
1148
1149        return false;
1150    }
1151
1152    /**
1153     * Initialize this object from a row from the user table.
1154     *
1155     * @param stdClass $row Row from the user table to load.
1156     * @param array|null $data Further user data to load into the object
1157     *
1158     *  user_groups   Array of arrays or stdClass result rows out of the user_groups
1159     *                table. Previously you were supposed to pass an array of strings
1160     *                here, but we also need expiry info nowadays, so an array of
1161     *                strings is ignored.
1162     */
1163    protected function loadFromRow( $row, $data = null ) {
1164        if ( !is_object( $row ) ) {
1165            throw new InvalidArgumentException( '$row must be an object' );
1166        }
1167
1168        $all = true;
1169
1170        if ( isset( $row->actor_id ) ) {
1171            $this->mActorId = (int)$row->actor_id;
1172            if ( $this->mActorId !== 0 ) {
1173                $this->mFrom = 'actor';
1174            }
1175            $this->setItemLoaded( 'actor' );
1176        } else {
1177            $all = false;
1178        }
1179
1180        if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1181            $this->mName = $row->user_name;
1182            $this->mFrom = 'name';
1183            $this->setItemLoaded( 'name' );
1184        } else {
1185            $all = false;
1186        }
1187
1188        if ( isset( $row->user_real_name ) ) {
1189            $this->mRealName = $row->user_real_name;
1190            $this->setItemLoaded( 'realname' );
1191        } else {
1192            $all = false;
1193        }
1194
1195        if ( isset( $row->user_id ) ) {
1196            $this->mId = intval( $row->user_id );
1197            if ( $this->mId !== 0 ) {
1198                $this->mFrom = 'id';
1199            }
1200            $this->setItemLoaded( 'id' );
1201        } else {
1202            $all = false;
1203        }
1204
1205        if ( isset( $row->user_editcount ) ) {
1206            // Don't try to set edit count for anonymous users
1207            // We check the id here and not in UserEditTracker because calling
1208            // User::getId() can trigger some other loading. This will result in
1209            // discarding the user_editcount field for rows if the id wasn't set.
1210            if ( $this->mId !== null && $this->mId !== 0 ) {
1211                MediaWikiServices::getInstance()
1212                    ->getUserEditTracker()
1213                    ->setCachedUserEditCount( $this, (int)$row->user_editcount );
1214            }
1215        } else {
1216            $all = false;
1217        }
1218
1219        if ( isset( $row->user_touched ) ) {
1220            $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1221        } else {
1222            $all = false;
1223        }
1224
1225        if ( isset( $row->user_token ) ) {
1226            // The definition for the column is binary(32), so trim the NULs
1227            // that appends. The previous definition was char(32), so trim
1228            // spaces too.
1229            $this->mToken = rtrim( $row->user_token, " \0" );
1230            if ( $this->mToken === '' ) {
1231                $this->mToken = null;
1232            }
1233        } else {
1234            $all = false;
1235        }
1236
1237        if ( isset( $row->user_email ) ) {
1238            $this->mEmail = $row->user_email;
1239            $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1240            $this->mEmailToken = $row->user_email_token;
1241            $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1242            $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1243        } else {
1244            $all = false;
1245        }
1246
1247        if ( $all ) {
1248            $this->mLoadedItems = true;
1249        }
1250
1251        if ( is_array( $data ) ) {
1252
1253            if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1254                MediaWikiServices::getInstance()
1255                    ->getUserGroupManager()
1256                    ->loadGroupMembershipsFromArray(
1257                        $this,
1258                        $data['user_groups'],
1259                        $this->queryFlagsUsed
1260                    );
1261            }
1262        }
1263    }
1264
1265    /**
1266     * Load the data for this user object from another user object.
1267     *
1268     * @param User $user
1269     */
1270    protected function loadFromUserObject( $user ) {
1271        $user->load();
1272        foreach ( self::$mCacheVars as $var ) {
1273            $this->$var = $user->$var;
1274        }
1275    }
1276
1277    /**
1278     * Builds update conditions. Additional conditions may be added to $conditions to
1279     * protected against race conditions using a compare-and-set (CAS) mechanism
1280     * based on comparing $this->mTouched with the user_touched field.
1281     *
1282     * @param IReadableDatabase $db
1283     * @param array $conditions WHERE conditions for use with Database::update
1284     * @return array WHERE conditions for use with Database::update
1285     */
1286    protected function makeUpdateConditions( IReadableDatabase $db, array $conditions ) {
1287        if ( $this->mTouched ) {
1288            // CAS check: only update if the row wasn't changed since it was loaded.
1289            $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1290        }
1291
1292        return $conditions;
1293    }
1294
1295    /**
1296     * Bump user_touched if it didn't change since this object was loaded
1297     *
1298     * On success, the mTouched field is updated.
1299     * The user serialization cache is always cleared.
1300     *
1301     * @internal
1302     * @return bool Whether user_touched was actually updated
1303     * @since 1.26
1304     */
1305    public function checkAndSetTouched() {
1306        $this->load();
1307
1308        if ( !$this->mId ) {
1309            return false; // anon
1310        }
1311
1312        // Get a new user_touched that is higher than the old one
1313        $newTouched = $this->newTouchedTimestamp();
1314
1315        $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
1316        $dbw->newUpdateQueryBuilder()
1317            ->update( 'user' )
1318            ->set( [ 'user_touched' => $dbw->timestamp( $newTouched ) ] )
1319            ->where( $this->makeUpdateConditions( $dbw, [
1320                'user_id' => $this->mId,
1321            ] ) )
1322            ->caller( __METHOD__ )->execute();
1323        $success = ( $dbw->affectedRows() > 0 );
1324
1325        if ( $success ) {
1326            $this->mTouched = $newTouched;
1327            $this->clearSharedCache( 'changed' );
1328        } else {
1329            // Clears on failure too since that is desired if the cache is stale
1330            $this->clearSharedCache( 'refresh' );
1331        }
1332
1333        return $success;
1334    }
1335
1336    /**
1337     * Clear various cached data stored in this object. The cache of the user table
1338     * data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given.
1339     *
1340     * @param bool|string $reloadFrom Reload user and user_groups table data from a
1341     *   given source. May be "name", "id", "actor", "defaults", "session", or false for no reload.
1342     */
1343    public function clearInstanceCache( $reloadFrom = false ) {
1344        global $wgFullyInitialised;
1345
1346        $this->mDatePreference = null;
1347        $this->mHash = false;
1348        $this->mThisAsAuthority = null;
1349
1350        if ( $wgFullyInitialised && $this->mFrom ) {
1351            $services = MediaWikiServices::getInstance();
1352
1353            if ( $services->peekService( 'PermissionManager' ) ) {
1354                $services->getPermissionManager()->invalidateUsersRightsCache( $this );
1355            }
1356
1357            if ( $services->peekService( 'UserOptionsManager' ) ) {
1358                $services->getUserOptionsManager()->clearUserOptionsCache( $this );
1359            }
1360
1361            if ( $services->peekService( 'TalkPageNotificationManager' ) ) {
1362                $services->getTalkPageNotificationManager()->clearInstanceCache( $this );
1363            }
1364
1365            if ( $services->peekService( 'UserGroupManager' ) ) {
1366                $services->getUserGroupManager()->clearCache( $this );
1367            }
1368
1369            if ( $services->peekService( 'UserEditTracker' ) ) {
1370                $services->getUserEditTracker()->clearUserEditCache( $this );
1371            }
1372
1373            if ( $services->peekService( 'BlockManager' ) ) {
1374                $services->getBlockManager()->clearUserCache( $this );
1375            }
1376        }
1377
1378        if ( $reloadFrom ) {
1379            if ( in_array( $reloadFrom, [ 'name', 'id', 'actor' ] ) ) {
1380                $this->mLoadedItems = [ $reloadFrom => true ];
1381            } else {
1382                $this->mLoadedItems = [];
1383            }
1384            $this->mFrom = $reloadFrom;
1385        }
1386    }
1387
1388    /**
1389     * Is this user subject to rate limiting?
1390     *
1391     * @return bool True if rate limited
1392     */
1393    public function isPingLimitable() {
1394        $limiter = MediaWikiServices::getInstance()->getRateLimiter();
1395        $subject = $this->toRateLimitSubject();
1396        return !$limiter->isExempt( $subject );
1397    }
1398
1399    /**
1400     * Primitive rate limits: enforce maximum actions per time period
1401     * to put a brake on flooding.
1402     *
1403     * The method generates both a generic profiling point and a per action one
1404     * (suffix being "-$action").
1405     *
1406     * @note When using a shared cache like memcached, IP-address
1407     * last-hit counters will be shared across wikis.
1408     *
1409     * @param string $action Action to enforce; 'edit' if unspecified
1410     * @param int $incrBy Positive amount to increment counter by [defaults to 1]
1411     *
1412     * @return bool True if a rate limiter was tripped
1413     */
1414    public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1415        return $this->getThisAsAuthority()->limit( $action, $incrBy, null );
1416    }
1417
1418    /**
1419     * @internal for use by UserAuthority only!
1420     * @return RateLimitSubject
1421     */
1422    public function toRateLimitSubject(): RateLimitSubject {
1423        $flags = [
1424            'exempt' => $this->isAllowed( 'noratelimit' ),
1425            'newbie' => $this->isNewbie(),
1426        ];
1427
1428        return new RateLimitSubject( $this, $this->getRequest()->getIP(), $flags );
1429    }
1430
1431    /**
1432     * Get the block affecting the user, or null if the user is not blocked
1433     *
1434     * @param int|bool $freshness One of the IDBAccessObject::READ_XXX constants.
1435     *                 For backwards compatibility, a boolean is also accepted,
1436     *                 with true meaning READ_NORMAL and false meaning
1437     *                 READ_LATEST.
1438     * @param bool $disableIpBlockExemptChecking This is used internally to prevent
1439     *   a infinite recursion with autopromote. See T270145.
1440     *
1441     * @return ?AbstractBlock
1442     */
1443    public function getBlock(
1444        $freshness = IDBAccessObject::READ_NORMAL,
1445        $disableIpBlockExemptChecking = false
1446    ): ?Block {
1447        if ( is_bool( $freshness ) ) {
1448            $fromReplica = $freshness;
1449        } else {
1450            $fromReplica = ( $freshness !== IDBAccessObject::READ_LATEST );
1451        }
1452
1453        if ( $disableIpBlockExemptChecking ) {
1454            $isExempt = false;
1455        } else {
1456            $isExempt = $this->isAllowed( 'ipblock-exempt' );
1457        }
1458
1459        // TODO: Block checking shouldn't really be done from the User object. Block
1460        // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1461        // which need more knowledge of the request context than the User should have.
1462        // Since we do currently check blocks from the User, we have to do the following
1463        // here:
1464        // - Check if this is the user associated with the main request
1465        // - If so, pass the relevant request information to the block manager
1466        $request = null;
1467        if ( !$isExempt && $this->isGlobalSessionUser() ) {
1468            // This is the global user, so we need to pass the request
1469            $request = $this->getRequest();
1470        }
1471
1472        return MediaWikiServices::getInstance()->getBlockManager()->getBlock(
1473            $this,
1474            $request,
1475            $fromReplica,
1476        );
1477    }
1478
1479    /**
1480     * Check if user is blocked on all wikis.
1481     * Do not use for actual edit permission checks!
1482     * This is intended for quick UI checks.
1483     *
1484     * @param string $ip IP address, uses current client if none given
1485     * @return bool True if blocked, false otherwise
1486     * @deprecated since 1.40, emits deprecation warnings since 1.43. Use getBlock instead.
1487     */
1488    public function isBlockedGlobally( $ip = '' ) {
1489        wfDeprecated( __METHOD__, '1.40' );
1490        return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
1491    }
1492
1493    /**
1494     * Check if user is blocked on all wikis.
1495     * Do not use for actual edit permission checks!
1496     * This is intended for quick UI checks.
1497     *
1498     * @param string $ip IP address, uses current client if none given
1499     * @return AbstractBlock|null Block object if blocked, null otherwise
1500     * @deprecated since 1.40. Use getBlock instead
1501     */
1502    public function getGlobalBlock( $ip = '' ) {
1503        if ( $this->mGlobalBlock !== null ) {
1504            return $this->mGlobalBlock ?: null;
1505        }
1506        // User is already an IP?
1507        if ( IPUtils::isIPAddress( $this->getName() ) ) {
1508            $ip = $this->getName();
1509        } elseif ( !$ip ) {
1510            $ip = $this->getRequest()->getIP();
1511        }
1512        $blocked = false;
1513        $block = null;
1514        $this->getHookRunner()->onUserIsBlockedGlobally( $this, $ip, $blocked, $block );
1515
1516        if ( $blocked && $block === null ) {
1517            // back-compat: UserIsBlockedGlobally didn't have $block param first
1518            $block = new SystemBlock( [
1519                'address' => $ip,
1520                'systemBlock' => 'global-block'
1521            ] );
1522        }
1523
1524        $this->mGlobalBlock = $blocked ? $block : false;
1525        return $this->mGlobalBlock ?: null;
1526    }
1527
1528    /**
1529     * Check if user account is locked
1530     *
1531     * @return bool True if locked, false otherwise
1532     */
1533    public function isLocked() {
1534        if ( $this->mLocked !== null ) {
1535            return $this->mLocked;
1536        }
1537        // Reset for hook
1538        $this->mLocked = false;
1539        $this->getHookRunner()->onUserIsLocked( $this, $this->mLocked );
1540        return $this->mLocked;
1541    }
1542
1543    /**
1544     * Check if user account is hidden
1545     *
1546     * @return bool True if hidden, false otherwise
1547     */
1548    public function isHidden() {
1549        $block = $this->getBlock();
1550        return $block ? $block->getHideName() : false;
1551    }
1552
1553    /**
1554     * Get the user's ID.
1555     * @param string|false $wikiId The wiki ID expected by the caller.
1556     * @return int The user's ID; 0 if the user is anonymous or nonexistent
1557     */
1558    public function getId( $wikiId = self::LOCAL ): int {
1559        $this->assertWiki( $wikiId );
1560        if ( $this->mId === null && $this->mName !== null ) {
1561            $userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils();
1562            if ( $userNameUtils->isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) ) {
1563                // Special case, we know the user is anonymous
1564                // Note that "external" users are "local" (they have an actor ID that is relative to
1565                // the local wiki).
1566                return 0;
1567            }
1568        }
1569
1570        if ( !$this->isItemLoaded( 'id' ) ) {
1571            // Don't load if this was initialized from an ID
1572            $this->load();
1573        }
1574
1575        return (int)$this->mId;
1576    }
1577
1578    /**
1579     * Set the user and reload all fields according to a given ID
1580     * @param int $v User ID to reload
1581     */
1582    public function setId( $v ) {
1583        $this->mId = $v;
1584        $this->clearInstanceCache( 'id' );
1585    }
1586
1587    /**
1588     * Get the user name, or the IP of an anonymous user
1589     * @return string User's name or IP address
1590     */
1591    public function getName(): string {
1592        if ( $this->isItemLoaded( 'name', 'only' ) ) {
1593            // Special case optimisation
1594            return $this->mName;
1595        }
1596
1597        $this->load();
1598        if ( $this->mName === false ) {
1599            // Clean up IPs
1600            $this->mName = IPUtils::sanitizeIP( $this->getRequest()->getIP() );
1601        }
1602
1603        return $this->mName;
1604    }
1605
1606    /**
1607     * Set the user name.
1608     *
1609     * This does not reload fields from the database according to the given
1610     * name. Rather, it is used to create a temporary "nonexistent user" for
1611     * later addition to the database. It can also be used to set the IP
1612     * address for an anonymous user to something other than the current
1613     * remote IP.
1614     *
1615     * @note User::newFromName() has roughly the same function, when the named user
1616     * does not exist.
1617     * @param string $str New user name to set
1618     */
1619    public function setName( $str ) {
1620        $this->load();
1621        $this->mName = $str;
1622    }
1623
1624    /**
1625     * Get the user's actor ID.
1626     * @since 1.31
1627     * @note This method was removed from the UserIdentity interface in 1.36,
1628     *       but remains supported in the User class for now.
1629     *       New code should use ActorNormalization::findActorId() or
1630     *       ActorNormalization::acquireActorId() instead.
1631     * @param IDatabase|string|false $dbwOrWikiId Deprecated since 1.36.
1632     *        If a database connection is passed, a new actor ID is assigned if needed.
1633     *        ActorNormalization::acquireActorId() should be used for that purpose instead.
1634     * @return int The actor's ID, or 0 if no actor ID exists and $dbw was null
1635     * @throws PreconditionException if $dbwOrWikiId is a string and does not match the local wiki
1636     */
1637    public function getActorId( $dbwOrWikiId = self::LOCAL ): int {
1638        if ( $dbwOrWikiId ) {
1639            wfDeprecatedMsg( 'Passing a parameter to getActorId() is deprecated', '1.36' );
1640        }
1641
1642        if ( is_string( $dbwOrWikiId ) ) {
1643            $this->assertWiki( $dbwOrWikiId );
1644        }
1645
1646        if ( !$this->isItemLoaded( 'actor' ) ) {
1647            $this->load();
1648        }
1649
1650        if ( !$this->mActorId && $dbwOrWikiId instanceof IDatabase ) {
1651            MediaWikiServices::getInstance()
1652                ->getActorStoreFactory()
1653                ->getActorNormalization( $dbwOrWikiId->getDomainID() )
1654                ->acquireActorId( $this, $dbwOrWikiId );
1655            // acquireActorId will call setActorId on $this
1656            Assert::postcondition(
1657                $this->mActorId !== null,
1658                "Failed to acquire actor ID for user id {$this->mId} name {$this->mName}"
1659            );
1660        }
1661
1662        return (int)$this->mActorId;
1663    }
1664
1665    /**
1666     * Sets the actor id.
1667     * For use by ActorStore only.
1668     * Should be removed once callers of getActorId() have been migrated to using ActorNormalization.
1669     *
1670     * @internal
1671     * @deprecated since 1.36
1672     * @param int $actorId
1673     */
1674    public function setActorId( int $actorId ) {
1675        $this->mActorId = $actorId;
1676        $this->setItemLoaded( 'actor' );
1677    }
1678
1679    /**
1680     * Get the user's name escaped by underscores.
1681     * @return string Username escaped by underscores.
1682     */
1683    public function getTitleKey(): string {
1684        return str_replace( ' ', '_', $this->getName() );
1685    }
1686
1687    /**
1688     * Generate a current or new-future timestamp to be stored in the
1689     * user_touched field when we update things.
1690     *
1691     * @return string Timestamp in TS_MW format
1692     */
1693    private function newTouchedTimestamp() {
1694        $time = (int)ConvertibleTimestamp::now( TS_UNIX );
1695        if ( $this->mTouched ) {
1696            $time = max( $time, (int)ConvertibleTimestamp::convert( TS_UNIX, $this->mTouched ) + 1 );
1697        }
1698
1699        return ConvertibleTimestamp::convert( TS_MW, $time );
1700    }
1701
1702    /**
1703     * Clear user data from memcached
1704     *
1705     * Use after applying updates to the database; caller's
1706     * responsibility to update user_touched if appropriate.
1707     *
1708     * Called implicitly from invalidateCache() and saveSettings().
1709     *
1710     * @param string $mode Use 'refresh' to clear now or 'changed' to clear before DB commit
1711     */
1712    public function clearSharedCache( $mode = 'refresh' ) {
1713        if ( !$this->getId() ) {
1714            return;
1715        }
1716
1717        $dbProvider = MediaWikiServices::getInstance()->getConnectionProvider();
1718        $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1719        $key = $this->getCacheKey( $cache );
1720
1721        if ( $mode === 'refresh' ) {
1722            $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
1723        } else {
1724            $dbProvider->getPrimaryDatabase()->onTransactionPreCommitOrIdle(
1725                static function () use ( $cache, $key ) {
1726                    $cache->delete( $key );
1727                },
1728                __METHOD__
1729            );
1730        }
1731    }
1732
1733    /**
1734     * Immediately touch the user data cache for this account
1735     *
1736     * Calls touch() and removes account data from memcached
1737     */
1738    public function invalidateCache() {
1739        $this->touch();
1740        $this->clearSharedCache( 'changed' );
1741    }
1742
1743    /**
1744     * Update the "touched" timestamp for the user
1745     *
1746     * This is useful on various login/logout events when making sure that
1747     * a browser or proxy that has multiple tenants does not suffer cache
1748     * pollution where the new user sees the old users content. The value
1749     * of getTouched() is checked when determining 304 vs 200 responses.
1750     * Unlike invalidateCache(), this preserves the User object cache and
1751     * avoids database writes.
1752     *
1753     * @since 1.25
1754     */
1755    public function touch() {
1756        $id = $this->getId();
1757        if ( $id ) {
1758            $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1759            $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
1760            $cache->touchCheckKey( $key );
1761            $this->mQuickTouched = null;
1762        }
1763    }
1764
1765    /**
1766     * Validate the cache for this account.
1767     * @param string $timestamp A timestamp in TS_MW format
1768     * @return bool
1769     */
1770    public function validateCache( $timestamp ) {
1771        return ( $timestamp >= $this->getTouched() );
1772    }
1773
1774    /**
1775     * Get the user touched timestamp
1776     *
1777     * Use this value only to validate caches via inequalities
1778     * such as in the case of HTTP If-Modified-Since response logic
1779     *
1780     * @return string TS_MW Timestamp
1781     */
1782    public function getTouched() {
1783        $this->load();
1784
1785        if ( $this->mId ) {
1786            if ( $this->mQuickTouched === null ) {
1787                $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1788                $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
1789
1790                $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
1791            }
1792
1793            return max( $this->mTouched, $this->mQuickTouched );
1794        }
1795
1796        return $this->mTouched;
1797    }
1798
1799    /**
1800     * Get the user_touched timestamp field (time of last DB updates)
1801     * @return string TS_MW Timestamp
1802     * @since 1.26
1803     */
1804    public function getDBTouched() {
1805        $this->load();
1806
1807        return $this->mTouched;
1808    }
1809
1810    /**
1811     * Changes credentials of the user.
1812     *
1813     * This is a convenience wrapper around AuthManager::changeAuthenticationData.
1814     * Note that this can return a status that isOK() but not isGood() on certain types of failures,
1815     * e.g. when no provider handled the change.
1816     *
1817     * @param array $data A set of authentication data in fieldname => value format. This is the
1818     *   same data you would pass the changeauthenticationdata API - 'username', 'password' etc.
1819     * @return Status
1820     * @since 1.27
1821     */
1822    public function changeAuthenticationData( array $data ) {
1823        $manager = MediaWikiServices::getInstance()->getAuthManager();
1824        $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
1825        $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
1826
1827        $status = Status::newGood( 'ignored' );
1828        foreach ( $reqs as $req ) {
1829            $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
1830        }
1831        if ( $status->getValue() === 'ignored' ) {
1832            $status->warning( 'authenticationdatachange-ignored' );
1833        }
1834
1835        if ( $status->isGood() ) {
1836            foreach ( $reqs as $req ) {
1837                $manager->changeAuthenticationData( $req );
1838            }
1839        }
1840        return $status;
1841    }
1842
1843    /**
1844     * Get the user's current token.
1845     * @param bool $forceCreation Force the generation of a new token if the
1846     *   user doesn't have one (default=true for backwards compatibility).
1847     * @return string|null Token
1848     */
1849    public function getToken( $forceCreation = true ) {
1850        $authenticationTokenVersion = MediaWikiServices::getInstance()
1851            ->getMainConfig()->get( MainConfigNames::AuthenticationTokenVersion );
1852
1853        $this->load();
1854        if ( !$this->mToken && $forceCreation ) {
1855            $this->setToken();
1856        }
1857
1858        if ( !$this->mToken ) {
1859            // The user doesn't have a token, return null to indicate that.
1860            return null;
1861        }
1862
1863        if ( $this->mToken === self::INVALID_TOKEN ) {
1864            // We return a random value here so existing token checks are very
1865            // likely to fail.
1866            return MWCryptRand::generateHex( self::TOKEN_LENGTH );
1867        }
1868
1869        if ( $authenticationTokenVersion === null ) {
1870            // $wgAuthenticationTokenVersion not in use, so return the raw secret
1871            return $this->mToken;
1872        }
1873
1874        // $wgAuthenticationTokenVersion in use, so hmac it.
1875        $ret = MWCryptHash::hmac( $authenticationTokenVersion, $this->mToken, false );
1876
1877        // The raw hash can be overly long. Shorten it up.
1878        $len = max( 32, self::TOKEN_LENGTH );
1879        if ( strlen( $ret ) < $len ) {
1880            // Should never happen, even md5 is 128 bits
1881            throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
1882        }
1883
1884        return substr( $ret, -$len );
1885    }
1886
1887    /**
1888     * Set the random token (used for persistent authentication)
1889     * Called from loadDefaults() among other places.
1890     *
1891     * @param string|false $token If specified, set the token to this value
1892     */
1893    public function setToken( $token = false ) {
1894        $this->load();
1895        if ( $this->mToken === self::INVALID_TOKEN ) {
1896            LoggerFactory::getInstance( 'session' )
1897                ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
1898        } elseif ( !$token ) {
1899            $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
1900        } else {
1901            $this->mToken = $token;
1902        }
1903    }
1904
1905    /**
1906     * Get the user's e-mail address
1907     * @return string User's email address
1908     */
1909    public function getEmail(): string {
1910        $this->load();
1911        $email = $this->mEmail;
1912        $this->getHookRunner()->onUserGetEmail( $this, $email );
1913        // In case a hook handler returns e.g. null
1914        $this->mEmail = is_string( $email ) ? $email : '';
1915        return $this->mEmail;
1916    }
1917
1918    /**
1919     * Get the timestamp of the user's e-mail authentication
1920     * @return string TS_MW timestamp
1921     */
1922    public function getEmailAuthenticationTimestamp() {
1923        $this->load();
1924        $this->getHookRunner()->onUserGetEmailAuthenticationTimestamp(
1925            $this, $this->mEmailAuthenticated );
1926        return $this->mEmailAuthenticated;
1927    }
1928
1929    /**
1930     * Set the user's e-mail address
1931     * @param string $str New e-mail address
1932     */
1933    public function setEmail( string $str ) {
1934        $this->load();
1935        if ( $str == $this->getEmail() ) {
1936            return;
1937        }
1938        $this->invalidateEmail();
1939        $this->mEmail = $str;
1940        $this->getHookRunner()->onUserSetEmail( $this, $this->mEmail );
1941    }
1942
1943    /**
1944     * Set the user's e-mail address and send a confirmation mail if needed.
1945     *
1946     * @since 1.20
1947     * @param string $str New e-mail address
1948     * @return Status
1949     */
1950    public function setEmailWithConfirmation( string $str ) {
1951        $config = MediaWikiServices::getInstance()->getMainConfig();
1952        $enableEmail = $config->get( MainConfigNames::EnableEmail );
1953
1954        if ( !$enableEmail ) {
1955            return Status::newFatal( 'emaildisabled' );
1956        }
1957
1958        $oldaddr = $this->getEmail();
1959        if ( $str === $oldaddr ) {
1960            return Status::newGood( true );
1961        }
1962
1963        $type = $oldaddr != '' ? 'changed' : 'set';
1964        $notificationResult = null;
1965
1966        $emailAuthentication = $config->get( MainConfigNames::EmailAuthentication );
1967
1968        if ( $emailAuthentication && $type === 'changed' ) {
1969            // Send the user an email notifying the user of the change in registered
1970            // email address on their previous email address
1971            $change = $str != '' ? 'changed' : 'removed';
1972            $notificationResult = $this->sendMail(
1973                wfMessage( 'notificationemail_subject_' . $change )->text(),
1974                wfMessage( 'notificationemail_body_' . $change,
1975                    $this->getRequest()->getIP(),
1976                    $this->getName(),
1977                    $str )->text()
1978            );
1979        }
1980
1981        $this->setEmail( $str );
1982
1983        if ( $str !== '' && $emailAuthentication ) {
1984            // Send a confirmation request to the new address if needed
1985            $result = $this->sendConfirmationMail( $type );
1986
1987            if ( $notificationResult !== null ) {
1988                $result->merge( $notificationResult );
1989            }
1990
1991            if ( $result->isGood() ) {
1992                // Say to the caller that a confirmation and notification mail has been sent
1993                $result->value = 'eauth';
1994            }
1995        } else {
1996            $result = Status::newGood( true );
1997        }
1998
1999        return $result;
2000    }
2001
2002    /**
2003     * Get the user's real name
2004     * @return string User's real name
2005     */
2006    public function getRealName(): string {
2007        if ( !$this->isItemLoaded( 'realname' ) ) {
2008            $this->load();
2009        }
2010
2011        return $this->mRealName;
2012    }
2013
2014    /**
2015     * Set the user's real name
2016     * @param string $str New real name
2017     */
2018    public function setRealName( string $str ) {
2019        $this->load();
2020        $this->mRealName = $str;
2021    }
2022
2023    /**
2024     * Get a token stored in the preferences (like the watchlist one),
2025     * resetting it if it's empty (and saving changes).
2026     *
2027     * @param string $oname The option name to retrieve the token from
2028     * @return string|false User's current value for the option, or false if this option is disabled.
2029     * @see resetTokenFromOption()
2030     * @see getOption()
2031     * @deprecated since 1.26 Applications should use the OAuth extension
2032     */
2033    public function getTokenFromOption( $oname ) {
2034        $hiddenPrefs =
2035            MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::HiddenPrefs );
2036
2037        $id = $this->getId();
2038        if ( !$id || in_array( $oname, $hiddenPrefs ) ) {
2039            return false;
2040        }
2041
2042        $userOptionsLookup = MediaWikiServices::getInstance()
2043            ->getUserOptionsLookup();
2044        $token = $userOptionsLookup->getOption( $this, (string)$oname );
2045        if ( !$token ) {
2046            // Default to a value based on the user token to avoid space
2047            // wasted on storing tokens for all users. When this option
2048            // is set manually by the user, only then is it stored.
2049            $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2050        }
2051
2052        return $token;
2053    }
2054
2055    /**
2056     * Reset a token stored in the preferences (like the watchlist one).
2057     * *Does not* save user's preferences (similarly to UserOptionsManager::setOption()).
2058     *
2059