29 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
43 use Wikimedia\Assert\Assert;
44 use Wikimedia\Assert\PreconditionException;
45 use Wikimedia\IPUtils;
49 use Wikimedia\ScopedCallback;
67 use ProtectedHookAccessorTrait;
118 'mEmailAuthenticated',
120 'mEmailTokenExpires',
278 if ( $name ===
'mRights' ) {
281 } elseif ( $name ===
'mOptions' ) {
285 } elseif ( !property_exists( $this, $name ) ) {
297 public function __set( $name, $value ) {
300 if ( $name ===
'mRights' ) {
301 MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
303 $value ===
null ? [] : $value
305 } elseif ( $name ===
'mOptions' ) {
307 MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $this );
308 foreach ( $value as $key => $val ) {
311 } elseif ( !property_exists( $this, $name ) ) {
312 $this->$name = $value;
320 array_keys( get_object_vars( $this ) ),
350 $this->mLoadedItems ===
true || $this->mFrom !==
'session';
358 public function load( $flags = self::READ_NORMAL ) {
361 if ( $this->mLoadedItems ===
true ) {
367 $this->mLoadedItems =
true;
368 $this->queryFlagsUsed = $flags;
373 ->warning(
'User::loadFromSession called before the end of Setup.php', [
374 'exception' =>
new Exception(
'User::loadFromSession called before the end of Setup.php' ),
377 $this->mLoadedItems = $oldLoadedItems;
381 switch ( $this->mFrom ) {
387 if ( $this->mId != 0 ) {
388 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
389 if ( $lb->hasOrMadeRecentMasterChanges() ) {
390 $flags |= self::READ_LATEST;
391 $this->queryFlagsUsed = $flags;
400 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
401 if ( $lb->hasOrMadeRecentMasterChanges() ) {
402 $flags |= self::READ_LATEST;
403 $this->queryFlagsUsed = $flags;
407 $row =
wfGetDB( $index )->selectRow(
409 [
'actor_id',
'actor_user',
'actor_name' ],
410 $this->mFrom ===
'name'
412 ? [
'actor_name' => IPUtils::sanitizeIP( $this->mName ) ]
413 : [
'actor_id' => $this->mActorId ],
420 $this->
loadDefaults( $this->mFrom ===
'name' ? $this->mName :
false );
421 } elseif ( $row->actor_user ) {
422 $this->mId = $row->actor_user;
425 $this->
loadDefaults( $row->actor_name, $row->actor_id );
433 $this->getHookRunner()->onUserLoadAfterLoadFromSession( $this );
436 throw new UnexpectedValueException(
437 "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
447 if ( $this->mId == 0 ) {
465 $this->mLoadedItems =
true;
466 $this->queryFlagsUsed = $flags;
476 public static function purge( $dbDomain, $userId ) {
477 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
478 $key =
$cache->makeGlobalKey(
'user',
'id', $dbDomain, $userId );
488 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
490 return $cache->makeGlobalKey(
'user',
'id', $lbFactory->getLocalDomainID(), $this->mId );
499 $id = $this->
getId();
501 return $id ? [ $this->
getCacheKey( $cache ) ] : [];
513 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
514 $data =
$cache->getWithSetCallback(
519 wfDebug(
"User: cache miss for user {$this->mId}" );
524 foreach ( self::$mCacheVars as $name ) {
525 $data[$name] = $this->$name;
531 $groupMemberships = MediaWikiServices::getInstance()
532 ->getUserGroupManager()
533 ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
537 foreach ( $groupMemberships as $ugm ) {
538 if ( $ugm->getExpiry() ) {
539 $secondsUntilExpiry =
540 wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
542 if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
543 $ttl = $secondsUntilExpiry;
551 [
'pcTTL' => $cache::TTL_PROC_LONG,
'version' =>
self::VERSION ]
555 foreach ( self::$mCacheVars as $name ) {
556 $this->$name = $data[$name];
584 public static function newFromName( $name, $validate =
'valid' ) {
586 $validationLevels = [
587 'valid' => UserFactory::RIGOR_VALID,
588 'usable' => UserFactory::RIGOR_USABLE,
589 'creatable' => UserFactory::RIGOR_CREATABLE
591 if ( $validate ===
true ) {
594 if ( $validate ===
false ) {
595 $validation = UserFactory::RIGOR_NONE;
596 } elseif ( array_key_exists( $validate, $validationLevels ) ) {
597 $validation = $validationLevels[ $validate ];
601 $validation = $validate;
604 $user = MediaWikiServices::getInstance()
606 ->newFromName( (
string)$name, $validation );
609 if ( $user ===
null ) {
624 return MediaWikiServices::getInstance()
626 ->newFromId( (
int)$id );
639 return MediaWikiServices::getInstance()
641 ->newFromActorId( (
int)$id );
658 if ( $identity instanceof
User ) {
662 return MediaWikiServices::getInstance()
664 ->newFromUserIdentity( $identity );
682 public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain =
false ) {
683 return MediaWikiServices::getInstance()
685 ->newFromAnyId( $userId, $userName, $actorId, $dbDomain );
702 return MediaWikiServices::getInstance()
704 ->newFromConfirmationCode( (
string)$code, $flags );
716 $user->mFrom =
'session';
717 $user->mRequest = $request;
779 'validate' => UserNameUtils::RIGOR_VALID,
786 $validationLevels = [
787 'valid' => UserNameUtils::RIGOR_VALID,
788 'usable' => UserNameUtils::RIGOR_USABLE,
789 'creatable' => UserNameUtils::RIGOR_CREATABLE
791 $validate = $options[
'validate'];
794 if ( $validate ===
false ) {
795 $validation = UserNameUtils::RIGOR_NONE;
796 } elseif ( array_key_exists( $validate, $validationLevels ) ) {
797 $validation = $validationLevels[ $validate ];
801 $validation = $validate;
804 if ( $validation !== UserNameUtils::RIGOR_VALID ) {
806 __METHOD__ .
' options["validation"] parameter must be omitted or set to "valid".',
810 $services = MediaWikiServices::getInstance();
811 $userNameUtils = $services->getUserNameUtils();
813 $name = $userNameUtils->getCanonical( (
string)$name, $validation );
814 if ( $name ===
false ) {
818 $loadBalancer = $services->getDBLoadBalancer();
822 $row =
$dbr->selectRow(
823 $userQuery[
'tables'],
824 $userQuery[
'fields'],
825 [
'user_name' => $name ],
832 $dbw = $loadBalancer->getConnectionRef(
DB_MASTER );
833 $row = $dbw->selectRow(
834 $userQuery[
'tables'],
835 $userQuery[
'fields'],
836 [
'user_name' => $name ],
845 if ( !$options[
'create'] ) {
852 if ( !$userNameUtils->isValid( $name ) || $userNameUtils->isUsable( $name ) ) {
858 $dbw = $loadBalancer->getConnectionRef(
DB_MASTER );
859 return $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) use ( $name ) {
863 [
'actor_name' => $name,
'actor_user' =>
null ],
875 $dbw->
delete(
'actor', [
'actor_id' => $row->actor_id ], $fname );
879 [
'actor_id' => $row->actor_id ],
880 [
'actor_id' => $user->getActorId() ],
883 $user->clearInstanceCache(
'id' );
884 $user->invalidateCache();
891 if ( !$user->isSystemUser() ) {
893 if ( !$options[
'steal'] ) {
897 $services->getAuthManager()->revokeAccessForUser( $name );
899 $user->invalidateEmail();
901 $user->saveSettings();
902 SessionManager::singleton()->preventSessionsForUser( $user->getName() );
916 public static function whoIs( $id ) {
936 public static function idFromName( $name, $flags = self::READ_NORMAL ) {
938 $name = (string)$name;
940 if ( $nt ===
null ) {
945 if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
946 return self::$idCacheByName[$name] ===
null ? null : (int)self::$idCacheByName[$name];
955 [
'user_name' => $nt->getText() ],
960 if (
$s ===
false ) {
963 $result = (int)
$s->user_id;
966 if ( count( self::$idCacheByName ) >= 1000 ) {
967 self::$idCacheByName = [];
970 self::$idCacheByName[$name] = $result;
979 self::$idCacheByName = [];
1000 public static function isIP( $name ) {
1001 return preg_match(
'/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
1002 || IPUtils::isIPv6( $name );
1013 return IPUtils::isValidRange( $this->mName );
1029 return MediaWikiServices::getInstance()->getUserNameUtils()->isValid( $name );
1045 return MediaWikiServices::getInstance()->getUserNameUtils()->isUsable( $name );
1059 if ( $groups === [] ) {
1063 $groups = array_unique( (array)$groups );
1064 $limit = min( 5000, $limit );
1066 $conds = [
'ug_group' => $groups ];
1067 if ( $after !==
null ) {
1068 $conds[] =
'ug_user > ' . (int)$after;
1072 $ids =
$dbr->selectFieldValues(
1079 'ORDER BY' =>
'ug_user',
1100 return MediaWikiServices::getInstance()->getUserNameUtils()->isCreatable( $name );
1146 if ( !$this->getHookRunner()->onIsValidPassword( $password, $result, $this ) ) {
1147 $status->error( $result );
1151 if ( $result ===
false ) {
1152 $status->merge( $upp->checkUserPassword( $this, $password ),
true );
1156 if ( $result ===
true ) {
1160 $status->error( $result );
1181 $validationLevels = [
1182 'valid' => UserNameUtils::RIGOR_VALID,
1183 'usable' => UserNameUtils::RIGOR_USABLE,
1184 'creatable' => UserNameUtils::RIGOR_CREATABLE
1187 if ( $validate ===
false ) {
1188 $validation = UserNameUtils::RIGOR_NONE;
1189 } elseif ( array_key_exists( $validate, $validationLevels ) ) {
1190 $validation = $validationLevels[ $validate ];
1194 $validation = $validate;
1197 return MediaWikiServices::getInstance()
1198 ->getUserNameUtils()
1199 ->getCanonical( (
string)$name, $validation );
1213 $this->mName = $name;
1214 $this->mActorId = $actorId;
1215 $this->mRealName =
'';
1218 $loggedOut = $this->mRequest && !defined(
'MW_NO_SESSION' )
1219 ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1220 if ( $loggedOut !== 0 ) {
1221 $this->mTouched =
wfTimestamp( TS_MW, $loggedOut );
1223 $this->mTouched =
'1'; # Allow any pages to be cached
1226 $this->mToken =
null;
1227 $this->mEmailAuthenticated =
null;
1228 $this->mEmailToken =
'';
1229 $this->mEmailTokenExpires =
null;
1232 $this->getHookRunner()->onUserLoadDefaults( $this, $name );
1248 return ( $this->mLoadedItems ===
true && $all ===
'all' ) ||
1249 ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] ===
true );
1260 if ( is_array( $this->mLoadedItems ) ) {
1261 $this->mLoadedItems[$item] =
true;
1273 $session = $this->
getRequest()->getSession();
1274 $user = $session->getUser();
1275 if ( $user->isRegistered() ) {
1279 $session->set(
'wsUserID', $this->
getId() );
1280 $session->set(
'wsUserName', $this->
getName() );
1281 $session->set(
'wsToken', $this->
getToken() );
1298 $this->mId = intval( $this->mId );
1300 if ( !$this->mId ) {
1310 $s = $db->selectRow(
1311 $userQuery[
'tables'],
1312 $userQuery[
'fields'],
1313 [
'user_id' => $this->mId ],
1319 $this->queryFlagsUsed = $flags;
1320 $this->getHookRunner()->onUserLoadFromDatabase( $this,
$s );
1322 if (
$s !==
false ) {
1349 if ( !is_object( $row ) ) {
1350 throw new InvalidArgumentException(
'$row must be an object' );
1355 if ( isset( $row->actor_id ) ) {
1356 $this->mActorId = (int)$row->actor_id;
1357 if ( $this->mActorId !== 0 ) {
1358 $this->mFrom =
'actor';
1365 if ( isset( $row->user_name ) && $row->user_name !==
'' ) {
1366 $this->mName = $row->user_name;
1367 $this->mFrom =
'name';
1373 if ( isset( $row->user_real_name ) ) {
1374 $this->mRealName = $row->user_real_name;
1380 if ( isset( $row->user_id ) ) {
1381 $this->mId = intval( $row->user_id );
1382 if ( $this->mId !== 0 ) {
1383 $this->mFrom =
'id';
1390 if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !==
'' ) {
1391 self::$idCacheByName[$row->user_name] = $row->user_id;
1394 if ( isset( $row->user_editcount ) ) {
1395 $this->mEditCount = $row->user_editcount;
1400 if ( isset( $row->user_touched ) ) {
1401 $this->mTouched =
wfTimestamp( TS_MW, $row->user_touched );
1406 if ( isset( $row->user_token ) ) {
1410 $this->mToken = rtrim( $row->user_token,
" \0" );
1411 if ( $this->mToken ===
'' ) {
1412 $this->mToken =
null;
1418 if ( isset( $row->user_email ) ) {
1419 $this->mEmail = $row->user_email;
1420 $this->mEmailAuthenticated =
wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1421 $this->mEmailToken = $row->user_email_token;
1422 $this->mEmailTokenExpires =
wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1429 $this->mLoadedItems =
true;
1432 if ( is_array( $data ) ) {
1434 if ( isset( $data[
'user_groups'] ) && is_array( $data[
'user_groups'] ) ) {
1435 MediaWikiServices::getInstance()
1436 ->getUserGroupManager()
1437 ->loadGroupMembershipsFromArray(
1439 $data[
'user_groups'],
1440 $this->queryFlagsUsed
1443 if ( isset( $data[
'user_properties'] ) && is_array( $data[
'user_properties'] ) ) {
1444 MediaWikiServices::getInstance()
1445 ->getUserOptionsManager()
1446 ->loadUserOptions( $this, $this->queryFlagsUsed, $data[
'user_properties'] );
1458 foreach ( self::$mCacheVars as $var ) {
1459 $this->$var = $user->$var;
1479 return MediaWikiServices::getInstance()
1480 ->getUserGroupManager()
1481 ->addUserToAutopromoteOnceGroups( $this, $event );
1494 if ( $this->mTouched ) {
1496 $conditions[
'user_touched'] = $db->
timestamp( $this->mTouched );
1515 if ( !$this->mId ) {
1523 $dbw->update(
'user',
1524 [
'user_touched' => $dbw->timestamp( $newTouched ) ],
1525 $this->makeUpdateConditions( $dbw, [
1526 'user_id' => $this->mId,
1530 $success = ( $dbw->affectedRows() > 0 );
1533 $this->mTouched = $newTouched;
1553 $this->mDatePreference =
null;
1554 $this->mBlockedby = -1; # Unset
1555 $this->mHash =
false;
1556 $this->mEditCount =
null;
1557 $this->mThisAsAuthority =
null;
1560 $services = MediaWikiServices::getInstance();
1561 $services->getPermissionManager()->invalidateUsersRightsCache( $this );
1562 $services->getUserOptionsManager()->clearUserOptionsCache( $this );
1563 $services->getTalkPageNotificationManager()->clearInstanceCache( $this );
1564 $services->getUserGroupManager()->clearCache( $this );
1565 $services->getUserEditTracker()->clearUserEditCache( $this );
1568 if ( $reloadFrom ) {
1569 $this->mLoadedItems = [];
1570 $this->mFrom = $reloadFrom;
1582 return MediaWikiServices::getInstance()
1583 ->getUserOptionsLookup()
1584 ->getDefaultOptions();
1595 return MediaWikiServices::getInstance()
1596 ->getUserOptionsLookup()
1597 ->getDefaultOption( $opt );
1611 private function getBlockedStatus( $fromReplica =
true, $disableIpBlockExemptChecking =
false ) {
1612 if ( $this->mBlockedby != -1 ) {
1616 wfDebug( __METHOD__ .
": checking blocked status for " . $this->
getName() );
1638 $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1642 $disableIpBlockExemptChecking
1646 $this->mBlock = $block;
1647 $this->mBlockedby = $block->getByName();
1648 $this->mBlockreason = $block->getReason();
1649 $this->mHideName = $block->getHideName();
1650 $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1652 $this->mBlock =
null;
1653 $this->mBlockedby =
'';
1654 $this->mBlockreason =
'';
1655 $this->mHideName = 0;
1656 $this->mAllowUsertalk =
false;
1673 return !$this->
isAllowed(
'noratelimit' );
1697 if ( !$this->getHookRunner()->onPingLimiter( $this, $action, $result, $incrBy ) ) {
1706 $limits = array_merge(
1707 [
'&can-bypass' =>
true ],
1716 $logger->debug( __METHOD__ .
": limiting $action rate for {$this->getName()}" );
1719 $id = $this->
getId();
1725 if ( isset( $limits[
'anon'] ) ) {
1726 $keys[
$cache->makeKey(
'limiter', $action,
'anon' )] = $limits[
'anon'];
1730 if ( isset( $limits[
'user-global'] ) ) {
1733 $centralId = $lookup
1739 $realm = $lookup->getProviderId();
1741 $globalKey =
$cache->makeGlobalKey(
'limiter', $action,
'user-global',
1742 $realm, $centralId );
1745 $globalKey =
$cache->makeKey(
'limiter', $action,
'user-global',
1748 $keys[$globalKey] = $limits[
'user-global'];
1754 if ( isset( $limits[
'ip'] ) ) {
1756 $keys[
$cache->makeGlobalKey(
'limiter', $action,
'ip', $ip )] = $limits[
'ip'];
1759 if ( isset( $limits[
'subnet'] ) ) {
1761 $subnet = IPUtils::getSubnet( $ip );
1762 if ( $subnet !==
false ) {
1763 $keys[
$cache->makeGlobalKey(
'limiter', $action,
'subnet', $subnet )] = $limits[
'subnet'];
1770 if ( $id !== 0 && isset( $limits[
'user'] ) ) {
1772 $userLimit = $limits[
'user'];
1775 if ( $id !== 0 && $isNewbie && isset( $limits[
'newbie'] ) ) {
1776 $userLimit = $limits[
'newbie'];
1780 foreach ( $this->
getGroups() as $group ) {
1781 if ( isset( $limits[$group] ) ) {
1782 if ( $userLimit ===
false
1783 || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1785 $userLimit = $limits[$group];
1792 if ( $userLimit !==
false ) {
1796 list( $max, $period ) = $userLimit;
1797 $logger->debug( __METHOD__ .
": effective user limit: $max in {$period}s" );
1798 $keys[
$cache->makeKey(
'limiter', $action,
'user', $id )] = $userLimit;
1802 if ( isset( $limits[
'ip-all'] ) ) {
1805 if ( $isNewbie || $userLimit ===
false
1806 || $limits[
'ip-all'][0] / $limits[
'ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1807 $keys[
$cache->makeGlobalKey(
'limiter', $action,
'ip-all', $ip )] = $limits[
'ip-all'];
1812 if ( isset( $limits[
'subnet-all'] ) ) {
1814 $subnet = IPUtils::getSubnet( $ip );
1815 if ( $subnet !==
false ) {
1817 if ( $isNewbie || $userLimit ===
false
1818 || $limits[
'ip-all'][0] / $limits[
'ip-all'][1]
1819 > $userLimit[0] / $userLimit[1] ) {
1820 $keys[
$cache->makeGlobalKey(
'limiter', $action,
'subnet-all', $subnet )] = $limits[
'subnet-all'];
1828 $now = MWTimestamp::time();
1832 foreach (
$keys as $key => $limit ) {
1841 function (
$cache, $key, $data, &$expiry )
1842 use ( $action, $logger, &$triggered, $now, $clockFudge, $limit, $incrBy )
1847 list( $max, $period ) = $limit;
1849 $expiry = $now + (int)$period;
1856 $fields = explode(
'|', $data );
1857 $storedCount = (int)( $fields[0] ?? 0 );
1858 $storedExpiry = (int)( $fields[1] ?? PHP_INT_MAX );
1861 if ( $storedExpiry < ( $now + $clockFudge ) ) {
1863 'User::pingLimiter: '
1864 .
'Stale rate limit entry, cache key failed to expire (T246991)',
1866 'action' => $action,
1869 'period' => $period,
1870 'count' => $storedCount,
1872 'expiry' => MWTimestamp::convert( TS_DB, $storedExpiry ),
1878 $expiry = min( $storedExpiry, $now + (
int)$period );
1879 $count = $storedCount;
1884 if ( $count >= $max ) {
1885 if ( !$triggered ) {
1887 'User::pingLimiter: User tripped rate limit',
1889 'action' => $action,
1893 'period' => $period,
1904 $data =
"$count|$expiry";
1936 public function getBlock( $fromReplica =
true, $disableIpBlockExemptChecking =
false ) {
1938 return $this->mBlock instanceof
AbstractBlock ? $this->mBlock :
null;
1953 return MediaWikiServices::getInstance()->getPermissionManager()
1954 ->isBlockedFrom( $this,
$title, $fromReplica );
1983 return ( $this->mBlock ? $this->mBlock->getId() :
false );
2009 if ( $this->mGlobalBlock !==
null ) {
2010 return $this->mGlobalBlock ?:
null;
2013 if ( IPUtils::isIPAddress( $this->
getName() ) ) {
2020 $this->getHookRunner()->onUserIsBlockedGlobally( $this, $ip, $blocked, $block );
2022 if ( $blocked && $block ===
null ) {
2026 'systemBlock' =>
'global-block'
2030 $this->mGlobalBlock = $blocked ? $block :
false;
2031 return $this->mGlobalBlock ?:
null;
2040 if ( $this->mLocked !==
null ) {
2044 $this->mLocked =
false;
2045 $this->getHookRunner()->onUserIsLocked( $this, $this->mLocked );
2055 if ( $this->mHideName !==
null ) {
2068 if ( $this->mId ===
null && $this->mName !==
null &&
2097 return $this->
getId();
2120 if ( $this->mName ===
false ) {
2122 $this->mName = IPUtils::sanitizeIP( $this->
getRequest()->getIP() );
2143 $this->mName = $str;
2157 if ( $dbwOrWikiId instanceof
IDatabase ) {
2167 if ( !$this->mActorId && $dbwOrWikiId instanceof
IDatabase ) {
2168 MediaWikiServices::getInstance()
2169 ->getActorStoreFactory()
2170 ->getActorNormalization( $dbwOrWikiId->getDomainID() )
2171 ->acquireActorId( $this, $dbwOrWikiId );
2173 Assert::postcondition(
2174 $this->mActorId !==
null,
2175 "Failed to acquire actor ID for user id {$this->mId} name {$this->mName}"
2193 $this->mActorId = $actorId;
2203 return str_replace(
' ',
'_', $this->
getName() );
2213 return MediaWikiServices::getInstance()
2214 ->getTalkPageNotificationManager()
2215 ->userHasNewMessages( $this );
2237 if ( !$this->getHookRunner()->onUserRetrieveNewTalks( $this, $talks ) ) {
2241 $services = MediaWikiServices::getInstance();
2242 $userHasNewMessages = $services->getTalkPageNotificationManager()
2243 ->userHasNewMessages( $this );
2244 if ( !$userHasNewMessages ) {
2248 $timestamp = $services->getTalkPageNotificationManager()
2249 ->getLatestSeenMessageTimestamp( $this );
2252 $revRecord = $services->getRevisionLookup()
2253 ->getRevisionByTimestamp( $utp, $timestamp );
2261 'link' => $utp->getLocalURL(),
2275 $newMessageRevisionId =
null;
2281 if ( $newMessageLinks && count( $newMessageLinks ) === 1
2283 && $newMessageLinks[0][
'rev']
2286 $newMessageRevision = $newMessageLinks[0][
'rev'];
2287 $newMessageRevisionId = $newMessageRevision->getId();
2290 return $newMessageRevisionId;
2303 if ( $curRev && $curRev instanceof
Revision ) {
2304 $curRev = $curRev->getRevisionRecord();
2307 MediaWikiServices::getInstance()
2308 ->getTalkPageNotificationManager()
2309 ->setUserHasNewMessages( $this, $curRev );
2311 MediaWikiServices::getInstance()
2312 ->getTalkPageNotificationManager()
2313 ->removeUserHasNewMessages( $this );
2325 if ( $this->mTouched ) {
2326 $time = max( $time,
wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2343 if ( !$this->
getId() ) {
2347 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2348 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2351 if ( $mode ===
'refresh' ) {
2352 $cache->delete( $key, 1 );
2354 $lb->getConnectionRef(
DB_MASTER )->onTransactionPreCommitOrIdle(
2355 static function () use (
$cache, $key ) {
2386 $id = $this->
getId();
2388 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2389 $key =
$cache->makeKey(
'user-quicktouched',
'id', $id );
2390 $cache->touchCheckKey( $key );
2391 $this->mQuickTouched =
null;
2401 return ( $timestamp >= $this->
getTouched() );
2416 if ( $this->mQuickTouched ===
null ) {
2417 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2418 $key =
$cache->makeKey(
'user-quicktouched',
'id', $this->mId );
2423 return max( $this->mTouched, $this->mQuickTouched );
2453 $manager = MediaWikiServices::getInstance()->getAuthManager();
2454 $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2455 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2458 foreach ( $reqs as $req ) {
2459 $status->merge( $manager->allowsAuthenticationDataChange( $req ),
true );
2461 if ( $status->getValue() ===
'ignored' ) {
2462 $status->warning(
'authenticationdatachange-ignored' );
2465 if ( $status->isGood() ) {
2466 foreach ( $reqs as $req ) {
2467 $manager->changeAuthenticationData( $req );
2483 if ( !$this->mToken && $forceCreation ) {
2487 if ( !$this->mToken ) {
2492 if ( $this->mToken === self::INVALID_TOKEN ) {
2507 $len = max( 32, self::TOKEN_LENGTH );
2508 if ( strlen( $ret ) < $len ) {
2510 throw new \UnexpectedValueException(
'Hmac returned less than 128 bits' );
2513 return substr( $ret, -$len );
2524 if ( $this->mToken === self::INVALID_TOKEN ) {
2526 ->debug( __METHOD__ .
": Ignoring attempt to set token for system user \"$this\"" );
2527 } elseif ( !$token ) {
2530 $this->mToken = $token;
2540 $this->getHookRunner()->onUserGetEmail( $this, $this->mEmail );
2550 $this->getHookRunner()->onUserGetEmailAuthenticationTimestamp(
2551 $this, $this->mEmailAuthenticated );
2565 $this->mEmail = $str;
2566 $this->getHookRunner()->onUserSetEmail( $this, $this->mEmail );
2584 if ( $str === $oldaddr ) {
2588 $type = $oldaddr !=
'' ?
'changed' :
'set';
2589 $notificationResult =
null;
2594 $change = $str !=
'' ?
'changed' :
'removed';
2595 $notificationResult = $this->
sendMail(
2596 wfMessage(
'notificationemail_subject_' . $change )->text(),
2597 wfMessage(
'notificationemail_body_' . $change,
2610 if ( $notificationResult !==
null ) {
2611 $result->merge( $notificationResult );
2614 if ( $result->isGood() ) {
2616 $result->value =
'eauth';
2643 $this->mRealName = $str;
2658 public function getOption( $oname, $defaultOverride =
null, $ignoreHidden =
false ) {
2659 if ( $oname ===
null ) {
2662 return MediaWikiServices::getInstance()
2663 ->getUserOptionsLookup()
2664 ->getOption( $this, $oname, $defaultOverride, $ignoreHidden );
2677 return MediaWikiServices::getInstance()
2678 ->getUserOptionsLookup()
2679 ->getOptions( $this, $flags );
2691 return MediaWikiServices::getInstance()
2692 ->getUserOptionsLookup()
2693 ->getBoolOption( $this, $oname );
2706 if ( $oname ===
null ) {
2709 return MediaWikiServices::getInstance()
2710 ->getUserOptionsLookup()
2711 ->getIntOption( $this, $oname, $defaultOverride );
2724 MediaWikiServices::getInstance()
2725 ->getUserOptionsManager()
2726 ->setOption( $this, $oname, $val );
2742 $id = $this->
getId();
2752 $token = hash_hmac(
'sha1',
"$oname:$id", $this->
getToken() );
2803 return MediaWikiServices::getInstance()
2804 ->getUserOptionsManager()
2805 ->listOptionKinds();
2822 return MediaWikiServices::getInstance()
2823 ->getUserOptionsManager()
2824 ->getOptionKinds( $this, $context, $options );
2843 $resetKinds = [
'registered',
'registered-multiselect',
'registered-checkmatrix',
'unused' ],
2846 MediaWikiServices::getInstance()
2847 ->getUserOptionsManager()
2861 if ( $this->mDatePreference ===
null ) {
2864 $map =
$wgLang->getDatePreferenceMigrationMap();
2865 if ( isset( $map[$value] ) ) {
2866 $value = $map[$value];
2868 $this->mDatePreference = $value;
2888 $this->getHookRunner()->onUserRequiresHTTPS( $this, $https );
2921 return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
2933 return MediaWikiServices::getInstance()
2934 ->getUserGroupManager()
2935 ->getUserGroups( $this, $this->queryFlagsUsed );
2948 return MediaWikiServices::getInstance()
2949 ->getUserGroupManager()
2950 ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
2964 return MediaWikiServices::getInstance()
2965 ->getUserGroupManager()
2966 ->getUserEffectiveGroups( $this, $this->queryFlagsUsed, $recache );
2980 return MediaWikiServices::getInstance()
2981 ->getUserGroupManager()
2982 ->getUserImplicitGroups( $this, $this->queryFlagsUsed, $recache );
2997 return MediaWikiServices::getInstance()
2998 ->getUserGroupManager()
2999 ->getUserFormerGroups( $this, $this->queryFlagsUsed );
3007 if ( !$this->
getId() ) {
3011 if ( $this->mEditCount ===
null ) {
3012 $this->mEditCount = MediaWikiServices::getInstance()
3013 ->getUserEditTracker()
3014 ->getUserEditCount( $this );
3033 return MediaWikiServices::getInstance()
3034 ->getUserGroupManager()
3035 ->addUserToGroup( $this, $group, $expiry,
true );
3048 return MediaWikiServices::getInstance()
3049 ->getUserGroupManager()
3050 ->removeUserFromGroup( $this, $group );
3062 return $this->
getId() != 0;
3093 $this->getHookRunner()->onUserIsBot( $this, $isBot );
3109 if ( $this->
getEmail() || $this->mToken !== self::INVALID_TOKEN ||
3110 MediaWikiServices::getInstance()->getAuthManager()->userCanAuthenticate( $this->mName )
3168 if ( $this->mRequest ) {
3183 if (
$title->isWatchable() && ( !$checkRights || $this->isAllowed(
'viewmywatchlist' ) ) ) {
3184 return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this,
$title );
3200 if (
$title->isWatchable() && ( !$checkRights || $this->isAllowed(
'viewmywatchlist' ) ) ) {
3201 return MediaWikiServices::getInstance()->getWatchedItemStore()
3202 ->isTempWatched( $this,
$title );
3218 $checkRights = self::CHECK_USER_RIGHTS,
3219 ?
string $expiry =
null
3221 if ( !
$title->isWatchable() ) {
3225 if ( !$checkRights || $this->
isAllowed(
'editmywatchlist' ) ) {
3226 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3227 $store->addWatch( $this,
$title->getSubjectPage(), $expiry );
3228 if (
$title->canHaveTalkPage() ) {
3229 $store->addWatch( $this,
$title->getTalkPage(), $expiry );
3243 if ( !
$title->isWatchable() ) {
3247 if ( !$checkRights || $this->
isAllowed(
'editmywatchlist' ) ) {
3248 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3249 $store->removeWatch( $this,
$title->getSubjectPage() );
3250 if (
$title->canHaveTalkPage() ) {
3251 $store->removeWatch( $this,
$title->getTalkPage() );
3269 MediaWikiServices::getInstance()
3270 ->getWatchlistNotificationManager()
3271 ->clearTitleUserNotifications( $this,
$title, $oldid );
3285 MediaWikiServices::getInstance()
3286 ->getWatchlistNotificationManager()
3287 ->clearAllUserNotifications( $this );
3310 if ( $registration ===
null ) {
3313 $registration = $experiencedRegistration;
3317 $registration > $learnerRegistration ) {
3322 $registration <= $experiencedRegistration
3324 return 'experienced';
3338 public function setCookies( $request =
null, $secure =
null, $rememberMe =
false ) {
3340 if ( $this->mId == 0 ) {
3344 $session = $this->
getRequest()->getSession();
3345 if ( $request && $session->getRequest() !== $request ) {
3346 $session = $session->sessionWithRequest( $request );
3348 $delay = $session->delaySave();
3350 if ( !$session->getUser()->equals( $this ) ) {
3351 if ( !$session->canSetUser() ) {
3353 ->warning( __METHOD__ .
3354 ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3358 $session->setUser( $this );
3361 $session->setRememberUser( $rememberMe );
3362 if ( $secure !==
null ) {
3363 $session->setForceHTTPS( $secure );
3366 $session->persist();
3368 ScopedCallback::consume( $delay );
3377 if ( $this->getHookRunner()->onUserLogout( $user ) ) {
3387 $session = $this->
getRequest()->getSession();
3388 if ( !$session->canSetUser() ) {
3390 ->warning( __METHOD__ .
": Cannot log out of an immutable session" );
3391 $error =
'immutable';
3392 } elseif ( !$session->getUser()->equals( $this ) ) {
3394 ->warning( __METHOD__ .
3395 ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3399 $error =
'wronguser';
3402 $delay = $session->delaySave();
3403 $session->unpersist();
3404 $session->setLoggedOutTimestamp( time() );
3405 $session->setUser(
new User );
3406 $session->set(
'wsUserID', 0 );
3407 $session->resetAllTokens();
3408 ScopedCallback::consume( $delay );
3412 'event' =>
'logout',
3413 'successful' => $error ===
false,
3414 'status' => $error ?:
'success',
3428 "Could not update user with ID '{$this->mId}'; DB is read-only."
3434 if ( $this->mId == 0 ) {
3444 $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) use ( $newTouched ) {
3447 'user_name' => $this->mName,
3448 'user_real_name' => $this->mRealName,
3449 'user_email' => $this->mEmail,
3450 'user_email_authenticated' => $dbw->
timestampOrNull( $this->mEmailAuthenticated ),
3451 'user_touched' => $dbw->
timestamp( $newTouched ),
3452 'user_token' => strval( $this->mToken ),
3453 'user_email_token' => $this->mEmailToken,
3454 'user_email_token_expires' => $dbw->
timestampOrNull( $this->mEmailTokenExpires ),
3455 ], $this->makeUpdateConditions( $dbw, [
3456 'user_id' => $this->mId,
3462 $this->clearSharedCache(
'refresh' );
3464 $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ?
'master' :
'replica';
3465 LoggerFactory::getInstance(
'preferences' )->warning(
3466 "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
3467 [
'user_id' => $this->mId,
'db_flag' => $from ]
3469 throw new MWException(
"CAS update failed on user_touched. " .
3470 "The version of the user to be saved is older than the current version."
3476 [
'actor_name' => $this->mName ],
3477 [
'actor_user' => $this->mId ],
3482 $this->mTouched = $newTouched;
3483 MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3485 $this->getHookRunner()->onUserSaveSettings( $this );
3487 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3488 $hcu->purgeTitleUrls( $this->
getUserPage(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
3506 $id = $db->selectField(
'user',
3507 'user_id', [
'user_name' =>
$s ], __METHOD__, $options );
3528 foreach ( [
'password',
'newpassword',
'newpass_time',
'password_expires' ] as $field ) {
3529 if ( isset( $params[$field] ) ) {
3530 wfDeprecated( __METHOD__ .
" with param '$field'",
'1.27' );
3531 unset( $params[$field] );
3538 if ( isset( $params[
'options'] ) ) {
3539 MediaWikiServices::getInstance()
3540 ->getUserOptionsManager()
3541 ->loadUserOptions( $user, $user->queryFlagsUsed, $params[
'options'] );
3542 unset( $params[
'options'] );
3549 'user_name' => $name,
3550 'user_password' => $noPass,
3551 'user_newpassword' => $noPass,
3552 'user_email' => $user->mEmail,
3553 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3554 'user_real_name' => $user->mRealName,
3555 'user_token' => strval( $user->mToken ),
3556 'user_registration' => $dbw->timestamp( $user->mRegistration ),
3557 'user_editcount' => 0,
3558 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3560 foreach ( $params as $name => $value ) {
3561 $fields[
"user_$name"] = $value;
3564 return $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) use ( $fields ) {
3565 $dbw->
insert(
'user', $fields, $fname, [
'IGNORE' ] );
3567 $newUser = self::newFromId( $dbw->insertId() );
3568 $newUser->mName = $fields[
'user_name'];
3569 $newUser->updateActorId( $dbw );
3571 $newUser->load( self::READ_LATEST );
3607 if ( !$this->mToken ) {
3611 if ( !is_string( $this->mName ) ) {
3612 throw new RuntimeException(
"User name field is not set." );
3618 $status = $dbw->doAtomicSection( __METHOD__,
function (
IDatabase $dbw, $fname ) {
3622 'user_name' => $this->mName,
3623 'user_password' => $noPass,
3624 'user_newpassword' => $noPass,
3625 'user_email' => $this->mEmail,
3626 'user_email_authenticated' => $dbw->
timestampOrNull( $this->mEmailAuthenticated ),
3627 'user_real_name' => $this->mRealName,
3628 'user_token' => strval( $this->mToken ),
3629 'user_registration' => $dbw->
timestamp( $this->mRegistration ),
3630 'user_editcount' => 0,
3631 'user_touched' => $dbw->
timestamp( $this->mTouched ),
3640 [
'user_name' => $this->mName ],
3642 [
'LOCK IN SHARE MODE' ]
3649 throw new MWException( $fname .
": hit a key conflict attempting " .
3650 "to insert user '{$this->mName}' row, but it was not present in select!" );
3660 if ( !$status->isGood() ) {
3667 MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3678 [
'actor_user' => $this->mId,
'actor_name' => $this->mName ],
3681 $this->mActorId = (int)$dbw->
insertId();
3705 if ( $this->mId == 0 ) {
3709 $userblock = DatabaseBlock::newFromTarget( $this->
getName() );
3710 if ( !$userblock ) {
3714 return (
bool)$userblock->doAutoblock( $this->
getRequest()->getIP() );
3723 if ( $this->mBlock && $this->mBlock->appliesToRight(
'createaccount' ) ) {
3727 # T15611: if the IP address the user is trying to create an account from is
3728 # blocked with createaccount disabled, prevent new account creation there even
3729 # when the user is logged in
3730 if ( $this->mBlockedFromCreateAccount ===
false && !$this->
isAllowed(
'ipblock-exempt' ) ) {
3731 $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
3735 return $this->mBlockedFromCreateAccount instanceof
AbstractBlock
3736 && $this->mBlockedFromCreateAccount->
appliesToRight(
'createaccount' )
3737 ? $this->mBlockedFromCreateAccount
3747 return $this->mBlock && $this->mBlock->appliesToRight(
'sendemail' );
3758 return $this->mBlock && $this->mBlock->appliesToRight(
'upload' );
3785 return $title->getTalkPage();
3794 return !$this->
isAllowed(
'autoconfirmed' );
3816 return $request->getSession()->getToken( $salt );
3848 public function matchEditToken( $val, $salt =
'', $request =
null, $maxage =
null ) {
3863 $val = substr( $val, 0, strspn( $val,
'0123456789abcdef' ) ) . Token::SUFFIX;
3882 if (
$type ==
'created' ||
$type ===
false ) {
3883 $message =
'confirmemail_body';
3885 } elseif (
$type ===
true ) {
3886 $message =
'confirmemail_body_changed';
3890 $message =
'confirmemail_body_' .
$type;
3894 'subject' =>
wfMessage(
'confirmemail_subject' )->text(),
3899 $wgLang->userTimeAndDate( $expiration, $this ),
3901 $wgLang->userDate( $expiration, $this ),
3902 $wgLang->userTime( $expiration, $this ) )->text(),
3909 'confirmURL' => $url,
3910 'invalidateURL' => $invalidateURL,
3911 'expiration' => $expiration
3914 $this->getHookRunner()->onUserSendConfirmationMail( $this, $mail, $info );
3915 return $this->
sendMail( $mail[
'subject'], $mail[
'body'], $mail[
'from'], $mail[
'replyTo'] );
3929 public function sendMail( $subject, $body, $from =
null, $replyto =
null ) {
3932 if ( $from instanceof
User ) {
3936 wfMessage(
'emailsender' )->inContentLanguage()->text() );
3941 'replyTo' => $replyto,
3962 $hash = md5( $token );
3963 $this->mEmailToken = $hash;
3964 $this->mEmailTokenExpires = $expiration;
3974 return $this->
getTokenUrl(
'ConfirmEmail', $token );
3983 return $this->
getTokenUrl(
'InvalidateEmail', $token );
4003 return $title->getCanonicalURL();
4018 $this->getHookRunner()->onConfirmEmailComplete( $this );
4032 $this->mEmailToken =
null;
4033 $this->mEmailTokenExpires =
null;
4036 $this->getHookRunner()->onInvalidateEmailComplete( $this );
4046 $this->mEmailAuthenticated = $timestamp;
4047 $this->getHookRunner()->onUserSetEmailAuthenticationTimestamp(
4048 $this, $this->mEmailAuthenticated );
4062 $this->getHookRunner()->onUserCanSendEmail( $this, $canSend );
4091 if ( $this->getHookRunner()->onEmailConfirmed( $user, $confirmed ) ) {
4115 $this->mEmailToken &&
4141 return MediaWikiServices::getInstance()
4142 ->getUserEditTracker()
4143 ->getFirstEditTimestamp( $this );
4154 return MediaWikiServices::getInstance()
4155 ->getUserEditTracker()
4156 ->getLatestEditTimestamp( $this );
4169 return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4182 return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4201 return MediaWikiServices::getInstance()->getPermissionManager()
4202 ->groupHasPermission( $group, $role );
4213 return MediaWikiServices::getInstance()
4214 ->getUserGroupManager()
4223 return MediaWikiServices::getInstance()
4224 ->getUserGroupManager()
4225 ->listAllImplicitGroups();
4269 if ( is_int( $key ) ) {
4277 if ( is_int( $key ) ) {
4312 if ( $this->
isAllowed(
'userrights' ) ) {
4317 $all = array_merge( self::getAllGroups() );
4335 foreach ( $addergroups as $addergroup ) {
4336 $groups = array_merge_recursive(
4339 $groups[
'add'] = array_unique( $groups[
'add'] );
4340 $groups[
'remove'] = array_unique( $groups[
'remove'] );
4341 $groups[
'add-self'] = array_unique( $groups[
'add-self'] );
4342 $groups[
'remove-self'] = array_unique( $groups[
'remove-self'] );
4357 DeferredUpdates::POSTSEND
4367 $this->mEditCount = $count;
4378 return MediaWikiServices::getInstance()
4379 ->getUserEditTracker()
4380 ->initializeUserEditCount( $this );
4391 $key =
"right-$right";
4393 return $msg->isDisabled() ? $right : $msg->text();
4420 'tables' => [
'user',
'user_actor' =>
'actor' ],
4428 'user_email_authenticated',
4430 'user_email_token_expires',
4431 'user_registration',
4433 'user_actor.actor_id',
4436 'user_actor' => [
'JOIN',
'user_actor.actor_user = user_id' ],
4454 foreach ( MediaWikiServices::getInstance()
4455 ->getPermissionManager()
4477 if ( !$this->
getId() ) {
4482 if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
4569 if ( !$this->mThisAsAuthority ) {
4578 MediaWikiServices::getInstance()->getPermissionManager()
4592 $globalUserName = $sessionUser->isSafeToLoad()
4593 ? $sessionUser->getName()
4594 : IPUtils::sanitizeIP( $sessionUser->getRequest()->getIP() );
4596 return $this->
getName() === $globalUserName;