34 use Wikimedia\Assert\Assert;
36 use Wikimedia\ScopedCallback;
100 'mEmailAuthenticated',
102 'mEmailTokenExpires',
237 return (
string)$this->
getName();
242 if ( $name ===
'mRights' ) {
245 } elseif ( !property_exists( $this, $name ) ) {
257 public function __set( $name, $value ) {
260 if ( $name ===
'mRights' ) {
261 MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
263 is_null( $value ) ? [] : $value
265 } elseif ( !property_exists( $this, $name ) ) {
266 $this->$name = $value;
295 $this->mLoadedItems ===
true || $this->mFrom !==
'session';
303 public function load( $flags = self::READ_NORMAL ) {
306 if ( $this->mLoadedItems ===
true ) {
312 $this->mLoadedItems =
true;
313 $this->queryFlagsUsed = $flags;
318 ->warning(
'User::loadFromSession called before the end of Setup.php', [
319 'exception' =>
new Exception(
'User::loadFromSession called before the end of Setup.php' ),
322 $this->mLoadedItems = $oldLoadedItems;
326 switch ( $this->mFrom ) {
332 if ( $this->mId != 0 ) {
333 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
334 if ( $lb->hasOrMadeRecentMasterChanges() ) {
335 $flags |= self::READ_LATEST;
336 $this->queryFlagsUsed = $flags;
345 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
346 if ( $lb->hasOrMadeRecentMasterChanges() ) {
347 $flags |= self::READ_LATEST;
348 $this->queryFlagsUsed = $flags;
352 $row =
wfGetDB( $index )->selectRow(
354 [
'actor_id',
'actor_user',
'actor_name' ],
355 $this->mFrom ===
'name' ? [
'actor_name' => $this->mName ] : [
'actor_id' => $this->mActorId ],
362 $this->
loadDefaults( $this->mFrom ===
'name' ? $this->mName :
false );
363 } elseif ( $row->actor_user ) {
364 $this->mId = $row->actor_user;
367 $this->
loadDefaults( $row->actor_name, $row->actor_id );
375 Hooks::run(
'UserLoadAfterLoadFromSession', [ $this ] );
378 throw new UnexpectedValueException(
379 "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
389 if ( $this->mId == 0 ) {
407 $this->mLoadedItems =
true;
408 $this->queryFlagsUsed = $flags;
418 public static function purge( $dbDomain, $userId ) {
419 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
420 $key =
$cache->makeGlobalKey(
'user',
'id', $dbDomain, $userId );
430 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
432 return $cache->makeGlobalKey(
'user',
'id', $lbFactory->getLocalDomainID(),
$this->mId );
441 $id = $this->
getId();
443 return $id ? [ $this->
getCacheKey( $cache ) ] : [];
453 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
454 $data =
$cache->getWithSetCallback(
457 function ( $oldValue, &$ttl, array &$setOpts ) use (
$cache ) {
459 wfDebug(
"User: cache miss for user {$this->mId}\n" );
466 foreach ( self::$mCacheVars as $name ) {
467 $data[$name] = $this->$name;
474 foreach ( $this->mGroupMemberships as $ugm ) {
475 if ( $ugm->getExpiry() ) {
476 $secondsUntilExpiry =
wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
477 if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
478 $ttl = $secondsUntilExpiry;
485 [
'pcTTL' => $cache::TTL_PROC_LONG,
'version' =>
self::VERSION ]
489 foreach ( self::$mCacheVars as $name ) {
490 $this->$name = $data[$name];
515 public static function newFromName( $name, $validate =
'valid' ) {
516 if ( $validate ===
true ) {
520 if ( $name ===
false ) {
572 if ( $identity instanceof
User ) {
577 $identity->
getId() === 0 ? null : $identity->
getId(),
596 public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain =
false ) {
600 if ( $dbDomain !==
false ) {
606 $user->mFrom =
'defaults';
608 if ( $actorId !==
null ) {
609 $user->mActorId = (int)$actorId;
610 if ( $user->mActorId !== 0 ) {
611 $user->mFrom =
'actor';
616 if ( $userName !==
null && $userName !==
'' ) {
617 $user->mName = $userName;
618 $user->mFrom =
'name';
619 $user->setItemLoaded(
'name' );
622 if ( $userId !==
null ) {
623 $user->mId = (int)$userId;
624 if ( $user->mId !== 0 ) {
627 $user->setItemLoaded(
'id' );
630 if ( $user->mFrom ===
'defaults' ) {
631 throw new InvalidArgumentException(
632 'Cannot create a user with no name, no ID, and no actor ID'
651 $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
655 $id = $db->selectField(
659 'user_email_token' => md5( $code ),
660 'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
676 $user->mFrom =
'session';
677 $user->mRequest = $request;
739 'validate' =>
'valid',
745 if ( $name ===
false ) {
751 $row =
$dbr->selectRow(
752 $userQuery[
'tables'],
753 $userQuery[
'fields'],
754 [
'user_name' => $name ],
762 $row = $dbw->selectRow(
763 $userQuery[
'tables'],
764 $userQuery[
'fields'],
765 [
'user_name' => $name ],
774 return $options[
'create']
783 if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
784 AuthManager::singleton()->userCanAuthenticate( $name )
787 if ( !$options[
'steal'] ) {
791 AuthManager::singleton()->revokeAccessForUser( $name );
793 $user->invalidateEmail();
795 $user->saveSettings();
796 SessionManager::singleton()->preventSessionsForUser( $user->getName() );
809 public static function whoIs( $id ) {
829 public static function idFromName( $name, $flags = self::READ_NORMAL ) {
831 $name = (string)$name;
833 if ( is_null( $nt ) ) {
838 if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
839 return is_null( self::$idCacheByName[$name] ) ? null : (int)self::$idCacheByName[$name];
848 [
'user_name' => $nt->getText() ],
853 if (
$s ===
false ) {
856 $result = (int)
$s->user_id;
859 if ( count( self::$idCacheByName ) >= 1000 ) {
860 self::$idCacheByName = [];
863 self::$idCacheByName[$name] = $result;
872 self::$idCacheByName = [];
891 public static function isIP( $name ) {
892 return preg_match(
'/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
921 || self::isIP( $name )
922 || strpos( $name,
'/' ) !==
false
924 || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
932 if ( is_null( $parsed )
933 || $parsed->getNamespace()
934 || strcmp( $name, $parsed->getPrefixedText() ) ) {
940 $unicodeBlacklist =
'/[' .
941 '\x{0080}-\x{009f}' . # iso-8859-1 control chars
942 '\x{00a0}' . # non-breaking space
943 '\x{2000}-\x{200f}' . # various whitespace
944 '\x{2028}-\x{202f}' . # breaks and control chars
945 '\x{3000}' . # ideographic space
946 '\x{e000}-\x{f8ff}' . #
private use
948 if ( preg_match( $unicodeBlacklist, $name ) ) {
969 if ( !self::isValidUserName( $name ) ) {
973 static $reservedUsernames =
false;
974 if ( !$reservedUsernames ) {
976 Hooks::run(
'UserGetReservedNames', [ &$reservedUsernames ] );
980 foreach ( $reservedUsernames as $reserved ) {
981 if ( substr( $reserved, 0, 4 ) ==
'msg:' ) {
982 $reserved =
wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
984 if ( $reserved == $name ) {
1002 if ( $groups === [] ) {
1006 $groups = array_unique( (array)$groups );
1007 $limit = min( 5000, $limit );
1009 $conds = [
'ug_group' => $groups ];
1010 if ( $after !==
null ) {
1011 $conds[] =
'ug_user > ' . (int)$after;
1015 $ids =
$dbr->selectFieldValues(
1022 'ORDER BY' =>
'ug_user',
1047 if ( strlen( $name ) > 235 ) {
1049 ": '$name' invalid due to length" );
1058 ": '$name' invalid due to wgInvalidUsernameCharacters" );
1108 if ( !
Hooks::run(
'isValidPassword', [ $password, &$result, $this ] ) ) {
1113 if ( $result ===
false ) {
1114 $status->merge( $upp->checkUserPassword( $this, $password ),
true );
1118 if ( $result ===
true ) {
1141 $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1143 # Reject names containing '#'; these will be cleaned up
1144 # with title normalisation, but then it's too late to
1146 if ( strpos( $name,
'#' ) !==
false ) {
1152 $t = ( $validate !== false ) ?
1155 if ( is_null(
$t ) ||
$t->getNamespace() !==
NS_USER ||
$t->isExternal() ) {
1159 $name =
$t->getText();
1161 switch ( $validate ) {
1165 if ( !self::isValidUserName( $name ) ) {
1170 if ( !self::isUsableName( $name ) ) {
1175 if ( !self::isCreatableName( $name ) ) {
1180 throw new InvalidArgumentException(
1181 'Invalid parameter value for $validate in ' . __METHOD__ );
1197 $this->mName = $name;
1198 $this->mActorId = $actorId;
1199 $this->mRealName =
'';
1201 $this->mOptionOverrides =
null;
1202 $this->mOptionsLoaded =
false;
1204 $loggedOut = $this->mRequest && !defined(
'MW_NO_SESSION' )
1205 ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1206 if ( $loggedOut !== 0 ) {
1207 $this->mTouched =
wfTimestamp( TS_MW, $loggedOut );
1209 $this->mTouched =
'1'; # Allow any pages to be cached
1212 $this->mToken =
null;
1213 $this->mEmailAuthenticated =
null;
1214 $this->mEmailToken =
'';
1215 $this->mEmailTokenExpires =
null;
1217 $this->mGroupMemberships = [];
1219 Hooks::run(
'UserLoadDefaults', [ $this, $name ] );
1235 return ( $this->mLoadedItems ===
true && $all ===
'all' ) ||
1236 ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] ===
true );
1245 if ( is_array( $this->mLoadedItems ) ) {
1246 $this->mLoadedItems[$item] =
true;
1258 $session = $this->
getRequest()->getSession();
1259 $user = $session->getUser();
1260 if ( $user->isLoggedIn() ) {
1266 MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1269 $session->set(
'wsUserID', $this->
getId() );
1270 $session->set(
'wsUserName', $this->
getName() );
1271 $session->set(
'wsToken', $this->
getToken() );
1285 MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1297 $this->mId = intval( $this->mId );
1299 if ( !$this->mId ) {
1309 $s = $db->selectRow(
1310 $userQuery[
'tables'],
1311 $userQuery[
'fields'],
1312 [
'user_id' => $this->mId ],
1318 $this->queryFlagsUsed = $flags;
1321 if (
$s !==
false ) {
1324 $this->mGroupMemberships =
null;
1349 if ( !is_object( $row ) ) {
1350 throw new InvalidArgumentException(
'$row must be an object' );
1355 $this->mGroupMemberships =
null;
1357 if ( isset( $row->actor_id ) ) {
1358 $this->mActorId = (int)$row->actor_id;
1359 if ( $this->mActorId !== 0 ) {
1360 $this->mFrom =
'actor';
1367 if ( isset( $row->user_name ) && $row->user_name !==
'' ) {
1368 $this->mName = $row->user_name;
1369 $this->mFrom =
'name';
1375 if ( isset( $row->user_real_name ) ) {
1376 $this->mRealName = $row->user_real_name;
1382 if ( isset( $row->user_id ) ) {
1383 $this->mId = intval( $row->user_id );
1384 if ( $this->mId !== 0 ) {
1385 $this->mFrom =
'id';
1392 if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !==
'' ) {
1393 self::$idCacheByName[$row->user_name] = $row->user_id;
1396 if ( isset( $row->user_editcount ) ) {
1397 $this->mEditCount = $row->user_editcount;
1402 if ( isset( $row->user_touched ) ) {
1403 $this->mTouched =
wfTimestamp( TS_MW, $row->user_touched );
1408 if ( isset( $row->user_token ) ) {
1412 $this->mToken = rtrim( $row->user_token,
" \0" );
1413 if ( $this->mToken ===
'' ) {
1414 $this->mToken =
null;
1420 if ( isset( $row->user_email ) ) {
1421 $this->mEmail = $row->user_email;
1422 $this->mEmailAuthenticated =
wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1423 $this->mEmailToken = $row->user_email_token;
1424 $this->mEmailTokenExpires =
wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1431 $this->mLoadedItems =
true;
1434 if ( is_array( $data ) ) {
1435 if ( isset( $data[
'user_groups'] ) && is_array( $data[
'user_groups'] ) ) {
1436 if ( $data[
'user_groups'] === [] ) {
1437 $this->mGroupMemberships = [];
1439 $firstGroup = reset( $data[
'user_groups'] );
1440 if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1441 $this->mGroupMemberships = [];
1442 foreach ( $data[
'user_groups'] as $row ) {
1444 $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1449 if ( isset( $data[
'user_properties'] ) && is_array( $data[
'user_properties'] ) ) {
1462 foreach ( self::$mCacheVars as $var ) {
1463 $this->$var = $user->$var;
1471 if ( is_null( $this->mGroupMemberships ) ) {
1472 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1502 if ( $toPromote === [] ) {
1512 foreach ( $toPromote as $group ) {
1515 $newGroups = array_merge( $oldGroups, $toPromote );
1519 Hooks::run(
'UserGroupsChanged', [ $this, $toPromote, [],
false,
false, $oldUGMs, $newUGMs ] );
1522 $logEntry->setPerformer( $this );
1524 $logEntry->setParameters( [
1525 '4::oldgroups' => $oldGroups,
1526 '5::newgroups' => $newGroups,
1528 $logid = $logEntry->insert();
1530 $logEntry->publish( $logid );
1546 if ( $this->mTouched ) {
1548 $conditions[
'user_touched'] = $db->
timestamp( $this->mTouched );
1566 if ( !$this->mId ) {
1574 $dbw->update(
'user',
1575 [
'user_touched' => $dbw->timestamp( $newTouched ) ],
1577 'user_id' => $this->mId,
1581 $success = ( $dbw->affectedRows() > 0 );
1584 $this->mTouched = $newTouched;
1604 $this->mNewtalk = -1;
1605 $this->mDatePreference =
null;
1606 $this->mBlockedby = -1; # Unset
1607 $this->mHash =
false;
1608 $this->mEffectiveGroups =
null;
1609 $this->mImplicitGroups =
null;
1610 $this->mGroupMemberships =
null;
1611 $this->mOptions =
null;
1612 $this->mOptionsLoaded =
false;
1613 $this->mEditCount =
null;
1617 MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
1622 if ( $reloadFrom ) {
1623 $this->mLoadedItems = [];
1624 $this->mFrom = $reloadFrom;
1640 Assert::invariant( defined(
'MW_PHPUNIT_TEST' ),
'Unit tests only' );
1641 self::$defOpt =
null;
1642 self::$defOptLang =
null;
1654 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1655 if ( self::$defOpt !==
null && self::$defOptLang === $contLang->getCode() ) {
1664 self::$defOptLang = $contLang->getCode();
1666 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1667 if ( $langCode === $contLang->getCode() ) {
1668 self::$defOpt[
'variant'] = $langCode;
1670 self::$defOpt[
"variant-$langCode"] = $langCode;
1678 self::$defOpt[
'searchNs' . $nsnum] = (bool)$val;
1682 Hooks::run(
'UserGetDefaultOptions', [ &self::$defOpt ] );
1695 return $defOpts[$opt] ??
null;
1708 if ( $this->mBlockedby != -1 ) {
1712 wfDebug( __METHOD__ .
": checking...\n" );
1733 $globalUserName = $sessionUser->isSafeToLoad()
1734 ? $sessionUser->getName()
1737 if ( $this->
getName() === $globalUserName ) {
1743 $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1750 $this->mBlock = $block;
1751 $this->mBlockedby = $block->getByName();
1752 $this->mBlockreason = $block->getReason();
1753 $this->mHideName = $block->getHideName();
1754 $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1756 $this->mBlock =
null;
1757 $this->mBlockedby =
'';
1758 $this->mBlockreason =
'';
1759 $this->mHideName = 0;
1760 $this->mAllowUsertalk =
false;
1766 Hooks::run(
'GetBlockedStatus', [ &$thisUser ],
'1.34' );
1778 return MediaWikiServices::getInstance()->getBlockManager()
1779 ->isDnsBlacklisted( $ip, $checkWhitelist );
1797 $ipReversed = implode(
'.', array_reverse( explode(
'.', $ip ) ) );
1799 foreach ( (array)$bases as
$base ) {
1803 if ( is_array(
$base ) ) {
1804 if ( count(
$base ) >= 2 ) {
1806 $host =
"{$base[1]}.$ipReversed.{$base[0]}";
1808 $host =
"$ipReversed.{$base[0]}";
1810 $basename =
$base[0];
1812 $host =
"$ipReversed.$base";
1816 $ipList = gethostbynamel( $host );
1819 wfDebugLog(
'dnsblacklist',
"Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1824 wfDebugLog(
'dnsblacklist',
"Requested $host, not found in $basename." );
1852 $resultProxyList = [];
1853 $deprecatedIPEntries = [];
1859 if ( $keyIsIP && !$valueIsIP ) {
1860 $deprecatedIPEntries[] = $key;
1861 $resultProxyList[] = $key;
1862 } elseif ( $keyIsIP && $valueIsIP ) {
1863 $deprecatedIPEntries[] = $key;
1864 $resultProxyList[] = $key;
1865 $resultProxyList[] = $value;
1867 $resultProxyList[] = $value;
1871 if ( $deprecatedIPEntries ) {
1873 'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1874 implode(
', ', $deprecatedIPEntries ) .
', please move them to values)',
'1.30' );
1877 $proxyListIPSet =
new IPSet( $resultProxyList );
1878 return $proxyListIPSet->match( $ip );
1894 return !$this->
isAllowed(
'noratelimit' );
1916 if ( !
Hooks::run(
'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1925 $limits = array_merge(
1926 [
'&can-bypass' =>
true ],
1936 $id = $this->
getId();
1943 if ( isset( $limits[
'anon'] ) ) {
1944 $keys[
$cache->makeKey(
'limiter', $action,
'anon' )] = $limits[
'anon'];
1946 } elseif ( isset( $limits[
'user'] ) ) {
1948 $userLimit = $limits[
'user'];
1954 if ( isset( $limits[
'ip'] ) ) {
1956 $keys[
"mediawiki:limiter:$action:ip:$ip"] = $limits[
'ip'];
1959 if ( isset( $limits[
'subnet'] ) ) {
1962 if ( $subnet !==
false ) {
1963 $keys[
"mediawiki:limiter:$action:subnet:$subnet"] = $limits[
'subnet'];
1970 foreach ( $this->
getGroups() as $group ) {
1971 if ( isset( $limits[$group] ) ) {
1972 if ( $userLimit ===
false
1973 || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1975 $userLimit = $limits[$group];
1981 if ( $id !== 0 && $isNewbie && isset( $limits[
'newbie'] ) ) {
1982 $userLimit = $limits[
'newbie'];
1986 if ( $userLimit !==
false ) {
1990 list( $max, $period ) = $userLimit;
1991 wfDebug( __METHOD__ .
": effective user limit: $max in {$period}s\n" );
1992 $keys[
$cache->makeKey(
'limiter', $action,
'user', $id )] = $userLimit;
1996 if ( isset( $limits[
'ip-all'] ) ) {
1999 if ( $isNewbie || $userLimit ===
false
2000 || $limits[
'ip-all'][0] / $limits[
'ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2001 $keys[
"mediawiki:limiter:$action:ip-all:$ip"] = $limits[
'ip-all'];
2006 if ( isset( $limits[
'subnet-all'] ) ) {
2009 if ( $subnet !==
false ) {
2011 if ( $isNewbie || $userLimit ===
false
2012 || $limits[
'ip-all'][0] / $limits[
'ip-all'][1]
2013 > $userLimit[0] / $userLimit[1] ) {
2014 $keys[
"mediawiki:limiter:$action:subnet-all:$subnet"] = $limits[
'subnet-all'];
2020 foreach (
$keys as $key => $limit ) {
2024 list( $max, $period ) = $limit;
2025 $summary =
"(limit $max in {$period}s)";
2026 $count =
$cache->get( $key );
2028 if ( $count && $count >= $max ) {
2029 wfDebugLog(
'ratelimit',
"User '{$this->getName()}' " .
2030 "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2033 wfDebug( __METHOD__ .
": adding record for $key $summary\n" );
2034 if ( $incrBy > 0 ) {
2035 $cache->add( $key, 0, intval( $period ) );
2038 if ( $incrBy > 0 ) {
2039 $cache->incrWithInit( $key, (
int)$period, $incrBy, $incrBy );
2059 $this->
getBlock()->appliesToRight(
'edit' );
2070 return $this->mBlock instanceof
AbstractBlock ? $this->mBlock :
null;
2085 return MediaWikiServices::getInstance()->getPermissionManager()
2086 ->isBlockedFrom( $this,
$title, $fromReplica );
2113 return ( $this->mBlock ? $this->mBlock->getId() : false );
2139 if ( $this->mGlobalBlock !==
null ) {
2140 return $this->mGlobalBlock ?:
null;
2152 Hooks::run(
'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2154 if ( $blocked && $block ===
null ) {
2158 'systemBlock' =>
'global-block'
2162 $this->mGlobalBlock = $blocked ? $block :
false;
2163 return $this->mGlobalBlock ?:
null;
2172 if ( $this->mLocked !==
null ) {
2176 $this->mLocked =
false;
2177 Hooks::run(
'UserIsLocked', [ $this, &$this->mLocked ] );
2187 if ( $this->mHideName !==
null ) {
2191 if ( !$this->mHideName ) {
2193 $this->mHideName =
false;
2194 Hooks::run(
'UserIsHidden', [ $this, &$this->mHideName ],
'1.34' );
2204 if ( $this->mId ===
null && $this->mName !==
null &&
2239 if ( $this->mName ===
false ) {
2262 $this->mName = $str;
2276 if ( !$this->mActorId && $dbw ) {
2278 'actor_user' => $this->
getId() ?:
null,
2279 'actor_name' => (string)$this->
getName(),
2281 if ( $q[
'actor_user'] ===
null && self::isUsableName( $q[
'actor_name'] ) ) {
2283 'Cannot create an actor for a usable name that is not an existing user'
2286 if ( $q[
'actor_name'] ===
'' ) {
2289 $dbw->insert(
'actor', $q, __METHOD__, [
'IGNORE' ] );
2290 if ( $dbw->affectedRows() ) {
2291 $this->mActorId = (int)$dbw->insertId();
2295 $this->mActorId = (int)$dbw->selectField(
2300 [
'LOCK IN SHARE MODE' ]
2302 if ( !$this->mActorId ) {
2304 "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2320 return str_replace(
' ',
'_', $this->
getName() );
2331 if ( $this->mNewtalk === -1 ) {
2332 $this->mNewtalk =
false; # reset talk page status
2336 if ( !$this->mId ) {
2340 $this->mNewtalk =
false;
2345 $this->mNewtalk = $this->
checkNewtalk(
'user_id', $this->mId );
2369 if ( !
Hooks::run(
'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2379 $timestamp =
$dbr->selectField(
'user_newtalk',
2380 'MIN(user_last_timestamp)',
2381 $this->
isAnon() ? [
'user_ip' => $this->
getName() ] : [
'user_id' => $this->
getId() ],
2387 'link' => $utp->getLocalURL(),
2399 $newMessageRevisionId =
null;
2405 if ( $newMessageLinks && count( $newMessageLinks ) === 1
2407 && $newMessageLinks[0][
'rev']
2410 $newMessageRevision = $newMessageLinks[0][
'rev'];
2411 $newMessageRevisionId = $newMessageRevision->getId();
2414 return $newMessageRevisionId;
2428 $ok =
$dbr->selectField(
'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2430 return $ok !==
false;
2442 $prevRev = $curRev ? $curRev->getPrevious() :
false;
2443 $ts = $prevRev ? $prevRev->getTimestamp() :
null;
2446 $dbw->insert(
'user_newtalk',
2447 [ $field => $id,
'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2450 if ( $dbw->affectedRows() ) {
2451 wfDebug( __METHOD__ .
": set on ($field, $id)\n" );
2455 wfDebug( __METHOD__ .
" already set ($field, $id)\n" );
2467 $dbw->delete(
'user_newtalk',
2470 if ( $dbw->affectedRows() ) {
2471 wfDebug( __METHOD__ .
": killed on ($field, $id)\n" );
2475 wfDebug( __METHOD__ .
": already gone ($field, $id)\n" );
2491 $this->mNewtalk = $val;
2498 $id = $this->
getId();
2520 if ( $this->mTouched ) {
2521 $time = max( $time,
wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2538 if ( !$this->
getId() ) {
2542 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2543 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2546 if ( $mode ===
'refresh' ) {
2547 $cache->delete( $key, 1 );
2549 $lb->getConnectionRef(
DB_MASTER )->onTransactionPreCommitOrIdle(
2550 function () use (
$cache, $key ) {
2581 $id = $this->
getId();
2583 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2584 $key =
$cache->makeKey(
'user-quicktouched',
'id', $id );
2585 $cache->touchCheckKey( $key );
2586 $this->mQuickTouched =
null;
2596 return ( $timestamp >= $this->
getTouched() );
2611 if ( $this->mQuickTouched ===
null ) {
2612 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2613 $key =
$cache->makeKey(
'user-quicktouched',
'id', $this->mId );
2618 return max( $this->mTouched, $this->mQuickTouched );
2678 $manager = AuthManager::singleton();
2681 if ( !$manager->userExists( $this->getName() ) ) {
2682 throw new LogicException(
'Cannot set a password for a user that is not in the database.' );
2686 'username' => $this->
getName(),
2692 ->info( __METHOD__ .
': Password change rejected: '
2693 .
$status->getWikiText(
null,
null,
'en' ) );
2697 $this->
setOption(
'watchlisttoken',
false );
2698 SessionManager::singleton()->invalidateSessionsForUser( $this );
2716 $manager = AuthManager::singleton();
2717 $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2718 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2721 foreach ( $reqs as $req ) {
2722 $status->merge( $manager->allowsAuthenticationDataChange( $req ),
true );
2724 if (
$status->getValue() ===
'ignored' ) {
2725 $status->warning(
'authenticationdatachange-ignored' );
2729 foreach ( $reqs as $req ) {
2730 $manager->changeAuthenticationData( $req );
2746 if ( !$this->mToken && $forceCreation ) {
2750 if ( !$this->mToken ) {
2755 if ( $this->mToken === self::INVALID_TOKEN ) {
2770 $len = max( 32, self::TOKEN_LENGTH );
2771 if ( strlen( $ret ) < $len ) {
2773 throw new \UnexpectedValueException(
'Hmac returned less than 128 bits' );
2776 return substr( $ret, -$len );
2787 if ( $this->mToken === self::INVALID_TOKEN ) {
2789 ->debug( __METHOD__ .
": Ignoring attempt to set token for system user \"$this\"" );
2790 } elseif ( !$token ) {
2793 $this->mToken = $token;
2803 Hooks::run(
'UserGetEmail', [ $this, &$this->mEmail ] );
2813 Hooks::run(
'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2823 if ( $str == $this->mEmail ) {
2827 $this->mEmail = $str;
2828 Hooks::run(
'UserSetEmail', [ $this, &$this->mEmail ] );
2846 if ( $str === $oldaddr ) {
2850 $type = $oldaddr !=
'' ?
'changed' :
'set';
2851 $notificationResult =
null;
2856 $change = $str !=
'' ?
'changed' :
'removed';
2857 $notificationResult = $this->
sendMail(
2858 wfMessage(
'notificationemail_subject_' . $change )->text(),
2859 wfMessage(
'notificationemail_body_' . $change,
2872 if ( $notificationResult !==
null ) {
2873 $result->merge( $notificationResult );
2876 if ( $result->isGood() ) {
2878 $result->value =
'eauth';
2905 $this->mRealName = $str;
2918 public function getOption( $oname, $defaultOverride =
null, $ignoreHidden =
false ) {
2922 # We want 'disabled' preferences to always behave as the default value for
2923 # users, even if they have set the option explicitly in their settings (ie they
2924 # set it, and then it was disabled removing their ability to change it). But
2925 # we don't want to erase the preferences in the database in case the preference
2926 # is re-enabled again. So don't touch $mOptions, just override the returned value
2931 if ( array_key_exists( $oname, $this->mOptions ) ) {
2932 return $this->mOptions[$oname];
2935 return $defaultOverride;
2951 # We want 'disabled' preferences to always behave as the default value for
2952 # users, even if they have set the option explicitly in their settings (ie they
2953 # set it, and then it was disabled removing their ability to change it). But
2954 # we don't want to erase the preferences in the database in case the preference
2955 # is re-enabled again. So don't touch $mOptions, just override the returned value
2958 if ( $default !==
null ) {
2959 $options[$pref] = $default;
2963 if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2964 $options = array_diff_assoc( $options, self::getDefaultOptions() );
2978 return (
bool)$this->
getOption( $oname );
2992 $val = $defaultOverride;
2994 return intval( $val );
3009 if ( is_null( $val ) ) {
3013 $this->mOptions[$oname] = $val;
3029 $id = $this->
getId();
3039 $token = hash_hmac(
'sha1',
"$oname:$id", $this->
getToken() );
3091 'registered-multiselect',
3092 'registered-checkmatrix',
3113 if ( $options ===
null ) {
3117 $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3118 $prefs = $preferencesFactory->getFormDescriptor( $this,
$context );
3123 $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(),
true );
3124 foreach ( $specialOptions as $name => $value ) {
3125 unset( $prefs[$name] );
3130 $multiselectOptions = [];
3131 foreach ( $prefs as $name => $info ) {
3132 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'multiselect' ) ||
3133 ( isset( $info[
'class'] ) && $info[
'class'] == HTMLMultiSelectField::class ) ) {
3135 $prefix = $info[
'prefix'] ?? $name;
3137 foreach ( $opts as $value ) {
3138 $multiselectOptions[
"$prefix$value"] =
true;
3141 unset( $prefs[$name] );
3144 $checkmatrixOptions = [];
3145 foreach ( $prefs as $name => $info ) {
3146 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'checkmatrix' ) ||
3147 ( isset( $info[
'class'] ) && $info[
'class'] == HTMLCheckMatrix::class ) ) {
3150 $prefix = $info[
'prefix'] ?? $name;
3152 foreach ( $columns as $column ) {
3153 foreach ( $rows as $row ) {
3154 $checkmatrixOptions[
"$prefix$column-$row"] =
true;
3158 unset( $prefs[$name] );
3163 foreach ( $options as $key => $value ) {
3164 if ( isset( $prefs[$key] ) ) {
3165 $mapping[$key] =
'registered';
3166 } elseif ( isset( $multiselectOptions[$key] ) ) {
3167 $mapping[$key] =
'registered-multiselect';
3168 } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3169 $mapping[$key] =
'registered-checkmatrix';
3170 } elseif ( isset( $specialOptions[$key] ) ) {
3171 $mapping[$key] =
'special';
3172 } elseif ( substr( $key, 0, 7 ) ===
'userjs-' ) {
3173 $mapping[$key] =
'userjs';
3175 $mapping[$key] =
'unused';
3197 $resetKinds = [
'registered',
'registered-multiselect',
'registered-checkmatrix',
'unused' ],
3203 if ( !is_array( $resetKinds ) ) {
3204 $resetKinds = [ $resetKinds ];
3207 if ( in_array(
'all', $resetKinds ) ) {
3208 $newOptions = $defaultOptions;
3215 $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3220 foreach ( $this->mOptions as $key => $value ) {
3221 if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3222 if ( array_key_exists( $key, $defaultOptions ) ) {
3223 $newOptions[$key] = $defaultOptions[$key];
3226 $newOptions[$key] = $value;
3231 Hooks::run(
'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3233 $this->mOptions = $newOptions;
3234 $this->mOptionsLoaded =
true;
3243 if ( is_null( $this->mDatePreference ) ) {
3246 $map =
$wgLang->getDatePreferenceMigrationMap();
3247 if ( isset( $map[$value] ) ) {
3248 $value = $map[$value];
3250 $this->mDatePreference = $value;
3268 Hooks::run(
'UserRequiresHTTPS', [ $this, &$https ] );
3301 return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
3313 return array_keys( $this->mGroupMemberships );
3337 if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3338 $this->mEffectiveGroups = array_unique( array_merge(
3345 Hooks::run(
'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3347 $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3360 if ( $recache || is_null( $this->mImplicitGroups ) ) {
3361 $this->mImplicitGroups = [
'*' ];
3362 if ( $this->
getId() ) {
3363 $this->mImplicitGroups[] =
'user';
3365 $this->mImplicitGroups = array_unique( array_merge(
3366 $this->mImplicitGroups,
3373 $this->mEffectiveGroups =
null;
3391 if ( is_null( $this->mFormerGroups ) ) {
3392 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3395 $res = $db->select(
'user_former_groups',
3397 [
'ufg_user' => $this->mId ],
3399 $this->mFormerGroups = [];
3400 foreach (
$res as $row ) {
3401 $this->mFormerGroups[] = $row->ufg_group;
3413 if ( !$this->
getId() ) {
3417 if ( $this->mEditCount ===
null ) {
3421 $count =
$dbr->selectField(
3422 'user',
'user_editcount',
3423 [
'user_id' => $this->mId ],
3427 if ( $count ===
null ) {
3431 $this->mEditCount = $count;
3455 if ( !
Hooks::run(
'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3461 if ( !$ugm->insert(
true ) ) {
3465 $this->mGroupMemberships[$group] = $ugm;
3470 MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3485 if ( !
Hooks::run(
'UserRemoveGroup', [ $this, &$group ] ) ) {
3491 if ( !$ugm || !$ugm->delete() ) {
3496 unset( $this->mGroupMemberships[$group] );
3501 MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3517 return $this->
getId() != 0;
3546 Hooks::run(
"UserIsBot", [ $this, &$isBot ] );
3561 return MediaWikiServices::getInstance()
3562 ->getPermissionManager()
3563 ->userHasAnyRight( $this, ...$permissions );
3573 return MediaWikiServices::getInstance()
3574 ->getPermissionManager()
3575 ->userHasAllRights( $this, ...$permissions );
3589 return MediaWikiServices::getInstance()->getPermissionManager()
3590 ->userHasRight( $this, $action );
3632 if ( $this->mRequest ) {
3649 if (
$title->isWatchable() && ( !$checkRights || $this->
isAllowed(
'viewmywatchlist' ) ) ) {
3650 return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this,
$title );
3663 if ( !$checkRights || $this->
isAllowed(
'editmywatchlist' ) ) {
3664 MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3680 if ( !$checkRights || $this->
isAllowed(
'editmywatchlist' ) ) {
3681 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3682 $store->removeWatch( $this,
$title->getSubjectPage() );
3683 $store->removeWatch( $this,
$title->getTalkPage() );
3705 if ( !$this->
isAllowed(
'editmywatchlist' ) ) {
3713 if ( !
Hooks::run(
'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3728 $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3729 $oldRev = $rl->getRevisionById( $oldid, Title::READ_LATEST );
3731 $newRev = $rl->getNextRevision( $oldRev );
3760 MediaWikiServices::getInstance()->getWatchedItemStore()
3761 ->resetNotificationTimestamp( $this,
$title, $force, $oldid );
3782 $id = $this->
getId();
3787 $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
3788 $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
3816 $registration > $learnerRegistration ) {
3821 $registration <= $experiencedRegistration
3823 return 'experienced';
3837 public function setCookies( $request =
null, $secure =
null, $rememberMe =
false ) {
3839 if ( $this->mId == 0 ) {
3843 $session = $this->
getRequest()->getSession();
3844 if ( $request && $session->getRequest() !== $request ) {
3845 $session = $session->sessionWithRequest( $request );
3847 $delay = $session->delaySave();
3849 if ( !$session->getUser()->equals( $this ) ) {
3850 if ( !$session->canSetUser() ) {
3852 ->warning( __METHOD__ .
3853 ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3857 $session->setUser( $this );
3860 $session->setRememberUser( $rememberMe );
3861 if ( $secure !==
null ) {
3862 $session->setForceHTTPS( $secure );
3865 $session->persist();
3867 ScopedCallback::consume( $delay );
3876 if (
Hooks::run(
'UserLogout', [ &$user ] ) ) {
3886 $session = $this->
getRequest()->getSession();
3887 if ( !$session->canSetUser() ) {
3889 ->warning( __METHOD__ .
": Cannot log out of an immutable session" );
3890 $error =
'immutable';
3891 } elseif ( !$session->getUser()->equals( $this ) ) {
3893 ->warning( __METHOD__ .
3894 ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3898 $error =
'wronguser';
3901 $delay = $session->delaySave();
3902 $session->unpersist();
3903 $session->setLoggedOutTimestamp( time() );
3904 $session->setUser(
new User );
3905 $session->set(
'wsUserID', 0 );
3906 $session->resetAllTokens();
3907 ScopedCallback::consume( $delay );
3911 'event' =>
'logout',
3912 'successful' => $error ===
false,
3913 'status' => $error ?:
'success',
3927 "Could not update user with ID '{$this->mId}'; DB is read-only."
3933 if ( $this->mId == 0 ) {
3943 $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) use ( $newTouched ) {
3946 'user_name' => $this->mName,
3947 'user_real_name' => $this->mRealName,
3948 'user_email' => $this->mEmail,
3949 'user_email_authenticated' => $dbw->
timestampOrNull( $this->mEmailAuthenticated ),
3950 'user_touched' => $dbw->
timestamp( $newTouched ),
3951 'user_token' => strval( $this->mToken ),
3953 'user_email_token_expires' => $dbw->
timestampOrNull( $this->mEmailTokenExpires ),
3955 'user_id' => $this->mId,
3963 $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ?
'master' :
'replica';
3964 LoggerFactory::getInstance(
'preferences' )->warning(
3965 "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
3966 [
'user_id' => $this->mId,
'db_flag' => $from ]
3968 throw new MWException(
"CAS update failed on user_touched. " .
3969 "The version of the user to be saved is older than the current version."
3975 [
'actor_name' => $this->mName ],
3976 [
'actor_user' => $this->mId ],
3981 $this->mTouched = $newTouched;
4001 $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4006 ? [
'LOCK IN SHARE MODE' ]
4009 $id = $db->selectField(
'user',
4010 'user_id', [
'user_name' =>
$s ], __METHOD__, $options );
4031 foreach ( [
'password',
'newpassword',
'newpass_time',
'password_expires' ] as $field ) {
4032 if ( isset( $params[$field] ) ) {
4033 wfDeprecated( __METHOD__ .
" with param '$field'",
'1.27' );
4034 unset( $params[$field] );
4041 if ( isset( $params[
'options'] ) ) {
4042 $user->mOptions = $params[
'options'] + (array)$user->mOptions;
4043 unset( $params[
'options'] );
4050 'user_name' => $name,
4051 'user_password' => $noPass,
4052 'user_newpassword' => $noPass,
4053 'user_email' => $user->mEmail,
4054 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4055 'user_real_name' => $user->mRealName,
4056 'user_token' => strval( $user->mToken ),
4057 'user_registration' => $dbw->timestamp( $user->mRegistration ),
4058 'user_editcount' => 0,
4059 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4061 foreach ( $params as $name => $value ) {
4062 $fields[
"user_$name"] = $value;
4065 return $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) use ( $fields ) {
4066 $dbw->
insert(
'user', $fields, $fname, [
'IGNORE' ] );
4069 $newUser->mName = $fields[
'user_name'];
4070 $newUser->updateActorId( $dbw );
4072 $newUser->load( self::READ_LATEST );
4108 if ( !$this->mToken ) {
4112 if ( !is_string( $this->mName ) ) {
4113 throw new RuntimeException(
"User name field is not set." );
4119 $status = $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) {
4123 'user_name' => $this->mName,
4124 'user_password' => $noPass,
4125 'user_newpassword' => $noPass,
4126 'user_email' => $this->mEmail,
4127 'user_email_authenticated' => $dbw->
timestampOrNull( $this->mEmailAuthenticated ),
4129 'user_token' => strval( $this->mToken ),
4130 'user_registration' => $dbw->
timestamp( $this->mRegistration ),
4131 'user_editcount' => 0,
4132 'user_touched' => $dbw->
timestamp( $this->mTouched ),
4141 [
'user_name' => $this->mName ],
4143 [
'LOCK IN SHARE MODE' ]
4150 throw new MWException( $fname .
": hit a key conflict attempting " .
4151 "to insert user '{$this->mName}' row, but it was not present in select!" );
4179 [
'actor_user' => $this->mId,
'actor_name' => $this->mName ],
4182 $this->mActorId = (int)$dbw->
insertId();
4204 wfDebug( __METHOD__ .
"()\n" );
4206 if ( $this->mId == 0 ) {
4210 $userblock = DatabaseBlock::newFromTarget( $this->
getName() );
4211 if ( !$userblock ) {
4215 return (
bool)$userblock->doAutoblock( $this->
getRequest()->getIP() );
4224 if ( $this->mBlock && $this->mBlock->appliesToRight(
'createaccount' ) ) {
4228 # T15611: if the IP address the user is trying to create an account from is
4229 # blocked with createaccount disabled, prevent new account creation there even
4230 # when the user is logged in
4231 if ( $this->mBlockedFromCreateAccount ===
false && !$this->
isAllowed(
'ipblock-exempt' ) ) {
4232 $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
4236 return $this->mBlockedFromCreateAccount instanceof
AbstractBlock
4237 && $this->mBlockedFromCreateAccount->
appliesToRight(
'createaccount' )
4238 ? $this->mBlockedFromCreateAccount
4248 return $this->mBlock && $this->mBlock->appliesToRight(
'sendemail' );
4259 return $this->mBlock && $this->mBlock->appliesToRight(
'upload' );
4286 return $title->getTalkPage();
4295 return !$this->
isAllowed(
'autoconfirmed' );
4307 $manager = AuthManager::singleton();
4308 $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4309 $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4311 'username' => $this->
getName(),
4312 'password' => $password,
4315 $res = $manager->beginAuthentication( $reqs,
'null:' );
4316 switch (
$res->status ) {
4317 case AuthenticationResponse::PASS:
4319 case AuthenticationResponse::FAIL:
4321 LoggerFactory::getInstance(
'authentication' )
4322 ->info( __METHOD__ .
': Authentication failed: ' .
$res->message->plain() );
4325 throw new BadMethodCallException(
4326 'AuthManager returned a response unsupported by ' . __METHOD__
4364 return $request->getSession()->getToken( $salt );
4396 public function matchEditToken( $val, $salt =
'', $request =
null, $maxage =
null ) {
4411 $val = substr( $val, 0, strspn( $val,
'0123456789abcdef' ) ) . Token::SUFFIX;
4430 if (
$type ==
'created' ||
$type ===
false ) {
4431 $message =
'confirmemail_body';
4433 } elseif (
$type ===
true ) {
4434 $message =
'confirmemail_body_changed';
4438 $message =
'confirmemail_body_' .
$type;
4442 'subject' =>
wfMessage(
'confirmemail_subject' )->text(),
4447 $wgLang->userTimeAndDate( $expiration, $this ),
4449 $wgLang->userDate( $expiration, $this ),
4450 $wgLang->userTime( $expiration, $this ) )->text(),
4457 'confirmURL' => $url,
4458 'invalidateURL' => $invalidateURL,
4459 'expiration' => $expiration
4462 Hooks::run(
'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4463 return $this->
sendMail( $mail[
'subject'], $mail[
'body'], $mail[
'from'], $mail[
'replyTo'] );
4477 public function sendMail( $subject, $body, $from =
null, $replyto =
null ) {
4480 if ( $from instanceof
User ) {
4484 wfMessage(
'emailsender' )->inContentLanguage()->text() );
4489 'replyTo' => $replyto,
4510 $hash = md5( $token );
4511 $this->mEmailToken = $hash;
4512 $this->mEmailTokenExpires = $expiration;
4522 return $this->
getTokenUrl(
'ConfirmEmail', $token );
4531 return $this->
getTokenUrl(
'InvalidateEmail', $token );
4551 return $title->getCanonicalURL();
4566 Hooks::run(
'ConfirmEmailComplete', [ $this ] );
4580 $this->mEmailToken =
null;
4581 $this->mEmailTokenExpires =
null;
4584 Hooks::run(
'InvalidateEmailComplete', [ $this ] );
4594 $this->mEmailAuthenticated = $timestamp;
4595 Hooks::run(
'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4611 Hooks::run(
'UserCanSendEmail', [ &$user, &$canSend ] );
4640 if (
Hooks::run(
'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4644 if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4664 $this->mEmailToken &&
4712 if ( $this->
getId() == 0 ) {
4717 $tsField = isset( $actorWhere[
'tables'][
'temp_rev_user'] )
4718 ?
'revactor_timestamp' :
'rev_timestamp';
4719 $sortOrder = $first ?
'ASC' :
'DESC';
4720 $time =
$dbr->selectField(
4721 [
'revision' ] + $actorWhere[
'tables'],
4723 [ $actorWhere[
'conds'] ],
4725 [
'ORDER BY' =>
"$tsField $sortOrder" ],
4726 $actorWhere[
'joins']
4744 return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4757 return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4776 return MediaWikiServices::getInstance()->getPermissionManager()
4777 ->groupHasPermission( $group, $role );
4799 return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4810 return array_values( array_diff(
4812 self::getImplicitGroups()
4824 return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4878 if ( is_int( $key ) ) {
4886 if ( is_int( $key ) ) {
4921 if ( $this->
isAllowed(
'userrights' ) ) {
4926 $all = array_merge( self::getAllGroups() );
4944 foreach ( $addergroups as $addergroup ) {
4945 $groups = array_merge_recursive(
4948 $groups[
'add'] = array_unique( $groups[
'add'] );
4949 $groups[
'remove'] = array_unique( $groups[
'remove'] );
4950 $groups[
'add-self'] = array_unique( $groups[
'add-self'] );
4951 $groups[
'remove-self'] = array_unique( $groups[
'remove-self'] );
4976 $this->mEditCount = $count;
4990 $count = (int)
$dbr->selectField(
4991 [
'revision' ] + $actorWhere[
'tables'],
4993 [ $actorWhere[
'conds'] ],
4996 $actorWhere[
'joins']
5002 [
'user_editcount' => $count ],
5004 'user_id' => $this->
getId(),
5005 'user_editcount IS NULL OR user_editcount < ' . (
int)$count
5021 $key =
"right-$right";
5023 return $msg->isDisabled() ? $right : $msg->text();
5034 $key =
"grant-$grant";
5036 return $msg->isDisabled() ? $grant : $msg->text();
5086 if ( $this->mOptionsLoaded ) {
5092 if ( !$this->
getId() ) {
5097 $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5098 $this->mOptions[
'variant'] = $variant;
5099 $this->mOptions[
'language'] = $variant;
5100 $this->mOptionsLoaded =
true;
5105 if ( !is_null( $this->mOptionOverrides ) ) {
5106 wfDebug(
"User: loading options for user " . $this->
getId() .
" from override cache.\n" );
5107 foreach ( $this->mOptionOverrides as $key => $value ) {
5108 $this->mOptions[$key] = $value;
5111 if ( !is_array( $data ) ) {
5112 wfDebug(
"User: loading options for user " . $this->
getId() .
" from database.\n" );
5114 $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5120 [
'up_property',
'up_value' ],
5121 [
'up_user' => $this->
getId() ],
5125 $this->mOptionOverrides = [];
5127 foreach (
$res as $row ) {
5132 if ( $row->up_value ===
'0' ) {
5135 $data[$row->up_property] = $row->up_value;
5139 foreach ( $data as $property => $value ) {
5140 $this->mOptionOverrides[$property] = $value;
5141 $this->mOptions[$property] = $value;
5147 $this->mOptions[
'language']
5150 $this->mOptionsLoaded =
true;
5152 Hooks::run(
'UserLoadOptions', [ $this, &$this->mOptions ] );
5168 if ( !
Hooks::run(
'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5172 $userId = $this->
getId();
5175 foreach ( $saveOptions as $key => $value ) {
5178 if ( ( $defaultOption ===
null && $value !==
false && $value !==
null )
5179 || $value != $defaultOption
5182 'up_user' => $userId,
5183 'up_property' => $key,
5184 'up_value' => $value,
5191 $res = $dbw->select(
'user_properties',
5192 [
'up_property',
'up_value' ], [
'up_user' => $userId ], __METHOD__ );
5197 foreach (
$res as $row ) {
5198 if ( !isset( $saveOptions[$row->up_property] )
5199 || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5201 $keysDelete[] = $row->up_property;
5205 if ( count( $keysDelete ) ) {
5213 $dbw->delete(
'user_properties',
5214 [
'up_user' => $userId,
'up_property' => $keysDelete ], __METHOD__ );
5217 $dbw->insert(
'user_properties', $insert_rows, __METHOD__, [
'IGNORE' ] );
5235 'user_email_authenticated',
5237 'user_email_token_expires',
5238 'user_registration',
5254 'tables' => [
'user',
'user_actor' =>
'actor' ],
5262 'user_email_authenticated',
5264 'user_email_token_expires',
5265 'user_registration',
5267 'user_actor.actor_id',
5270 'user_actor' => [
'JOIN',
'user_actor.actor_user = user_id' ],
5288 foreach ( MediaWikiServices::getInstance()
5311 if ( !$this->
getId() ) {
5316 if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {