34 use Wikimedia\Assert\Assert;
36 use Wikimedia\ScopedCallback;
100 'mEmailAuthenticated',
102 'mEmailTokenExpires',
240 return (
string)$this->
getName();
245 if ( $name ===
'mRights' ) {
248 } elseif ( !property_exists( $this, $name ) ) {
260 public function __set( $name, $value ) {
263 if ( $name ===
'mRights' ) {
264 MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
266 is_null( $value ) ? [] : $value
268 } elseif ( !property_exists( $this, $name ) ) {
269 $this->$name = $value;
298 $this->mLoadedItems ===
true || $this->mFrom !==
'session';
306 public function load( $flags = self::READ_NORMAL ) {
309 if ( $this->mLoadedItems ===
true ) {
315 $this->mLoadedItems =
true;
316 $this->queryFlagsUsed = $flags;
321 ->warning(
'User::loadFromSession called before the end of Setup.php', [
322 'exception' =>
new Exception(
'User::loadFromSession called before the end of Setup.php' ),
325 $this->mLoadedItems = $oldLoadedItems;
329 switch ( $this->mFrom ) {
335 if ( $this->mId != 0 ) {
336 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
337 if ( $lb->hasOrMadeRecentMasterChanges() ) {
338 $flags |= self::READ_LATEST;
339 $this->queryFlagsUsed = $flags;
348 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
349 if ( $lb->hasOrMadeRecentMasterChanges() ) {
350 $flags |= self::READ_LATEST;
351 $this->queryFlagsUsed = $flags;
355 $row =
wfGetDB( $index )->selectRow(
357 [
'actor_id',
'actor_user',
'actor_name' ],
358 $this->mFrom ===
'name' ? [
'actor_name' => $this->mName ] : [
'actor_id' => $this->mActorId ],
365 $this->
loadDefaults( $this->mFrom ===
'name' ? $this->mName :
false );
366 } elseif ( $row->actor_user ) {
367 $this->mId = $row->actor_user;
370 $this->
loadDefaults( $row->actor_name, $row->actor_id );
378 Hooks::run(
'UserLoadAfterLoadFromSession', [ $this ] );
381 throw new UnexpectedValueException(
382 "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
392 if ( $this->mId == 0 ) {
410 $this->mLoadedItems =
true;
411 $this->queryFlagsUsed = $flags;
421 public static function purge( $dbDomain, $userId ) {
422 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
423 $key =
$cache->makeGlobalKey(
'user',
'id', $dbDomain, $userId );
433 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
435 return $cache->makeGlobalKey(
'user',
'id', $lbFactory->getLocalDomainID(), $this->mId );
444 $id = $this->
getId();
446 return $id ? [ $this->
getCacheKey( $cache ) ] : [];
456 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
457 $data =
$cache->getWithSetCallback(
460 function ( $oldValue, &$ttl, array &$setOpts ) use (
$cache ) {
462 wfDebug(
"User: cache miss for user {$this->mId}\n" );
469 foreach ( self::$mCacheVars as $name ) {
470 $data[$name] = $this->$name;
477 foreach ( $this->mGroupMemberships as $ugm ) {
478 if ( $ugm->getExpiry() ) {
479 $secondsUntilExpiry =
wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
480 if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
481 $ttl = $secondsUntilExpiry;
488 [
'pcTTL' => $cache::TTL_PROC_LONG,
'version' =>
self::VERSION ]
492 foreach ( self::$mCacheVars as $name ) {
493 $this->$name = $data[$name];
518 public static function newFromName( $name, $validate =
'valid' ) {
519 if ( $validate ===
true ) {
523 if ( $name ===
false ) {
575 if ( $identity instanceof
User ) {
580 $identity->
getId() === 0 ?
null : $identity->
getId(),
599 public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain =
false ) {
603 if ( $dbDomain !==
false ) {
609 $user->mFrom =
'defaults';
611 if ( $actorId !==
null ) {
612 $user->mActorId = (int)$actorId;
613 if ( $user->mActorId !== 0 ) {
614 $user->mFrom =
'actor';
619 if ( $userName !==
null && $userName !==
'' ) {
620 $user->mName = $userName;
621 $user->mFrom =
'name';
622 $user->setItemLoaded(
'name' );
625 if ( $userId !==
null ) {
626 $user->mId = (int)$userId;
627 if ( $user->mId !== 0 ) {
630 $user->setItemLoaded(
'id' );
633 if ( $user->mFrom ===
'defaults' ) {
634 throw new InvalidArgumentException(
635 'Cannot create a user with no name, no ID, and no actor ID'
654 $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
658 $id = $db->selectField(
662 'user_email_token' => md5( $code ),
663 'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
679 $user->mFrom =
'session';
680 $user->mRequest = $request;
742 'validate' =>
'valid',
748 if ( $name ===
false ) {
754 $row =
$dbr->selectRow(
755 $userQuery[
'tables'],
756 $userQuery[
'fields'],
757 [
'user_name' => $name ],
765 $row = $dbw->selectRow(
766 $userQuery[
'tables'],
767 $userQuery[
'fields'],
768 [
'user_name' => $name ],
777 if ( !$options[
'create'] ) {
784 if ( !self::isValidUserName( $name ) || self::isUsableName( $name ) ) {
791 return $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) use ( $name ) {
795 [
'actor_name' => $name,
'actor_user' =>
null ],
807 $dbw->
delete(
'actor', [
'actor_id' => $row->actor_id ], $fname );
811 [
'actor_id' => $row->actor_id ],
812 [
'actor_id' => $user->getActorId() ],
815 $user->clearInstanceCache(
'id' );
816 $user->invalidateCache();
825 if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
826 AuthManager::singleton()->userCanAuthenticate( $name )
829 if ( !$options[
'steal'] ) {
833 AuthManager::singleton()->revokeAccessForUser( $name );
835 $user->invalidateEmail();
837 $user->saveSettings();
838 SessionManager::singleton()->preventSessionsForUser( $user->getName() );
851 public static function whoIs( $id ) {
871 public static function idFromName( $name, $flags = self::READ_NORMAL ) {
873 $name = (string)$name;
875 if ( is_null( $nt ) ) {
880 if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
881 return is_null( self::$idCacheByName[$name] ) ? null : (int)self::$idCacheByName[$name];
890 [
'user_name' => $nt->getText() ],
895 if (
$s ===
false ) {
898 $result = (int)
$s->user_id;
901 if ( count( self::$idCacheByName ) >= 1000 ) {
902 self::$idCacheByName = [];
905 self::$idCacheByName[$name] = $result;
914 self::$idCacheByName = [];
933 public static function isIP( $name ) {
934 return preg_match(
'/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
963 || self::isIP( $name )
964 || strpos( $name,
'/' ) !==
false
966 || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
974 if ( is_null( $parsed )
975 || $parsed->getNamespace()
976 || strcmp( $name, $parsed->getPrefixedText() ) ) {
982 $unicodeBlacklist =
'/[' .
983 '\x{0080}-\x{009f}' . # iso-8859-1 control chars
984 '\x{00a0}' . # non-breaking space
985 '\x{2000}-\x{200f}' . # various whitespace
986 '\x{2028}-\x{202f}' . # breaks and control chars
987 '\x{3000}' . # ideographic space
988 '\x{e000}-\x{f8ff}' . #
private use
990 if ( preg_match( $unicodeBlacklist, $name ) ) {
1011 if ( !self::isValidUserName( $name ) ) {
1015 if ( !self::$reservedUsernames ) {
1017 Hooks::run(
'UserGetReservedNames', [ &self::$reservedUsernames ] );
1021 foreach ( self::$reservedUsernames as $reserved ) {
1022 if ( substr( $reserved, 0, 4 ) ==
'msg:' ) {
1023 $reserved =
wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1025 if ( $reserved == $name ) {
1043 if ( $groups === [] ) {
1047 $groups = array_unique( (array)$groups );
1048 $limit = min( 5000, $limit );
1050 $conds = [
'ug_group' => $groups ];
1051 if ( $after !==
null ) {
1052 $conds[] =
'ug_user > ' . (int)$after;
1056 $ids =
$dbr->selectFieldValues(
1063 'ORDER BY' =>
'ug_user',
1088 if ( strlen( $name ) > 235 ) {
1090 ": '$name' invalid due to length" );
1099 ": '$name' invalid due to wgInvalidUsernameCharacters" );
1149 if ( !
Hooks::run(
'isValidPassword', [ $password, &$result, $this ] ) ) {
1150 $status->error( $result );
1154 if ( $result ===
false ) {
1155 $status->merge( $upp->checkUserPassword( $this, $password ),
true );
1159 if ( $result ===
true ) {
1163 $status->error( $result );
1182 $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1184 # Reject names containing '#'; these will be cleaned up
1185 # with title normalisation, but then it's too late to
1187 if ( strpos( $name,
'#' ) !==
false ) {
1193 $t = ( $validate !== false ) ?
1196 if ( is_null(
$t ) ||
$t->getNamespace() !==
NS_USER ||
$t->isExternal() ) {
1200 $name =
$t->getText();
1202 switch ( $validate ) {
1206 if ( !self::isValidUserName( $name ) ) {
1211 if ( !self::isUsableName( $name ) ) {
1216 if ( !self::isCreatableName( $name ) ) {
1221 throw new InvalidArgumentException(
1222 'Invalid parameter value for $validate in ' . __METHOD__ );
1238 $this->mName = $name;
1239 $this->mActorId = $actorId;
1240 $this->mRealName =
'';
1242 $this->mOptionOverrides =
null;
1243 $this->mOptionsLoaded =
false;
1245 $loggedOut = $this->mRequest && !defined(
'MW_NO_SESSION' )
1246 ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1247 if ( $loggedOut !== 0 ) {
1248 $this->mTouched =
wfTimestamp( TS_MW, $loggedOut );
1250 $this->mTouched =
'1'; # Allow any pages to be cached
1253 $this->mToken =
null;
1254 $this->mEmailAuthenticated =
null;
1255 $this->mEmailToken =
'';
1256 $this->mEmailTokenExpires =
null;
1258 $this->mGroupMemberships = [];
1260 Hooks::run(
'UserLoadDefaults', [ $this, $name ] );
1276 return ( $this->mLoadedItems ===
true && $all ===
'all' ) ||
1277 ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] ===
true );
1286 if ( is_array( $this->mLoadedItems ) ) {
1287 $this->mLoadedItems[$item] =
true;
1299 $session = $this->
getRequest()->getSession();
1300 $user = $session->getUser();
1301 if ( $user->isLoggedIn() ) {
1307 MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1310 $session->set(
'wsUserID', $this->
getId() );
1311 $session->set(
'wsUserName', $this->
getName() );
1312 $session->set(
'wsToken', $this->
getToken() );
1326 MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1338 $this->mId = intval( $this->mId );
1340 if ( !$this->mId ) {
1350 $s = $db->selectRow(
1351 $userQuery[
'tables'],
1352 $userQuery[
'fields'],
1353 [
'user_id' => $this->mId ],
1359 $this->queryFlagsUsed = $flags;
1362 if (
$s !==
false ) {
1365 $this->mGroupMemberships =
null;
1390 if ( !is_object( $row ) ) {
1391 throw new InvalidArgumentException(
'$row must be an object' );
1396 $this->mGroupMemberships =
null;
1398 if ( isset( $row->actor_id ) ) {
1399 $this->mActorId = (int)$row->actor_id;
1400 if ( $this->mActorId !== 0 ) {
1401 $this->mFrom =
'actor';
1408 if ( isset( $row->user_name ) && $row->user_name !==
'' ) {
1409 $this->mName = $row->user_name;
1410 $this->mFrom =
'name';
1416 if ( isset( $row->user_real_name ) ) {
1417 $this->mRealName = $row->user_real_name;
1423 if ( isset( $row->user_id ) ) {
1424 $this->mId = intval( $row->user_id );
1425 if ( $this->mId !== 0 ) {
1426 $this->mFrom =
'id';
1433 if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !==
'' ) {
1434 self::$idCacheByName[$row->user_name] = $row->user_id;
1437 if ( isset( $row->user_editcount ) ) {
1438 $this->mEditCount = $row->user_editcount;
1443 if ( isset( $row->user_touched ) ) {
1444 $this->mTouched =
wfTimestamp( TS_MW, $row->user_touched );
1449 if ( isset( $row->user_token ) ) {
1453 $this->mToken = rtrim( $row->user_token,
" \0" );
1454 if ( $this->mToken ===
'' ) {
1455 $this->mToken =
null;
1461 if ( isset( $row->user_email ) ) {
1462 $this->mEmail = $row->user_email;
1463 $this->mEmailAuthenticated =
wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1464 $this->mEmailToken = $row->user_email_token;
1465 $this->mEmailTokenExpires =
wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1472 $this->mLoadedItems =
true;
1475 if ( is_array( $data ) ) {
1476 if ( isset( $data[
'user_groups'] ) && is_array( $data[
'user_groups'] ) ) {
1477 if ( $data[
'user_groups'] === [] ) {
1478 $this->mGroupMemberships = [];
1480 $firstGroup = reset( $data[
'user_groups'] );
1481 if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1482 $this->mGroupMemberships = [];
1483 foreach ( $data[
'user_groups'] as $row ) {
1485 $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1490 if ( isset( $data[
'user_properties'] ) && is_array( $data[
'user_properties'] ) ) {
1503 foreach ( self::$mCacheVars as $var ) {
1504 $this->$var = $user->$var;
1512 if ( is_null( $this->mGroupMemberships ) ) {
1513 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1543 if ( $toPromote === [] ) {
1553 foreach ( $toPromote as $group ) {
1556 $newGroups = array_merge( $oldGroups, $toPromote );
1560 Hooks::run(
'UserGroupsChanged', [ $this, $toPromote, [],
false,
false, $oldUGMs, $newUGMs ] );
1563 $logEntry->setPerformer( $this );
1565 $logEntry->setParameters( [
1566 '4::oldgroups' => $oldGroups,
1567 '5::newgroups' => $newGroups,
1569 $logid = $logEntry->insert();
1571 $logEntry->publish( $logid );
1587 if ( $this->mTouched ) {
1589 $conditions[
'user_touched'] = $db->
timestamp( $this->mTouched );
1607 if ( !$this->mId ) {
1615 $dbw->update(
'user',
1616 [
'user_touched' => $dbw->timestamp( $newTouched ) ],
1617 $this->makeUpdateConditions( $dbw, [
1618 'user_id' => $this->mId,
1622 $success = ( $dbw->affectedRows() > 0 );
1625 $this->mTouched = $newTouched;
1645 $this->mNewtalk = -1;
1646 $this->mDatePreference =
null;
1647 $this->mBlockedby = -1; # Unset
1648 $this->mHash =
false;
1649 $this->mEffectiveGroups =
null;
1650 $this->mImplicitGroups =
null;
1651 $this->mGroupMemberships =
null;
1652 $this->mOptions =
null;
1653 $this->mOptionsLoaded =
false;
1654 $this->mEditCount =
null;
1658 MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
1663 if ( $reloadFrom ) {
1664 $this->mLoadedItems = [];
1665 $this->mFrom = $reloadFrom;
1681 Assert::invariant( defined(
'MW_PHPUNIT_TEST' ),
'Unit tests only' );
1682 self::$defOpt =
null;
1683 self::$defOptLang =
null;
1695 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1696 if ( self::$defOpt !==
null && self::$defOptLang === $contLang->getCode() ) {
1705 self::$defOptLang = $contLang->getCode();
1708 if ( $langCode === $contLang->getCode() ) {
1709 self::$defOpt[
'variant'] = $langCode;
1711 self::$defOpt[
"variant-$langCode"] = $langCode;
1719 self::$defOpt[
'searchNs' . $nsnum] = (bool)$val;
1723 Hooks::run(
'UserGetDefaultOptions', [ &self::$defOpt ] );
1736 return $defOpts[$opt] ??
null;
1749 if ( $this->mBlockedby != -1 ) {
1753 wfDebug( __METHOD__ .
": checking...\n" );
1774 $globalUserName = $sessionUser->isSafeToLoad()
1775 ? $sessionUser->getName()
1778 if ( $this->
getName() === $globalUserName ) {
1784 $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1791 $this->mBlock = $block;
1792 $this->mBlockedby = $block->getByName();
1793 $this->mBlockreason = $block->getReason();
1794 $this->mHideName = $block->getHideName();
1795 $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1797 $this->mBlock =
null;
1798 $this->mBlockedby =
'';
1799 $this->mBlockreason =
'';
1800 $this->mHideName = 0;
1801 $this->mAllowUsertalk =
false;
1807 Hooks::run(
'GetBlockedStatus', [ &$thisUser ],
'1.34' );
1819 return MediaWikiServices::getInstance()->getBlockManager()
1820 ->isDnsBlacklisted( $ip, $checkWhitelist );
1838 $ipReversed = implode(
'.', array_reverse( explode(
'.', $ip ) ) );
1840 foreach ( (array)$bases as
$base ) {
1844 if ( is_array(
$base ) ) {
1845 if ( count(
$base ) >= 2 ) {
1847 $host =
"{$base[1]}.$ipReversed.{$base[0]}";
1849 $host =
"$ipReversed.{$base[0]}";
1851 $basename =
$base[0];
1853 $host =
"$ipReversed.$base";
1857 $ipList = gethostbynamel( $host );
1860 wfDebugLog(
'dnsblacklist',
"Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1865 wfDebugLog(
'dnsblacklist',
"Requested $host, not found in $basename." );
1893 $resultProxyList = [];
1894 $deprecatedIPEntries = [];
1900 if ( $keyIsIP && !$valueIsIP ) {
1901 $deprecatedIPEntries[] = $key;
1902 $resultProxyList[] = $key;
1903 } elseif ( $keyIsIP && $valueIsIP ) {
1904 $deprecatedIPEntries[] = $key;
1905 $resultProxyList[] = $key;
1906 $resultProxyList[] = $value;
1908 $resultProxyList[] = $value;
1912 if ( $deprecatedIPEntries ) {
1914 'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1915 implode(
', ', $deprecatedIPEntries ) .
', please move them to values)',
'1.30' );
1918 $proxyListIPSet =
new IPSet( $resultProxyList );
1919 return $proxyListIPSet->match( $ip );
1935 return !$this->
isAllowed(
'noratelimit' );
1962 if ( !
Hooks::run(
'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1971 $limits = array_merge(
1972 [
'&can-bypass' =>
true ],
1981 $logger->debug( __METHOD__ .
": limiting $action rate for {$this->getName()}" );
1984 $id = $this->
getId();
1990 if ( isset( $limits[
'anon'] ) ) {
1991 $keys[
$cache->makeKey(
'limiter', $action,
'anon' )] = $limits[
'anon'];
1995 if ( isset( $limits[
'user-global'] ) ) {
1998 $centralId = $lookup
2004 $realm = $lookup->getProviderId();
2006 $globalKey =
$cache->makeGlobalKey(
'limiter', $action,
'user-global',
2007 $realm, $centralId );
2010 $globalKey =
$cache->makeKey(
'limiter', $action,
'user-global',
2013 $keys[$globalKey] = $limits[
'user-global'];
2019 if ( isset( $limits[
'ip'] ) ) {
2021 $keys[
$cache->makeGlobalKey(
'limiter', $action,
'ip', $ip )] = $limits[
'ip'];
2024 if ( isset( $limits[
'subnet'] ) ) {
2027 if ( $subnet !==
false ) {
2028 $keys[
$cache->makeGlobalKey(
'limiter', $action,
'subnet', $subnet )] = $limits[
'subnet'];
2035 if ( $id !== 0 && isset( $limits[
'user'] ) ) {
2037 $userLimit = $limits[
'user'];
2040 if ( $id !== 0 && $isNewbie && isset( $limits[
'newbie'] ) ) {
2041 $userLimit = $limits[
'newbie'];
2045 foreach ( $this->
getGroups() as $group ) {
2046 if ( isset( $limits[$group] ) ) {
2047 if ( $userLimit ===
false
2048 || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2050 $userLimit = $limits[$group];
2057 if ( $userLimit !==
false ) {
2061 list( $max, $period ) = $userLimit;
2062 $logger->debug( __METHOD__ .
": effective user limit: $max in {$period}s" );
2063 $keys[
$cache->makeKey(
'limiter', $action,
'user', $id )] = $userLimit;
2067 if ( isset( $limits[
'ip-all'] ) ) {
2070 if ( $isNewbie || $userLimit ===
false
2071 || $limits[
'ip-all'][0] / $limits[
'ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2072 $keys[
$cache->makeGlobalKey(
'limiter', $action,
'ip-all', $ip )] = $limits[
'ip-all'];
2077 if ( isset( $limits[
'subnet-all'] ) ) {
2080 if ( $subnet !==
false ) {
2082 if ( $isNewbie || $userLimit ===
false
2083 || $limits[
'ip-all'][0] / $limits[
'ip-all'][1]
2084 > $userLimit[0] / $userLimit[1] ) {
2085 $keys[
$cache->makeGlobalKey(
'limiter', $action,
'subnet-all', $subnet )]
2086 = $limits[
'subnet-all'];
2094 $now = MWTimestamp::time();
2098 foreach (
$keys as $key => $limit ) {
2106 function (
$cache, $key, $data, &$expiry )
2107 use ( $action, $logger, &$triggered, $now, $clockFudge, $limit, $incrBy )
2112 list( $max, $period ) = $limit;
2114 $expiry = $now + (int)$period;
2121 $fields = explode(
'|', $data );
2122 $storedCount = (int)( $fields[0] ?? 0 );
2123 $storedExpiry = (int)( $fields[1] ?? PHP_INT_MAX );
2126 if ( $storedExpiry < ( $now + $clockFudge ) ) {
2128 'User::pingLimiter: '
2129 .
'Stale rate limit entry, cache key failed to expire (T246991)',
2131 'action' => $action,
2134 'period' => $period,
2135 'count' => $storedCount,
2137 'expiry' => MWTimestamp::convert( TS_DB, $storedExpiry ),
2143 $expiry = min( $storedExpiry, $now + (
int)$period );
2144 $count = $storedCount;
2149 if ( $count >= $max ) {
2150 if ( !$triggered ) {
2152 'User::pingLimiter: User tripped rate limit',
2154 'action' => $action,
2158 'period' => $period,
2169 $data =
"$count|$expiry";
2191 $this->
getBlock()->appliesToRight(
'edit' );
2202 return $this->mBlock instanceof
AbstractBlock ? $this->mBlock :
null;
2217 return MediaWikiServices::getInstance()->getPermissionManager()
2218 ->isBlockedFrom( $this,
$title, $fromReplica );
2245 return ( $this->mBlock ? $this->mBlock->getId() :
false );
2271 if ( $this->mGlobalBlock !==
null ) {
2272 return $this->mGlobalBlock ?:
null;
2284 Hooks::run(
'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2286 if ( $blocked && $block ===
null ) {
2290 'systemBlock' =>
'global-block'
2294 $this->mGlobalBlock = $blocked ? $block :
false;
2295 return $this->mGlobalBlock ?:
null;
2304 if ( $this->mLocked !==
null ) {
2308 $this->mLocked =
false;
2309 Hooks::run(
'UserIsLocked', [ $this, &$this->mLocked ] );
2319 if ( $this->mHideName !==
null ) {
2323 if ( !$this->mHideName ) {
2325 $this->mHideName =
false;
2326 Hooks::run(
'UserIsHidden', [ $this, &$this->mHideName ],
'1.34' );
2336 if ( $this->mId ===
null && $this->mName !==
null &&
2371 if ( $this->mName ===
false ) {
2394 $this->mName = $str;
2408 if ( !$this->mActorId && $dbw ) {
2409 $migration = MediaWikiServices::getInstance()->getActorMigration();
2410 $this->mActorId = $migration->getNewActorId( $dbw, $this );
2424 return str_replace(
' ',
'_', $this->
getName() );
2435 if ( $this->mNewtalk === -1 ) {
2436 $this->mNewtalk =
false; # reset talk page status
2440 if ( !$this->mId ) {
2444 $this->mNewtalk =
false;
2449 $this->mNewtalk = $this->
checkNewtalk(
'user_id', $this->mId );
2473 if ( !
Hooks::run(
'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2483 $timestamp =
$dbr->selectField(
'user_newtalk',
2484 'MIN(user_last_timestamp)',
2485 $this->
isAnon() ? [
'user_ip' => $this->
getName() ] : [
'user_id' => $this->
getId() ],
2491 'link' => $utp->getLocalURL(),
2503 $newMessageRevisionId =
null;
2509 if ( $newMessageLinks && count( $newMessageLinks ) === 1
2511 && $newMessageLinks[0][
'rev']
2514 $newMessageRevision = $newMessageLinks[0][
'rev'];
2515 $newMessageRevisionId = $newMessageRevision->getId();
2518 return $newMessageRevisionId;
2532 $ok =
$dbr->selectField(
'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2534 return $ok !==
false;
2546 $prevRev = $curRev ? $curRev->getPrevious() :
false;
2547 $ts = $prevRev ? $prevRev->getTimestamp() :
null;
2550 $dbw->insert(
'user_newtalk',
2551 [ $field => $id,
'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2554 if ( $dbw->affectedRows() ) {
2555 wfDebug( __METHOD__ .
": set on ($field, $id)\n" );
2559 wfDebug( __METHOD__ .
" already set ($field, $id)\n" );
2571 $dbw->delete(
'user_newtalk',
2574 if ( $dbw->affectedRows() ) {
2575 wfDebug( __METHOD__ .
": killed on ($field, $id)\n" );
2579 wfDebug( __METHOD__ .
": already gone ($field, $id)\n" );
2595 $this->mNewtalk = $val;
2602 $id = $this->
getId();
2624 if ( $this->mTouched ) {
2625 $time = max( $time,
wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2642 if ( !$this->
getId() ) {
2646 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2647 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2650 if ( $mode ===
'refresh' ) {
2651 $cache->delete( $key, 1 );
2653 $lb->getConnectionRef(
DB_MASTER )->onTransactionPreCommitOrIdle(
2654 function () use (
$cache, $key ) {
2685 $id = $this->
getId();
2687 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2688 $key =
$cache->makeKey(
'user-quicktouched',
'id', $id );
2689 $cache->touchCheckKey( $key );
2690 $this->mQuickTouched =
null;
2700 return ( $timestamp >= $this->
getTouched() );
2715 if ( $this->mQuickTouched ===
null ) {
2716 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2717 $key =
$cache->makeKey(
'user-quicktouched',
'id', $this->mId );
2722 return max( $this->mTouched, $this->mQuickTouched );
2782 $manager = AuthManager::singleton();
2785 if ( !$manager->userExists( $this->getName() ) ) {
2786 throw new LogicException(
'Cannot set a password for a user that is not in the database.' );
2790 'username' => $this->
getName(),
2794 if ( !$status->isGood() ) {
2796 ->info( __METHOD__ .
': Password change rejected: '
2797 . $status->getWikiText(
null,
null,
'en' ) );
2801 $this->
setOption(
'watchlisttoken',
false );
2802 SessionManager::singleton()->invalidateSessionsForUser( $this );
2820 $manager = AuthManager::singleton();
2821 $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2822 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2825 foreach ( $reqs as $req ) {
2826 $status->merge( $manager->allowsAuthenticationDataChange( $req ),
true );
2828 if ( $status->getValue() ===
'ignored' ) {
2829 $status->warning(
'authenticationdatachange-ignored' );
2832 if ( $status->isGood() ) {
2833 foreach ( $reqs as $req ) {
2834 $manager->changeAuthenticationData( $req );
2850 if ( !$this->mToken && $forceCreation ) {
2854 if ( !$this->mToken ) {
2859 if ( $this->mToken === self::INVALID_TOKEN ) {
2874 $len = max( 32, self::TOKEN_LENGTH );
2875 if ( strlen( $ret ) < $len ) {
2877 throw new \UnexpectedValueException(
'Hmac returned less than 128 bits' );
2880 return substr( $ret, -$len );
2891 if ( $this->mToken === self::INVALID_TOKEN ) {
2893 ->debug( __METHOD__ .
": Ignoring attempt to set token for system user \"$this\"" );
2894 } elseif ( !$token ) {
2897 $this->mToken = $token;
2907 Hooks::run(
'UserGetEmail', [ $this, &$this->mEmail ] );
2917 Hooks::run(
'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2927 if ( $str == $this->mEmail ) {
2931 $this->mEmail = $str;
2932 Hooks::run(
'UserSetEmail', [ $this, &$this->mEmail ] );
2950 if ( $str === $oldaddr ) {
2954 $type = $oldaddr !=
'' ?
'changed' :
'set';
2955 $notificationResult =
null;
2960 $change = $str !=
'' ?
'changed' :
'removed';
2961 $notificationResult = $this->
sendMail(
2962 wfMessage(
'notificationemail_subject_' . $change )->text(),
2963 wfMessage(
'notificationemail_body_' . $change,
2976 if ( $notificationResult !==
null ) {
2977 $result->merge( $notificationResult );
2980 if ( $result->isGood() ) {
2982 $result->value =
'eauth';
3009 $this->mRealName = $str;
3022 public function getOption( $oname, $defaultOverride =
null, $ignoreHidden =
false ) {
3026 # We want 'disabled' preferences to always behave as the default value for
3027 # users, even if they have set the option explicitly in their settings (ie they
3028 # set it, and then it was disabled removing their ability to change it). But
3029 # we don't want to erase the preferences in the database in case the preference
3030 # is re-enabled again. So don't touch $mOptions, just override the returned value
3035 if ( array_key_exists( $oname, $this->mOptions ) ) {
3036 return $this->mOptions[$oname];
3039 return $defaultOverride;
3055 # We want 'disabled' preferences to always behave as the default value for
3056 # users, even if they have set the option explicitly in their settings (ie they
3057 # set it, and then it was disabled removing their ability to change it). But
3058 # we don't want to erase the preferences in the database in case the preference
3059 # is re-enabled again. So don't touch $mOptions, just override the returned value
3062 if ( $default !==
null ) {
3063 $options[$pref] = $default;
3067 if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3068 $options = array_diff_assoc( $options, self::getDefaultOptions() );
3082 return (
bool)$this->
getOption( $oname );
3096 $val = $defaultOverride;
3098 return intval( $val );
3113 if ( is_null( $val ) ) {
3117 $this->mOptions[$oname] = $val;
3133 $id = $this->
getId();
3143 $token = hash_hmac(
'sha1',
"$oname:$id", $this->
getToken() );
3195 'registered-multiselect',
3196 'registered-checkmatrix',
3217 if ( $options ===
null ) {
3221 $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3222 $prefs = $preferencesFactory->getFormDescriptor( $this,
$context );
3227 $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(),
true );
3228 foreach ( $specialOptions as $name => $value ) {
3229 unset( $prefs[$name] );
3234 $multiselectOptions = [];
3235 foreach ( $prefs as $name => $info ) {
3236 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'multiselect' ) ||
3237 ( isset( $info[
'class'] ) && $info[
'class'] == HTMLMultiSelectField::class ) ) {
3239 $prefix = $info[
'prefix'] ?? $name;
3241 foreach ( $opts as $value ) {
3242 $multiselectOptions[
"$prefix$value"] =
true;
3245 unset( $prefs[$name] );
3248 $checkmatrixOptions = [];
3249 foreach ( $prefs as $name => $info ) {
3250 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'checkmatrix' ) ||
3251 ( isset( $info[
'class'] ) && $info[
'class'] == HTMLCheckMatrix::class ) ) {
3254 $prefix = $info[
'prefix'] ?? $name;
3256 foreach ( $columns as $column ) {
3257 foreach ( $rows as $row ) {
3258 $checkmatrixOptions[
"$prefix$column-$row"] =
true;
3262 unset( $prefs[$name] );
3267 foreach ( $options as $key => $value ) {
3268 if ( isset( $prefs[$key] ) ) {
3269 $mapping[$key] =
'registered';
3270 } elseif ( isset( $multiselectOptions[$key] ) ) {
3271 $mapping[$key] =
'registered-multiselect';
3272 } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3273 $mapping[$key] =
'registered-checkmatrix';
3274 } elseif ( isset( $specialOptions[$key] ) ) {
3275 $mapping[$key] =
'special';
3276 } elseif ( substr( $key, 0, 7 ) ===
'userjs-' ) {
3277 $mapping[$key] =
'userjs';
3279 $mapping[$key] =
'unused';
3301 $resetKinds = [
'registered',
'registered-multiselect',
'registered-checkmatrix',
'unused' ],
3307 if ( !is_array( $resetKinds ) ) {
3308 $resetKinds = [ $resetKinds ];
3311 if ( in_array(
'all', $resetKinds ) ) {
3312 $newOptions = $defaultOptions;
3319 $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3324 foreach ( $this->mOptions as $key => $value ) {
3325 if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3326 if ( array_key_exists( $key, $defaultOptions ) ) {
3327 $newOptions[$key] = $defaultOptions[$key];
3330 $newOptions[$key] = $value;
3335 Hooks::run(
'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3337 $this->mOptions = $newOptions;
3338 $this->mOptionsLoaded =
true;
3347 if ( is_null( $this->mDatePreference ) ) {
3350 $map =
$wgLang->getDatePreferenceMigrationMap();
3351 if ( isset( $map[$value] ) ) {
3352 $value = $map[$value];
3354 $this->mDatePreference = $value;
3374 Hooks::run(
'UserRequiresHTTPS', [ $this, &$https ] );
3407 return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
3419 return array_keys( $this->mGroupMemberships );
3443 if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3444 $this->mEffectiveGroups = array_unique( array_merge(
3451 Hooks::run(
'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3453 $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3466 if ( $recache || is_null( $this->mImplicitGroups ) ) {
3467 $this->mImplicitGroups = [
'*' ];
3468 if ( $this->
getId() ) {
3469 $this->mImplicitGroups[] =
'user';
3471 $this->mImplicitGroups = array_unique( array_merge(
3472 $this->mImplicitGroups,
3479 $this->mEffectiveGroups =
null;
3497 if ( is_null( $this->mFormerGroups ) ) {
3498 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3501 $res = $db->select(
'user_former_groups',
3503 [
'ufg_user' => $this->mId ],
3505 $this->mFormerGroups = [];
3506 foreach (
$res as $row ) {
3507 $this->mFormerGroups[] = $row->ufg_group;
3519 if ( !$this->
getId() ) {
3523 if ( $this->mEditCount ===
null ) {
3527 $count =
$dbr->selectField(
3528 'user',
'user_editcount',
3529 [
'user_id' => $this->mId ],
3533 if ( $count ===
null ) {
3537 $this->mEditCount = $count;
3561 if ( !
Hooks::run(
'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3567 if ( !$ugm->insert(
true ) ) {
3571 $this->mGroupMemberships[$group] = $ugm;
3576 MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3591 if ( !
Hooks::run(
'UserRemoveGroup', [ $this, &$group ] ) ) {
3597 if ( !$ugm || !$ugm->delete() ) {
3602 unset( $this->mGroupMemberships[$group] );
3607 MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3623 return $this->
getId() != 0;
3652 Hooks::run(
"UserIsBot", [ $this, &$isBot ] );
3667 return MediaWikiServices::getInstance()
3668 ->getPermissionManager()
3669 ->userHasAnyRight( $this, ...$permissions );
3679 return MediaWikiServices::getInstance()
3680 ->getPermissionManager()
3681 ->userHasAllRights( $this, ...$permissions );
3695 return MediaWikiServices::getInstance()->getPermissionManager()
3696 ->userHasRight( $this, $action );
3738 if ( $this->mRequest ) {
3755 if (
$title->isWatchable() && ( !$checkRights || $this->isAllowed(
'viewmywatchlist' ) ) ) {
3756 return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this,
$title );
3769 if ( !$checkRights || $this->
isAllowed(
'editmywatchlist' ) ) {
3770 MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3786 if ( !$checkRights || $this->
isAllowed(
'editmywatchlist' ) ) {
3787 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3788 $store->removeWatch( $this,
$title->getSubjectPage() );
3789 $store->removeWatch( $this,
$title->getTalkPage() );
3811 if ( !$this->
isAllowed(
'editmywatchlist' ) ) {
3819 if ( !
Hooks::run(
'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3834 $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3835 $oldRev = $rl->getRevisionById( $oldid, Title::READ_LATEST );
3837 $newRev = $rl->getNextRevision( $oldRev );
3866 MediaWikiServices::getInstance()->getWatchedItemStore()
3867 ->resetNotificationTimestamp( $this,
$title, $force, $oldid );
3888 $id = $this->
getId();
3893 $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
3894 $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
3922 $registration > $learnerRegistration ) {
3927 $registration <= $experiencedRegistration
3929 return 'experienced';
3943 public function setCookies( $request =
null, $secure =
null, $rememberMe =
false ) {
3945 if ( $this->mId == 0 ) {
3949 $session = $this->
getRequest()->getSession();
3950 if ( $request && $session->getRequest() !== $request ) {
3951 $session = $session->sessionWithRequest( $request );
3953 $delay = $session->delaySave();
3955 if ( !$session->getUser()->equals( $this ) ) {
3956 if ( !$session->canSetUser() ) {
3958 ->warning( __METHOD__ .
3959 ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3963 $session->setUser( $this );
3966 $session->setRememberUser( $rememberMe );
3967 if ( $secure !==
null ) {
3968 $session->setForceHTTPS( $secure );
3971 $session->persist();
3973 ScopedCallback::consume( $delay );
3982 if (
Hooks::run(
'UserLogout', [ &$user ] ) ) {
3992 $session = $this->
getRequest()->getSession();
3993 if ( !$session->canSetUser() ) {
3995 ->warning( __METHOD__ .
": Cannot log out of an immutable session" );
3996 $error =
'immutable';
3997 } elseif ( !$session->getUser()->equals( $this ) ) {
3999 ->warning( __METHOD__ .
4000 ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4004 $error =
'wronguser';
4007 $delay = $session->delaySave();
4008 $session->unpersist();
4009 $session->setLoggedOutTimestamp( time() );
4010 $session->setUser(
new User );
4011 $session->set(
'wsUserID', 0 );
4012 $session->resetAllTokens();
4013 ScopedCallback::consume( $delay );
4017 'event' =>
'logout',
4018 'successful' => $error ===
false,
4019 'status' => $error ?:
'success',
4033 "Could not update user with ID '{$this->mId}'; DB is read-only."
4039 if ( $this->mId == 0 ) {
4049 $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) use ( $newTouched ) {
4052 'user_name' => $this->mName,
4053 'user_real_name' => $this->mRealName,
4054 'user_email' => $this->mEmail,
4055 'user_email_authenticated' => $dbw->
timestampOrNull( $this->mEmailAuthenticated ),
4056 'user_touched' => $dbw->
timestamp( $newTouched ),
4057 'user_token' => strval( $this->mToken ),
4058 'user_email_token' => $this->mEmailToken,
4059 'user_email_token_expires' => $dbw->
timestampOrNull( $this->mEmailTokenExpires ),
4060 ], $this->makeUpdateConditions( $dbw, [
4061 'user_id' => $this->mId,
4067 $this->clearSharedCache(
'refresh' );
4069 $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ?
'master' :
'replica';
4070 LoggerFactory::getInstance(
'preferences' )->warning(
4071 "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4072 [
'user_id' => $this->mId,
'db_flag' => $from ]
4074 throw new MWException(
"CAS update failed on user_touched. " .
4075 "The version of the user to be saved is older than the current version."
4081 [
'actor_name' => $this->mName ],
4082 [
'actor_user' => $this->mId ],
4087 $this->mTouched = $newTouched;
4107 $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4112 ? [
'LOCK IN SHARE MODE' ]
4115 $id = $db->selectField(
'user',
4116 'user_id', [
'user_name' =>
$s ], __METHOD__, $options );
4137 foreach ( [
'password',
'newpassword',
'newpass_time',
'password_expires' ] as $field ) {
4138 if ( isset( $params[$field] ) ) {
4139 wfDeprecated( __METHOD__ .
" with param '$field'",
'1.27' );
4140 unset( $params[$field] );
4147 if ( isset( $params[
'options'] ) ) {
4148 $user->mOptions = $params[
'options'] + (array)$user->mOptions;
4149 unset( $params[
'options'] );
4156 'user_name' => $name,
4157 'user_password' => $noPass,
4158 'user_newpassword' => $noPass,
4159 'user_email' => $user->mEmail,
4160 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4161 'user_real_name' => $user->mRealName,
4162 'user_token' => strval( $user->mToken ),
4163 'user_registration' => $dbw->timestamp( $user->mRegistration ),
4164 'user_editcount' => 0,
4165 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4167 foreach ( $params as $name => $value ) {
4168 $fields[
"user_$name"] = $value;
4171 return $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) use ( $fields ) {
4172 $dbw->
insert(
'user', $fields, $fname, [
'IGNORE' ] );
4174 $newUser = self::newFromId( $dbw->insertId() );
4175 $newUser->mName = $fields[
'user_name'];
4176 $newUser->updateActorId( $dbw );
4178 $newUser->load( self::READ_LATEST );
4214 if ( !$this->mToken ) {
4218 if ( !is_string( $this->mName ) ) {
4219 throw new RuntimeException(
"User name field is not set." );
4225 $status = $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) {
4229 'user_name' => $this->mName,
4230 'user_password' => $noPass,
4231 'user_newpassword' => $noPass,
4232 'user_email' => $this->mEmail,
4233 'user_email_authenticated' => $dbw->
timestampOrNull( $this->mEmailAuthenticated ),
4234 'user_real_name' => $this->mRealName,
4235 'user_token' => strval( $this->mToken ),
4236 'user_registration' => $dbw->
timestamp( $this->mRegistration ),
4237 'user_editcount' => 0,
4238 'user_touched' => $dbw->
timestamp( $this->mTouched ),
4247 [
'user_name' => $this->mName ],
4249 [
'LOCK IN SHARE MODE' ]
4256 throw new MWException( $fname .
": hit a key conflict attempting " .
4257 "to insert user '{$this->mName}' row, but it was not present in select!" );
4267 if ( !$status->isGood() ) {
4285 [
'actor_user' => $this->mId,
'actor_name' => $this->mName ],
4288 $this->mActorId = (int)$dbw->
insertId();
4310 wfDebug( __METHOD__ .
"()\n" );
4312 if ( $this->mId == 0 ) {
4316 $userblock = DatabaseBlock::newFromTarget( $this->
getName() );
4317 if ( !$userblock ) {
4321 return (
bool)$userblock->doAutoblock( $this->
getRequest()->getIP() );
4330 if ( $this->mBlock && $this->mBlock->appliesToRight(
'createaccount' ) ) {
4334 # T15611: if the IP address the user is trying to create an account from is
4335 # blocked with createaccount disabled, prevent new account creation there even
4336 # when the user is logged in
4337 if ( $this->mBlockedFromCreateAccount ===
false && !$this->
isAllowed(
'ipblock-exempt' ) ) {
4338 $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
4342 return $this->mBlockedFromCreateAccount instanceof
AbstractBlock
4343 && $this->mBlockedFromCreateAccount->
appliesToRight(
'createaccount' )
4344 ? $this->mBlockedFromCreateAccount
4354 return $this->mBlock && $this->mBlock->appliesToRight(
'sendemail' );
4365 return $this->mBlock && $this->mBlock->appliesToRight(
'upload' );
4392 return $title->getTalkPage();
4401 return !$this->
isAllowed(
'autoconfirmed' );
4413 $manager = AuthManager::singleton();
4414 $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4415 $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4417 'username' => $this->getName(),
4418 'password' => $password,
4421 $res = $manager->beginAuthentication( $reqs,
'null:' );
4422 switch (
$res->status ) {
4423 case AuthenticationResponse::PASS:
4425 case AuthenticationResponse::FAIL:
4427 LoggerFactory::getInstance(
'authentication' )
4428 ->info( __METHOD__ .
': Authentication failed: ' .
$res->message->plain() );
4431 throw new BadMethodCallException(
4432 'AuthManager returned a response unsupported by ' . __METHOD__
4470 return $request->getSession()->getToken( $salt );
4502 public function matchEditToken( $val, $salt =
'', $request =
null, $maxage =
null ) {
4517 $val = substr( $val, 0, strspn( $val,
'0123456789abcdef' ) ) . Token::SUFFIX;
4536 if (
$type ==
'created' ||
$type ===
false ) {
4537 $message =
'confirmemail_body';
4539 } elseif (
$type ===
true ) {
4540 $message =
'confirmemail_body_changed';
4544 $message =
'confirmemail_body_' .
$type;
4548 'subject' =>
wfMessage(
'confirmemail_subject' )->text(),
4553 $wgLang->userTimeAndDate( $expiration, $this ),
4555 $wgLang->userDate( $expiration, $this ),
4556 $wgLang->userTime( $expiration, $this ) )->text(),
4563 'confirmURL' => $url,
4564 'invalidateURL' => $invalidateURL,
4565 'expiration' => $expiration
4568 Hooks::run(
'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4569 return $this->
sendMail( $mail[
'subject'], $mail[
'body'], $mail[
'from'], $mail[
'replyTo'] );
4583 public function sendMail( $subject, $body, $from =
null, $replyto =
null ) {
4586 if ( $from instanceof
User ) {
4590 wfMessage(
'emailsender' )->inContentLanguage()->text() );
4595 'replyTo' => $replyto,
4616 $hash = md5( $token );
4617 $this->mEmailToken = $hash;
4618 $this->mEmailTokenExpires = $expiration;
4628 return $this->
getTokenUrl(
'ConfirmEmail', $token );
4637 return $this->
getTokenUrl(
'InvalidateEmail', $token );
4657 return $title->getCanonicalURL();
4672 Hooks::run(
'ConfirmEmailComplete', [ $this ] );
4686 $this->mEmailToken =
null;
4687 $this->mEmailTokenExpires =
null;
4690 Hooks::run(
'InvalidateEmailComplete', [ $this ] );
4700 $this->mEmailAuthenticated = $timestamp;
4701 Hooks::run(
'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4717 Hooks::run(
'UserCanSendEmail', [ &$user, &$canSend ] );
4746 if (
Hooks::run(
'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4770 $this->mEmailToken &&
4818 if ( $this->
getId() == 0 ) {
4823 $tsField = isset( $actorWhere[
'tables'][
'temp_rev_user'] )
4824 ?
'revactor_timestamp' :
'rev_timestamp';
4825 $sortOrder = $first ?
'ASC' :
'DESC';
4826 $time =
$dbr->selectField(
4827 [
'revision' ] + $actorWhere[
'tables'],
4829 [ $actorWhere[
'conds'] ],
4831 [
'ORDER BY' =>
"$tsField $sortOrder" ],
4832 $actorWhere[
'joins']
4850 return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4863 return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4882 return MediaWikiServices::getInstance()->getPermissionManager()
4883 ->groupHasPermission( $group, $role );
4905 return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4916 return array_values( array_diff(
4918 self::getImplicitGroups()
4930 return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4984 if ( is_int( $key ) ) {
4992 if ( is_int( $key ) ) {
5027 if ( $this->
isAllowed(
'userrights' ) ) {
5032 $all = array_merge( self::getAllGroups() );
5050 foreach ( $addergroups as $addergroup ) {
5051 $groups = array_merge_recursive(
5054 $groups[
'add'] = array_unique( $groups[
'add'] );
5055 $groups[
'remove'] = array_unique( $groups[
'remove'] );
5056 $groups[
'add-self'] = array_unique( $groups[
'add-self'] );
5057 $groups[
'remove-self'] = array_unique( $groups[
'remove-self'] );
5082 $this->mEditCount = $count;
5096 $count = (int)
$dbr->selectField(
5097 [
'revision' ] + $actorWhere[
'tables'],
5099 [ $actorWhere[
'conds'] ],
5102 $actorWhere[
'joins']
5108 [
'user_editcount' => $count ],
5110 'user_id' => $this->
getId(),
5111 'user_editcount IS NULL OR user_editcount < ' . (
int)$count
5127 $key =
"right-$right";
5129 return $msg->isDisabled() ? $right : $msg->text();
5140 $key =
"grant-$grant";
5142 return $msg->isDisabled() ? $grant : $msg->text();
5192 if ( $this->mOptionsLoaded ) {
5198 if ( !$this->
getId() ) {
5203 $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5204 $this->mOptions[
'variant'] = $variant;
5205 $this->mOptions[
'language'] = $variant;
5206 $this->mOptionsLoaded =
true;
5211 if ( !is_null( $this->mOptionOverrides ) ) {
5212 wfDebug(
"User: loading options for user " . $this->
getId() .
" from override cache.\n" );
5213 foreach ( $this->mOptionOverrides as $key => $value ) {
5214 $this->mOptions[$key] = $value;
5217 if ( !is_array( $data ) ) {
5218 wfDebug(
"User: loading options for user " . $this->
getId() .
" from database.\n" );
5220 $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5226 [
'up_property',
'up_value' ],
5227 [
'up_user' => $this->
getId() ],
5231 $this->mOptionOverrides = [];
5233 foreach (
$res as $row ) {
5238 if ( $row->up_value ===
'0' ) {
5241 $data[$row->up_property] = $row->up_value;
5245 foreach ( $data as $property => $value ) {
5246 $this->mOptionOverrides[$property] = $value;
5247 $this->mOptions[$property] = $value;
5253 $this->mOptions[
'language']
5256 $this->mOptionsLoaded =
true;
5258 Hooks::run(
'UserLoadOptions', [ $this, &$this->mOptions ] );
5274 if ( !
Hooks::run(
'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5278 $userId = $this->
getId();
5281 foreach ( $saveOptions as $key => $value ) {
5284 if ( ( $defaultOption ===
null && $value !==
false && $value !==
null )
5285 || $value != $defaultOption
5288 'up_user' => $userId,
5289 'up_property' => $key,
5290 'up_value' => $value,
5297 $res = $dbw->select(
'user_properties',
5298 [
'up_property',
'up_value' ], [
'up_user' => $userId ], __METHOD__ );
5303 foreach (
$res as $row ) {
5304 if ( !isset( $saveOptions[$row->up_property] )
5305 || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5307 $keysDelete[] = $row->up_property;
5311 if ( count( $keysDelete ) ) {
5319 $dbw->delete(
'user_properties',
5320 [
'up_user' => $userId,
'up_property' => $keysDelete ], __METHOD__ );
5323 $dbw->insert(
'user_properties', $insert_rows, __METHOD__, [
'IGNORE' ] );
5341 'user_email_authenticated',
5343 'user_email_token_expires',
5344 'user_registration',
5360 'tables' => [
'user',
'user_actor' =>
'actor' ],
5368 'user_email_authenticated',
5370 'user_email_token_expires',
5371 'user_registration',
5373 'user_actor.actor_id',
5376 'user_actor' => [
'JOIN',
'user_actor.actor_user = user_id' ],
5394 foreach ( MediaWikiServices::getInstance()
5417 if ( !$this->
getId() ) {
5422 if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {