MediaWiki  1.34.4
User.php
Go to the documentation of this file.
1 <?php
34 use Wikimedia\Assert\Assert;
35 use Wikimedia\IPSet;
36 use Wikimedia\ScopedCallback;
40 
51 class User implements IDBAccessObject, UserIdentity {
52 
56  const TOKEN_LENGTH = 32;
57 
61  const INVALID_TOKEN = '*** INVALID ***';
62 
67  const VERSION = 14;
68 
74 
78  const CHECK_USER_RIGHTS = true;
79 
83  const IGNORE_USER_RIGHTS = false;
84 
92  protected static $mCacheVars = [
93  // user table
94  'mId',
95  'mName',
96  'mRealName',
97  'mEmail',
98  'mTouched',
99  'mToken',
100  'mEmailAuthenticated',
101  'mEmailToken',
102  'mEmailTokenExpires',
103  'mRegistration',
104  'mEditCount',
105  // user_groups table
106  'mGroupMemberships',
107  // user_properties table
108  'mOptionOverrides',
109  // actor table
110  'mActorId',
111  ];
112 
114  private static $reservedUsernames = false;
115 
117  // @{
119  public $mId;
121  public $mName;
123  protected $mActorId;
125  public $mRealName;
126 
128  public $mEmail;
130  public $mTouched;
132  protected $mQuickTouched;
134  protected $mToken;
138  protected $mEmailToken;
142  protected $mRegistration;
144  protected $mEditCount;
148  protected $mOptionOverrides;
149  // @}
150 
151  // @{
156 
160  protected $mLoadedItems = [];
161  // @}
162 
173  public $mFrom;
174 
179  protected $mNewtalk;
181  protected $mDatePreference;
183  public $mBlockedby;
185  protected $mHash;
187  protected $mBlockreason;
189  protected $mEffectiveGroups;
191  protected $mImplicitGroups;
193  protected $mFormerGroups;
195  protected $mGlobalBlock;
197  protected $mLocked;
199  public $mHideName;
201  public $mOptions;
202 
204  private $mRequest;
205 
207  public $mBlock;
208 
210  protected $mAllowUsertalk;
211 
213  private $mBlockedFromCreateAccount = false;
214 
216  protected $queryFlagsUsed = self::READ_NORMAL;
217 
219  public static $idCacheByName = [];
220 
232  public function __construct() {
233  $this->clearInstanceCache( 'defaults' );
234  }
235 
239  public function __toString() {
240  return (string)$this->getName();
241  }
242 
243  public function &__get( $name ) {
244  // A shortcut for $mRights deprecation phase
245  if ( $name === 'mRights' ) {
246  $copy = $this->getRights();
247  return $copy;
248  } elseif ( !property_exists( $this, $name ) ) {
249  // T227688 - do not break $u->foo['bar'] = 1
250  wfLogWarning( 'tried to get non-existent property' );
251  $this->$name = null;
252  return $this->$name;
253  } else {
254  wfLogWarning( 'tried to get non-visible property' );
255  $null = null;
256  return $null;
257  }
258  }
259 
260  public function __set( $name, $value ) {
261  // A shortcut for $mRights deprecation phase, only known legitimate use was for
262  // testing purposes, other uses seem bad in principle
263  if ( $name === 'mRights' ) {
264  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
265  $this,
266  is_null( $value ) ? [] : $value
267  );
268  } elseif ( !property_exists( $this, $name ) ) {
269  $this->$name = $value;
270  } else {
271  wfLogWarning( 'tried to set non-visible property' );
272  }
273  }
274 
289  public function isSafeToLoad() {
290  global $wgFullyInitialised;
291 
292  // The user is safe to load if:
293  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
294  // * mLoadedItems === true (already loaded)
295  // * mFrom !== 'session' (sessions not involved at all)
296 
297  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
298  $this->mLoadedItems === true || $this->mFrom !== 'session';
299  }
300 
306  public function load( $flags = self::READ_NORMAL ) {
307  global $wgFullyInitialised;
308 
309  if ( $this->mLoadedItems === true ) {
310  return;
311  }
312 
313  // Set it now to avoid infinite recursion in accessors
314  $oldLoadedItems = $this->mLoadedItems;
315  $this->mLoadedItems = true;
316  $this->queryFlagsUsed = $flags;
317 
318  // If this is called too early, things are likely to break.
319  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
321  ->warning( 'User::loadFromSession called before the end of Setup.php', [
322  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
323  ] );
324  $this->loadDefaults();
325  $this->mLoadedItems = $oldLoadedItems;
326  return;
327  }
328 
329  switch ( $this->mFrom ) {
330  case 'defaults':
331  $this->loadDefaults();
332  break;
333  case 'id':
334  // Make sure this thread sees its own changes, if the ID isn't 0
335  if ( $this->mId != 0 ) {
336  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
337  if ( $lb->hasOrMadeRecentMasterChanges() ) {
338  $flags |= self::READ_LATEST;
339  $this->queryFlagsUsed = $flags;
340  }
341  }
342 
343  $this->loadFromId( $flags );
344  break;
345  case 'actor':
346  case 'name':
347  // Make sure this thread sees its own changes
348  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
349  if ( $lb->hasOrMadeRecentMasterChanges() ) {
350  $flags |= self::READ_LATEST;
351  $this->queryFlagsUsed = $flags;
352  }
353 
354  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
355  $row = wfGetDB( $index )->selectRow(
356  'actor',
357  [ 'actor_id', 'actor_user', 'actor_name' ],
358  $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
359  __METHOD__,
360  $options
361  );
362 
363  if ( !$row ) {
364  // Ugh.
365  $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
366  } elseif ( $row->actor_user ) {
367  $this->mId = $row->actor_user;
368  $this->loadFromId( $flags );
369  } else {
370  $this->loadDefaults( $row->actor_name, $row->actor_id );
371  }
372  break;
373  case 'session':
374  if ( !$this->loadFromSession() ) {
375  // Loading from session failed. Load defaults.
376  $this->loadDefaults();
377  }
378  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
379  break;
380  default:
381  throw new UnexpectedValueException(
382  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
383  }
384  }
385 
391  public function loadFromId( $flags = self::READ_NORMAL ) {
392  if ( $this->mId == 0 ) {
393  // Anonymous users are not in the database (don't need cache)
394  $this->loadDefaults();
395  return false;
396  }
397 
398  // Try cache (unless this needs data from the master DB).
399  // NOTE: if this thread called saveSettings(), the cache was cleared.
400  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
401  if ( $latest ) {
402  if ( !$this->loadFromDatabase( $flags ) ) {
403  // Can't load from ID
404  return false;
405  }
406  } else {
407  $this->loadFromCache();
408  }
409 
410  $this->mLoadedItems = true;
411  $this->queryFlagsUsed = $flags;
412 
413  return true;
414  }
415 
421  public static function purge( $dbDomain, $userId ) {
422  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
423  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
424  $cache->delete( $key );
425  }
426 
432  protected function getCacheKey( WANObjectCache $cache ) {
433  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
434 
435  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
436  }
437 
444  $id = $this->getId();
445 
446  return $id ? [ $this->getCacheKey( $cache ) ] : [];
447  }
448 
455  protected function loadFromCache() {
456  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
457  $data = $cache->getWithSetCallback(
458  $this->getCacheKey( $cache ),
459  $cache::TTL_HOUR,
460  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
461  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
462  wfDebug( "User: cache miss for user {$this->mId}\n" );
463 
464  $this->loadFromDatabase( self::READ_NORMAL );
465  $this->loadGroups();
466  $this->loadOptions();
467 
468  $data = [];
469  foreach ( self::$mCacheVars as $name ) {
470  $data[$name] = $this->$name;
471  }
472 
473  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
474 
475  // if a user group membership is about to expire, the cache needs to
476  // expire at that time (T163691)
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;
482  }
483  }
484  }
485 
486  return $data;
487  },
488  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
489  );
490 
491  // Restore from cache
492  foreach ( self::$mCacheVars as $name ) {
493  $this->$name = $data[$name];
494  }
495 
496  return true;
497  }
498 
500  // @{
501 
518  public static function newFromName( $name, $validate = 'valid' ) {
519  if ( $validate === true ) {
520  $validate = 'valid';
521  }
522  $name = self::getCanonicalName( $name, $validate );
523  if ( $name === false ) {
524  return false;
525  }
526 
527  // Create unloaded user object
528  $u = new User;
529  $u->mName = $name;
530  $u->mFrom = 'name';
531  $u->setItemLoaded( 'name' );
532 
533  return $u;
534  }
535 
542  public static function newFromId( $id ) {
543  $u = new User;
544  $u->mId = $id;
545  $u->mFrom = 'id';
546  $u->setItemLoaded( 'id' );
547  return $u;
548  }
549 
557  public static function newFromActorId( $id ) {
558  $u = new User;
559  $u->mActorId = $id;
560  $u->mFrom = 'actor';
561  $u->setItemLoaded( 'actor' );
562  return $u;
563  }
564 
574  public static function newFromIdentity( UserIdentity $identity ) {
575  if ( $identity instanceof User ) {
576  return $identity;
577  }
578 
579  return self::newFromAnyId(
580  $identity->getId() === 0 ? null : $identity->getId(),
581  $identity->getName() === '' ? null : $identity->getName(),
582  $identity->getActorId() === 0 ? null : $identity->getActorId()
583  );
584  }
585 
599  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
600  // Stop-gap solution for the problem described in T222212.
601  // Force the User ID and Actor ID to zero for users loaded from the database
602  // of another wiki, to prevent subtle data corruption and confusing failure modes.
603  if ( $dbDomain !== false ) {
604  $userId = 0;
605  $actorId = 0;
606  }
607 
608  $user = new User;
609  $user->mFrom = 'defaults';
610 
611  if ( $actorId !== null ) {
612  $user->mActorId = (int)$actorId;
613  if ( $user->mActorId !== 0 ) {
614  $user->mFrom = 'actor';
615  }
616  $user->setItemLoaded( 'actor' );
617  }
618 
619  if ( $userName !== null && $userName !== '' ) {
620  $user->mName = $userName;
621  $user->mFrom = 'name';
622  $user->setItemLoaded( 'name' );
623  }
624 
625  if ( $userId !== null ) {
626  $user->mId = (int)$userId;
627  if ( $user->mId !== 0 ) {
628  $user->mFrom = 'id';
629  }
630  $user->setItemLoaded( 'id' );
631  }
632 
633  if ( $user->mFrom === 'defaults' ) {
634  throw new InvalidArgumentException(
635  'Cannot create a user with no name, no ID, and no actor ID'
636  );
637  }
638 
639  return $user;
640  }
641 
653  public static function newFromConfirmationCode( $code, $flags = 0 ) {
654  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
655  ? wfGetDB( DB_MASTER )
656  : wfGetDB( DB_REPLICA );
657 
658  $id = $db->selectField(
659  'user',
660  'user_id',
661  [
662  'user_email_token' => md5( $code ),
663  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
664  ]
665  );
666 
667  return $id ? self::newFromId( $id ) : null;
668  }
669 
677  public static function newFromSession( WebRequest $request = null ) {
678  $user = new User;
679  $user->mFrom = 'session';
680  $user->mRequest = $request;
681  return $user;
682  }
683 
699  public static function newFromRow( $row, $data = null ) {
700  $user = new User;
701  $user->loadFromRow( $row, $data );
702  return $user;
703  }
704 
740  public static function newSystemUser( $name, $options = [] ) {
741  $options += [
742  'validate' => 'valid',
743  'create' => true,
744  'steal' => false,
745  ];
746 
747  $name = self::getCanonicalName( $name, $options['validate'] );
748  if ( $name === false ) {
749  return null;
750  }
751 
752  $dbr = wfGetDB( DB_REPLICA );
753  $userQuery = self::getQueryInfo();
754  $row = $dbr->selectRow(
755  $userQuery['tables'],
756  $userQuery['fields'],
757  [ 'user_name' => $name ],
758  __METHOD__,
759  [],
760  $userQuery['joins']
761  );
762  if ( !$row ) {
763  // Try the master database...
764  $dbw = wfGetDB( DB_MASTER );
765  $row = $dbw->selectRow(
766  $userQuery['tables'],
767  $userQuery['fields'],
768  [ 'user_name' => $name ],
769  __METHOD__,
770  [],
771  $userQuery['joins']
772  );
773  }
774 
775  if ( !$row ) {
776  // No user. Create it?
777  if ( !$options['create'] ) {
778  // No.
779  return null;
780  }
781 
782  // If it's a reserved user that had an anonymous actor created for it at
783  // some point, we need special handling.
784  if ( !self::isValidUserName( $name ) || self::isUsableName( $name ) ) {
785  // Not reserved, so just create it.
786  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
787  }
788 
789  // It is reserved. Check for an anonymous actor row.
790  $dbw = wfGetDB( DB_MASTER );
791  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $name ) {
792  $row = $dbw->selectRow(
793  'actor',
794  [ 'actor_id' ],
795  [ 'actor_name' => $name, 'actor_user' => null ],
796  $fname,
797  [ 'FOR UPDATE' ]
798  );
799  if ( !$row ) {
800  // No anonymous actor.
801  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
802  }
803 
804  // There is an anonymous actor. Delete the actor row so we can create the user,
805  // then restore the old actor_id so as to not break existing references.
806  // @todo If MediaWiki ever starts using foreign keys for `actor`, this will break things.
807  $dbw->delete( 'actor', [ 'actor_id' => $row->actor_id ], $fname );
808  $user = self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
809  $dbw->update(
810  'actor',
811  [ 'actor_id' => $row->actor_id ],
812  [ 'actor_id' => $user->getActorId() ],
813  $fname
814  );
815  $user->clearInstanceCache( 'id' );
816  $user->invalidateCache();
817  return $user;
818  } );
819  }
820 
821  $user = self::newFromRow( $row );
822 
823  // A user is considered to exist as a non-system user if it can
824  // authenticate, or has an email set, or has a non-invalid token.
825  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
826  AuthManager::singleton()->userCanAuthenticate( $name )
827  ) {
828  // User exists. Steal it?
829  if ( !$options['steal'] ) {
830  return null;
831  }
832 
833  AuthManager::singleton()->revokeAccessForUser( $name );
834 
835  $user->invalidateEmail();
836  $user->mToken = self::INVALID_TOKEN;
837  $user->saveSettings();
838  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
839  }
840 
841  return $user;
842  }
843 
844  // @}
845 
851  public static function whoIs( $id ) {
852  return UserCache::singleton()->getProp( $id, 'name' );
853  }
854 
861  public static function whoIsReal( $id ) {
862  return UserCache::singleton()->getProp( $id, 'real_name' );
863  }
864 
871  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
872  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
873  $name = (string)$name;
874  $nt = Title::makeTitleSafe( NS_USER, $name );
875  if ( is_null( $nt ) ) {
876  // Illegal name
877  return null;
878  }
879 
880  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
881  return is_null( self::$idCacheByName[$name] ) ? null : (int)self::$idCacheByName[$name];
882  }
883 
884  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
885  $db = wfGetDB( $index );
886 
887  $s = $db->selectRow(
888  'user',
889  [ 'user_id' ],
890  [ 'user_name' => $nt->getText() ],
891  __METHOD__,
892  $options
893  );
894 
895  if ( $s === false ) {
896  $result = null;
897  } else {
898  $result = (int)$s->user_id;
899  }
900 
901  if ( count( self::$idCacheByName ) >= 1000 ) {
902  self::$idCacheByName = [];
903  }
904 
905  self::$idCacheByName[$name] = $result;
906 
907  return $result;
908  }
909 
913  public static function resetIdByNameCache() {
914  self::$idCacheByName = [];
915  }
916 
933  public static function isIP( $name ) {
934  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
935  || IP::isIPv6( $name );
936  }
937 
944  public function isIPRange() {
945  return IP::isValidRange( $this->mName );
946  }
947 
959  public static function isValidUserName( $name ) {
960  global $wgMaxNameChars;
961 
962  if ( $name == ''
963  || self::isIP( $name )
964  || strpos( $name, '/' ) !== false
965  || strlen( $name ) > $wgMaxNameChars
966  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
967  ) {
968  return false;
969  }
970 
971  // Ensure that the name can't be misresolved as a different title,
972  // such as with extra namespace keys at the start.
973  $parsed = Title::newFromText( $name );
974  if ( is_null( $parsed )
975  || $parsed->getNamespace()
976  || strcmp( $name, $parsed->getPrefixedText() ) ) {
977  return false;
978  }
979 
980  // Check an additional blacklist of troublemaker characters.
981  // Should these be merged into the title char list?
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
989  ']/u';
990  if ( preg_match( $unicodeBlacklist, $name ) ) {
991  return false;
992  }
993 
994  return true;
995  }
996 
1008  public static function isUsableName( $name ) {
1009  global $wgReservedUsernames;
1010  // Must be a valid username, obviously ;)
1011  if ( !self::isValidUserName( $name ) ) {
1012  return false;
1013  }
1014 
1015  if ( !self::$reservedUsernames ) {
1016  self::$reservedUsernames = $wgReservedUsernames;
1017  Hooks::run( 'UserGetReservedNames', [ &self::$reservedUsernames ] );
1018  }
1019 
1020  // Certain names may be reserved for batch processes.
1021  foreach ( self::$reservedUsernames as $reserved ) {
1022  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1023  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1024  }
1025  if ( $reserved == $name ) {
1026  return false;
1027  }
1028  }
1029  return true;
1030  }
1031 
1042  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1043  if ( $groups === [] ) {
1044  return UserArrayFromResult::newFromIDs( [] );
1045  }
1046 
1047  $groups = array_unique( (array)$groups );
1048  $limit = min( 5000, $limit );
1049 
1050  $conds = [ 'ug_group' => $groups ];
1051  if ( $after !== null ) {
1052  $conds[] = 'ug_user > ' . (int)$after;
1053  }
1054 
1055  $dbr = wfGetDB( DB_REPLICA );
1056  $ids = $dbr->selectFieldValues(
1057  'user_groups',
1058  'ug_user',
1059  $conds,
1060  __METHOD__,
1061  [
1062  'DISTINCT' => true,
1063  'ORDER BY' => 'ug_user',
1064  'LIMIT' => $limit,
1065  ]
1066  ) ?: [];
1067  return UserArray::newFromIDs( $ids );
1068  }
1069 
1082  public static function isCreatableName( $name ) {
1084 
1085  // Ensure that the username isn't longer than 235 bytes, so that
1086  // (at least for the builtin skins) user javascript and css files
1087  // will work. (T25080)
1088  if ( strlen( $name ) > 235 ) {
1089  wfDebugLog( 'username', __METHOD__ .
1090  ": '$name' invalid due to length" );
1091  return false;
1092  }
1093 
1094  // Preg yells if you try to give it an empty string
1095  if ( $wgInvalidUsernameCharacters !== '' &&
1096  preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name )
1097  ) {
1098  wfDebugLog( 'username', __METHOD__ .
1099  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1100  return false;
1101  }
1102 
1103  return self::isUsableName( $name );
1104  }
1105 
1112  public function isValidPassword( $password ) {
1113  // simple boolean wrapper for checkPasswordValidity
1114  return $this->checkPasswordValidity( $password )->isGood();
1115  }
1116 
1138  public function checkPasswordValidity( $password ) {
1139  global $wgPasswordPolicy;
1140 
1141  $upp = new UserPasswordPolicy(
1142  $wgPasswordPolicy['policies'],
1143  $wgPasswordPolicy['checks']
1144  );
1145 
1146  $status = Status::newGood( [] );
1147  $result = false; // init $result to false for the internal checks
1148 
1149  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1150  $status->error( $result );
1151  return $status;
1152  }
1153 
1154  if ( $result === false ) {
1155  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1156  return $status;
1157  }
1158 
1159  if ( $result === true ) {
1160  return $status;
1161  }
1162 
1163  $status->error( $result );
1164  return $status; // the isValidPassword hook set a string $result and returned true
1165  }
1166 
1180  public static function getCanonicalName( $name, $validate = 'valid' ) {
1181  // Force usernames to capital
1182  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1183 
1184  # Reject names containing '#'; these will be cleaned up
1185  # with title normalisation, but then it's too late to
1186  # check elsewhere
1187  if ( strpos( $name, '#' ) !== false ) {
1188  return false;
1189  }
1190 
1191  // Clean up name according to title rules,
1192  // but only when validation is requested (T14654)
1193  $t = ( $validate !== false ) ?
1194  Title::newFromText( $name, NS_USER ) : Title::makeTitle( NS_USER, $name );
1195  // Check for invalid titles
1196  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1197  return false;
1198  }
1199 
1200  $name = $t->getText();
1201 
1202  switch ( $validate ) {
1203  case false:
1204  break;
1205  case 'valid':
1206  if ( !self::isValidUserName( $name ) ) {
1207  $name = false;
1208  }
1209  break;
1210  case 'usable':
1211  if ( !self::isUsableName( $name ) ) {
1212  $name = false;
1213  }
1214  break;
1215  case 'creatable':
1216  if ( !self::isCreatableName( $name ) ) {
1217  $name = false;
1218  }
1219  break;
1220  default:
1221  throw new InvalidArgumentException(
1222  'Invalid parameter value for $validate in ' . __METHOD__ );
1223  }
1224  return $name;
1225  }
1226 
1236  public function loadDefaults( $name = false, $actorId = null ) {
1237  $this->mId = 0;
1238  $this->mName = $name;
1239  $this->mActorId = $actorId;
1240  $this->mRealName = '';
1241  $this->mEmail = '';
1242  $this->mOptionOverrides = null;
1243  $this->mOptionsLoaded = false;
1244 
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 );
1249  } else {
1250  $this->mTouched = '1'; # Allow any pages to be cached
1251  }
1252 
1253  $this->mToken = null; // Don't run cryptographic functions till we need a token
1254  $this->mEmailAuthenticated = null;
1255  $this->mEmailToken = '';
1256  $this->mEmailTokenExpires = null;
1257  $this->mRegistration = wfTimestamp( TS_MW );
1258  $this->mGroupMemberships = [];
1259 
1260  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1261  }
1262 
1275  public function isItemLoaded( $item, $all = 'all' ) {
1276  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1277  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1278  }
1279 
1285  protected function setItemLoaded( $item ) {
1286  if ( is_array( $this->mLoadedItems ) ) {
1287  $this->mLoadedItems[$item] = true;
1288  }
1289  }
1290 
1296  private function loadFromSession() {
1297  // MediaWiki\Session\Session already did the necessary authentication of the user
1298  // returned here, so just use it if applicable.
1299  $session = $this->getRequest()->getSession();
1300  $user = $session->getUser();
1301  if ( $user->isLoggedIn() ) {
1302  $this->loadFromUserObject( $user );
1303 
1304  // If this user is autoblocked, set a cookie to track the block. This has to be done on
1305  // every session load, because an autoblocked editor might not edit again from the same
1306  // IP address after being blocked.
1307  MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1308 
1309  // Other code expects these to be set in the session, so set them.
1310  $session->set( 'wsUserID', $this->getId() );
1311  $session->set( 'wsUserName', $this->getName() );
1312  $session->set( 'wsToken', $this->getToken() );
1313 
1314  return true;
1315  }
1316 
1317  return false;
1318  }
1319 
1325  public function trackBlockWithCookie() {
1326  MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1327  }
1328 
1336  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1337  // Paranoia
1338  $this->mId = intval( $this->mId );
1339 
1340  if ( !$this->mId ) {
1341  // Anonymous users are not in the database
1342  $this->loadDefaults();
1343  return false;
1344  }
1345 
1346  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1347  $db = wfGetDB( $index );
1348 
1349  $userQuery = self::getQueryInfo();
1350  $s = $db->selectRow(
1351  $userQuery['tables'],
1352  $userQuery['fields'],
1353  [ 'user_id' => $this->mId ],
1354  __METHOD__,
1355  $options,
1356  $userQuery['joins']
1357  );
1358 
1359  $this->queryFlagsUsed = $flags;
1360  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1361 
1362  if ( $s !== false ) {
1363  // Initialise user table data
1364  $this->loadFromRow( $s );
1365  $this->mGroupMemberships = null; // deferred
1366  $this->getEditCount(); // revalidation for nulls
1367  return true;
1368  }
1369 
1370  // Invalid user_id
1371  $this->mId = 0;
1372  $this->loadDefaults();
1373 
1374  return false;
1375  }
1376 
1389  protected function loadFromRow( $row, $data = null ) {
1390  if ( !is_object( $row ) ) {
1391  throw new InvalidArgumentException( '$row must be an object' );
1392  }
1393 
1394  $all = true;
1395 
1396  $this->mGroupMemberships = null; // deferred
1397 
1398  if ( isset( $row->actor_id ) ) {
1399  $this->mActorId = (int)$row->actor_id;
1400  if ( $this->mActorId !== 0 ) {
1401  $this->mFrom = 'actor';
1402  }
1403  $this->setItemLoaded( 'actor' );
1404  } else {
1405  $all = false;
1406  }
1407 
1408  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1409  $this->mName = $row->user_name;
1410  $this->mFrom = 'name';
1411  $this->setItemLoaded( 'name' );
1412  } else {
1413  $all = false;
1414  }
1415 
1416  if ( isset( $row->user_real_name ) ) {
1417  $this->mRealName = $row->user_real_name;
1418  $this->setItemLoaded( 'realname' );
1419  } else {
1420  $all = false;
1421  }
1422 
1423  if ( isset( $row->user_id ) ) {
1424  $this->mId = intval( $row->user_id );
1425  if ( $this->mId !== 0 ) {
1426  $this->mFrom = 'id';
1427  }
1428  $this->setItemLoaded( 'id' );
1429  } else {
1430  $all = false;
1431  }
1432 
1433  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1434  self::$idCacheByName[$row->user_name] = $row->user_id;
1435  }
1436 
1437  if ( isset( $row->user_editcount ) ) {
1438  $this->mEditCount = $row->user_editcount;
1439  } else {
1440  $all = false;
1441  }
1442 
1443  if ( isset( $row->user_touched ) ) {
1444  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1445  } else {
1446  $all = false;
1447  }
1448 
1449  if ( isset( $row->user_token ) ) {
1450  // The definition for the column is binary(32), so trim the NULs
1451  // that appends. The previous definition was char(32), so trim
1452  // spaces too.
1453  $this->mToken = rtrim( $row->user_token, " \0" );
1454  if ( $this->mToken === '' ) {
1455  $this->mToken = null;
1456  }
1457  } else {
1458  $all = false;
1459  }
1460 
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 );
1466  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1467  } else {
1468  $all = false;
1469  }
1470 
1471  if ( $all ) {
1472  $this->mLoadedItems = true;
1473  }
1474 
1475  if ( is_array( $data ) ) {
1476  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1477  if ( $data['user_groups'] === [] ) {
1478  $this->mGroupMemberships = [];
1479  } else {
1480  $firstGroup = reset( $data['user_groups'] );
1481  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1482  $this->mGroupMemberships = [];
1483  foreach ( $data['user_groups'] as $row ) {
1484  $ugm = UserGroupMembership::newFromRow( (object)$row );
1485  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1486  }
1487  }
1488  }
1489  }
1490  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1491  $this->loadOptions( $data['user_properties'] );
1492  }
1493  }
1494  }
1495 
1501  protected function loadFromUserObject( $user ) {
1502  $user->load();
1503  foreach ( self::$mCacheVars as $var ) {
1504  $this->$var = $user->$var;
1505  }
1506  }
1507 
1511  private function loadGroups() {
1512  if ( is_null( $this->mGroupMemberships ) ) {
1513  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1514  ? wfGetDB( DB_MASTER )
1515  : wfGetDB( DB_REPLICA );
1516  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1517  $this->mId, $db );
1518  }
1519  }
1520 
1535  public function addAutopromoteOnceGroups( $event ) {
1537 
1538  if ( wfReadOnly() || !$this->getId() ) {
1539  return [];
1540  }
1541 
1542  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1543  if ( $toPromote === [] ) {
1544  return [];
1545  }
1546 
1547  if ( !$this->checkAndSetTouched() ) {
1548  return []; // raced out (bug T48834)
1549  }
1550 
1551  $oldGroups = $this->getGroups(); // previous groups
1552  $oldUGMs = $this->getGroupMemberships();
1553  foreach ( $toPromote as $group ) {
1554  $this->addGroup( $group );
1555  }
1556  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1557  $newUGMs = $this->getGroupMemberships();
1558 
1559  // update groups in external authentication database
1560  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1561 
1562  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1563  $logEntry->setPerformer( $this );
1564  $logEntry->setTarget( $this->getUserPage() );
1565  $logEntry->setParameters( [
1566  '4::oldgroups' => $oldGroups,
1567  '5::newgroups' => $newGroups,
1568  ] );
1569  $logid = $logEntry->insert();
1570  if ( $wgAutopromoteOnceLogInRC ) {
1571  $logEntry->publish( $logid );
1572  }
1573 
1574  return $toPromote;
1575  }
1576 
1586  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1587  if ( $this->mTouched ) {
1588  // CAS check: only update if the row wasn't changed sicne it was loaded.
1589  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1590  }
1591 
1592  return $conditions;
1593  }
1594 
1604  protected function checkAndSetTouched() {
1605  $this->load();
1606 
1607  if ( !$this->mId ) {
1608  return false; // anon
1609  }
1610 
1611  // Get a new user_touched that is higher than the old one
1612  $newTouched = $this->newTouchedTimestamp();
1613 
1614  $dbw = wfGetDB( DB_MASTER );
1615  $dbw->update( 'user',
1616  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1617  $this->makeUpdateConditions( $dbw, [
1618  'user_id' => $this->mId,
1619  ] ),
1620  __METHOD__
1621  );
1622  $success = ( $dbw->affectedRows() > 0 );
1623 
1624  if ( $success ) {
1625  $this->mTouched = $newTouched;
1626  $this->clearSharedCache( 'changed' );
1627  } else {
1628  // Clears on failure too since that is desired if the cache is stale
1629  $this->clearSharedCache( 'refresh' );
1630  }
1631 
1632  return $success;
1633  }
1634 
1642  public function clearInstanceCache( $reloadFrom = false ) {
1643  global $wgFullyInitialised;
1644 
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;
1655 
1656  // Replacement of former `$this->mRights = null` line
1657  if ( $wgFullyInitialised && $this->mFrom ) {
1658  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
1659  $this
1660  );
1661  }
1662 
1663  if ( $reloadFrom ) {
1664  $this->mLoadedItems = [];
1665  $this->mFrom = $reloadFrom;
1666  }
1667  }
1668 
1670  private static $defOpt = null;
1672  private static $defOptLang = null;
1673 
1680  public static function resetGetDefaultOptionsForTestsOnly() {
1681  Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
1682  self::$defOpt = null;
1683  self::$defOptLang = null;
1684  }
1685 
1692  public static function getDefaultOptions() {
1694 
1695  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1696  if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
1697  // The content language does not change (and should not change) mid-request, but the
1698  // unit tests change it anyway, and expect this method to return values relevant to the
1699  // current content language.
1700  return self::$defOpt;
1701  }
1702 
1703  self::$defOpt = $wgDefaultUserOptions;
1704  // Default language setting
1705  self::$defOptLang = $contLang->getCode();
1706  self::$defOpt['language'] = self::$defOptLang;
1707  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1708  if ( $langCode === $contLang->getCode() ) {
1709  self::$defOpt['variant'] = $langCode;
1710  } else {
1711  self::$defOpt["variant-$langCode"] = $langCode;
1712  }
1713  }
1714 
1715  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1716  // since extensions may change the set of searchable namespaces depending
1717  // on user groups/permissions.
1718  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1719  self::$defOpt['searchNs' . $nsnum] = (bool)$val;
1720  }
1721  self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1722 
1723  Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
1724 
1725  return self::$defOpt;
1726  }
1727 
1734  public static function getDefaultOption( $opt ) {
1735  $defOpts = self::getDefaultOptions();
1736  return $defOpts[$opt] ?? null;
1737  }
1738 
1748  private function getBlockedStatus( $fromReplica = true ) {
1749  if ( $this->mBlockedby != -1 ) {
1750  return;
1751  }
1752 
1753  wfDebug( __METHOD__ . ": checking...\n" );
1754 
1755  // Initialize data...
1756  // Otherwise something ends up stomping on $this->mBlockedby when
1757  // things get lazy-loaded later, causing false positive block hits
1758  // due to -1 !== 0. Probably session-related... Nothing should be
1759  // overwriting mBlockedby, surely?
1760  $this->load();
1761 
1762  // TODO: Block checking shouldn't really be done from the User object. Block
1763  // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1764  // which need more knowledge of the request context than the User should have.
1765  // Since we do currently check blocks from the User, we have to do the following
1766  // here:
1767  // - Check if this is the user associated with the main request
1768  // - If so, pass the relevant request information to the block manager
1769  $request = null;
1770 
1771  // The session user is set up towards the end of Setup.php. Until then,
1772  // assume it's a logged-out user.
1773  $sessionUser = RequestContext::getMain()->getUser();
1774  $globalUserName = $sessionUser->isSafeToLoad()
1775  ? $sessionUser->getName()
1776  : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1777 
1778  if ( $this->getName() === $globalUserName ) {
1779  // This is the global user, so we need to pass the request
1780  $request = $this->getRequest();
1781  }
1782 
1783  // @phan-suppress-next-line PhanAccessMethodInternal It's the only allowed use
1784  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1785  $this,
1786  $request,
1787  $fromReplica
1788  );
1789 
1790  if ( $block ) {
1791  $this->mBlock = $block;
1792  $this->mBlockedby = $block->getByName();
1793  $this->mBlockreason = $block->getReason();
1794  $this->mHideName = $block->getHideName();
1795  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1796  } else {
1797  $this->mBlock = null;
1798  $this->mBlockedby = '';
1799  $this->mBlockreason = '';
1800  $this->mHideName = 0;
1801  $this->mAllowUsertalk = false;
1802  }
1803 
1804  // Avoid PHP 7.1 warning of passing $this by reference
1805  $thisUser = $this;
1806  // Extensions
1807  Hooks::run( 'GetBlockedStatus', [ &$thisUser ], '1.34' );
1808  }
1809 
1818  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1819  return MediaWikiServices::getInstance()->getBlockManager()
1820  ->isDnsBlacklisted( $ip, $checkWhitelist );
1821  }
1822 
1831  public function inDnsBlacklist( $ip, $bases ) {
1832  wfDeprecated( __METHOD__, '1.34' );
1833 
1834  $found = false;
1835  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1836  if ( IP::isIPv4( $ip ) ) {
1837  // Reverse IP, T23255
1838  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1839 
1840  foreach ( (array)$bases as $base ) {
1841  // Make hostname
1842  // If we have an access key, use that too (ProjectHoneypot, etc.)
1843  $basename = $base;
1844  if ( is_array( $base ) ) {
1845  if ( count( $base ) >= 2 ) {
1846  // Access key is 1, base URL is 0
1847  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1848  } else {
1849  $host = "$ipReversed.{$base[0]}";
1850  }
1851  $basename = $base[0];
1852  } else {
1853  $host = "$ipReversed.$base";
1854  }
1855 
1856  // Send query
1857  $ipList = gethostbynamel( $host );
1858 
1859  if ( $ipList ) {
1860  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1861  $found = true;
1862  break;
1863  }
1864 
1865  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1866  }
1867  }
1868 
1869  return $found;
1870  }
1871 
1879  public static function isLocallyBlockedProxy( $ip ) {
1880  wfDeprecated( __METHOD__, '1.34' );
1881 
1882  global $wgProxyList;
1883 
1884  if ( !$wgProxyList ) {
1885  return false;
1886  }
1887 
1888  if ( !is_array( $wgProxyList ) ) {
1889  // Load values from the specified file
1890  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1891  }
1892 
1893  $resultProxyList = [];
1894  $deprecatedIPEntries = [];
1895 
1896  // backward compatibility: move all ip addresses in keys to values
1897  foreach ( $wgProxyList as $key => $value ) {
1898  $keyIsIP = IP::isIPAddress( $key );
1899  $valueIsIP = IP::isIPAddress( $value );
1900  if ( $keyIsIP && !$valueIsIP ) {
1901  $deprecatedIPEntries[] = $key;
1902  $resultProxyList[] = $key;
1903  } elseif ( $keyIsIP && $valueIsIP ) {
1904  $deprecatedIPEntries[] = $key;
1905  $resultProxyList[] = $key;
1906  $resultProxyList[] = $value;
1907  } else {
1908  $resultProxyList[] = $value;
1909  }
1910  }
1911 
1912  if ( $deprecatedIPEntries ) {
1913  wfDeprecated(
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' );
1916  }
1917 
1918  $proxyListIPSet = new IPSet( $resultProxyList );
1919  return $proxyListIPSet->match( $ip );
1920  }
1921 
1927  public function isPingLimitable() {
1928  global $wgRateLimitsExcludedIPs;
1929  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1930  // No other good way currently to disable rate limits
1931  // for specific IPs. :P
1932  // But this is a crappy hack and should die.
1933  return false;
1934  }
1935  return !$this->isAllowed( 'noratelimit' );
1936  }
1937 
1954  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1955  // Avoid PHP 7.1 warning of passing $this by reference
1956  $user = $this;
1957 
1958  $logger = \MediaWiki\Logger\LoggerFactory::getInstance( 'ratelimit' );
1959 
1960  // Call the 'PingLimiter' hook
1961  $result = false;
1962  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1963  return $result;
1964  }
1965 
1966  global $wgRateLimits;
1967  if ( !isset( $wgRateLimits[$action] ) ) {
1968  return false;
1969  }
1970 
1971  $limits = array_merge(
1972  [ '&can-bypass' => true ],
1973  $wgRateLimits[$action]
1974  );
1975 
1976  // Some groups shouldn't trigger the ping limiter, ever
1977  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1978  return false;
1979  }
1980 
1981  $logger->debug( __METHOD__ . ": limiting $action rate for {$this->getName()}" );
1982 
1983  $keys = [];
1984  $id = $this->getId();
1985  $isNewbie = $this->isNewbie();
1987 
1988  if ( $id == 0 ) {
1989  // "shared anon" limit, for all anons combined
1990  if ( isset( $limits['anon'] ) ) {
1991  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1992  }
1993  } else {
1994  // "global per name" limit, across sites
1995  if ( isset( $limits['user-global'] ) ) {
1997 
1998  $centralId = $lookup
1999  ? $lookup->centralIdFromLocalUser( $this, CentralIdLookup::AUDIENCE_RAW )
2000  : 0;
2001 
2002  if ( $centralId ) {
2003  // We don't have proper realms, use provider ID.
2004  $realm = $lookup->getProviderId();
2005 
2006  $globalKey = $cache->makeGlobalKey( 'limiter', $action, 'user-global',
2007  $realm, $centralId );
2008  } else {
2009  // Fall back to a local key for a local ID
2010  $globalKey = $cache->makeKey( 'limiter', $action, 'user-global',
2011  'local', $id );
2012  }
2013  $keys[$globalKey] = $limits['user-global'];
2014  }
2015  }
2016 
2017  if ( $isNewbie ) {
2018  // "per ip" limit for anons and newbie users
2019  if ( isset( $limits['ip'] ) ) {
2020  $ip = $this->getRequest()->getIP();
2021  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
2022  }
2023  // "per subnet" limit for anons and newbie users
2024  if ( isset( $limits['subnet'] ) ) {
2025  $ip = $this->getRequest()->getIP();
2026  $subnet = IP::getSubnet( $ip );
2027  if ( $subnet !== false ) {
2028  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
2029  }
2030  }
2031  }
2032 
2033  // determine the "per user account" limit
2034  $userLimit = false;
2035  if ( $id !== 0 && isset( $limits['user'] ) ) {
2036  // default limit for logged-in users
2037  $userLimit = $limits['user'];
2038  }
2039  // limits for newbie logged-in users (overrides all the normal user limits)
2040  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2041  $userLimit = $limits['newbie'];
2042  } else {
2043  // Check for group-specific limits
2044  // If more than one group applies, use the highest allowance (if higher than the default)
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]
2049  ) {
2050  $userLimit = $limits[$group];
2051  }
2052  }
2053  }
2054  }
2055 
2056  // Set the user limit key
2057  if ( $userLimit !== false ) {
2058  // phan is confused because &can-bypass's value is a bool, so it assumes
2059  // that $userLimit is also a bool here.
2060  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
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;
2064  }
2065 
2066  // ip-based limits for all ping-limitable users
2067  if ( isset( $limits['ip-all'] ) ) {
2068  $ip = $this->getRequest()->getIP();
2069  // ignore if user limit is more permissive
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'];
2073  }
2074  }
2075 
2076  // subnet-based limits for all ping-limitable users
2077  if ( isset( $limits['subnet-all'] ) ) {
2078  $ip = $this->getRequest()->getIP();
2079  $subnet = IP::getSubnet( $ip );
2080  if ( $subnet !== false ) {
2081  // ignore if user limit is more permissive
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'];
2087  }
2088  }
2089  }
2090 
2091  // XXX: We may want to use $cache->getCurrentTime() here, but that would make it
2092  // harder to test for T246991. Also $cache->getCurrentTime() is documented
2093  // as being for testing only, so it apparently should not be called here.
2094  $now = MWTimestamp::time();
2095  $clockFudge = 3; // avoid log spam when a clock is slightly off
2096 
2097  $triggered = false;
2098  foreach ( $keys as $key => $limit ) {
2099  // Do the update in a merge callback, for atomicity.
2100  // To use merge(), we need to explicitly track the desired expiry timestamp.
2101  // This tracking was introduced to investigate T246991. Once it is no longer needed,
2102  // we could go back to incrWithInit(), though that has more potential for race
2103  // conditions between the get() and incrWithInit() calls.
2104  $cache->merge(
2105  $key,
2106  function ( $cache, $key, $data, &$expiry )
2107  use ( $action, $logger, &$triggered, $now, $clockFudge, $limit, $incrBy )
2108  {
2109  // phan is confused because &can-bypass's value is a bool, so it assumes
2110  // that $userLimit is also a bool here.
2111  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2112  list( $max, $period ) = $limit;
2113 
2114  $expiry = $now + (int)$period;
2115  $count = 0;
2116 
2117  // Already pinged?
2118  if ( $data ) {
2119  // NOTE: in order to investigate T246991, we write the expiry time
2120  // into the payload, along with the count.
2121  $fields = explode( '|', $data );
2122  $storedCount = (int)( $fields[0] ?? 0 );
2123  $storedExpiry = (int)( $fields[1] ?? PHP_INT_MAX );
2124 
2125  // Found a stale entry. This should not happen!
2126  if ( $storedExpiry < ( $now + $clockFudge ) ) {
2127  $logger->info(
2128  'User::pingLimiter: '
2129  . 'Stale rate limit entry, cache key failed to expire (T246991)',
2130  [
2131  'action' => $action,
2132  'user' => $this->getName(),
2133  'limit' => $max,
2134  'period' => $period,
2135  'count' => $storedCount,
2136  'key' => $key,
2137  'expiry' => MWTimestamp::convert( TS_DB, $storedExpiry ),
2138  ]
2139  );
2140  } else {
2141  // NOTE: We'll keep the original expiry when bumping counters,
2142  // resulting in a kind of fixed-window throttle.
2143  $expiry = min( $storedExpiry, $now + (int)$period );
2144  $count = $storedCount;
2145  }
2146  }
2147 
2148  // Limit exceeded!
2149  if ( $count >= $max ) {
2150  if ( !$triggered ) {
2151  $logger->info(
2152  'User::pingLimiter: User tripped rate limit',
2153  [
2154  'action' => $action,
2155  'user' => $this->getName(),
2156  'ip' => $this->getRequest()->getIP(),
2157  'limit' => $max,
2158  'period' => $period,
2159  'count' => $count,
2160  'key' => $key
2161  ]
2162  );
2163  }
2164 
2165  $triggered = true;
2166  }
2167 
2168  $count += $incrBy;
2169  $data = "$count|$expiry";
2170  return $data;
2171  }
2172  );
2173  }
2174 
2175  return $triggered;
2176  }
2177 
2189  public function isBlocked( $fromReplica = true ) {
2190  return $this->getBlock( $fromReplica ) instanceof AbstractBlock &&
2191  $this->getBlock()->appliesToRight( 'edit' );
2192  }
2193 
2200  public function getBlock( $fromReplica = true ) {
2201  $this->getBlockedStatus( $fromReplica );
2202  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
2203  }
2204 
2216  public function isBlockedFrom( $title, $fromReplica = false ) {
2217  return MediaWikiServices::getInstance()->getPermissionManager()
2218  ->isBlockedFrom( $this, $title, $fromReplica );
2219  }
2220 
2225  public function blockedBy() {
2226  $this->getBlockedStatus();
2227  return $this->mBlockedby;
2228  }
2229 
2234  public function blockedFor() {
2235  $this->getBlockedStatus();
2236  return $this->mBlockreason;
2237  }
2238 
2243  public function getBlockId() {
2244  $this->getBlockedStatus();
2245  return ( $this->mBlock ? $this->mBlock->getId() : false );
2246  }
2247 
2256  public function isBlockedGlobally( $ip = '' ) {
2257  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
2258  }
2259 
2270  public function getGlobalBlock( $ip = '' ) {
2271  if ( $this->mGlobalBlock !== null ) {
2272  return $this->mGlobalBlock ?: null;
2273  }
2274  // User is already an IP?
2275  if ( IP::isIPAddress( $this->getName() ) ) {
2276  $ip = $this->getName();
2277  } elseif ( !$ip ) {
2278  $ip = $this->getRequest()->getIP();
2279  }
2280  // Avoid PHP 7.1 warning of passing $this by reference
2281  $user = $this;
2282  $blocked = false;
2283  $block = null;
2284  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2285 
2286  if ( $blocked && $block === null ) {
2287  // back-compat: UserIsBlockedGlobally didn't have $block param first
2288  $block = new SystemBlock( [
2289  'address' => $ip,
2290  'systemBlock' => 'global-block'
2291  ] );
2292  }
2293 
2294  $this->mGlobalBlock = $blocked ? $block : false;
2295  return $this->mGlobalBlock ?: null;
2296  }
2297 
2303  public function isLocked() {
2304  if ( $this->mLocked !== null ) {
2305  return $this->mLocked;
2306  }
2307  // Reset for hook
2308  $this->mLocked = false;
2309  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2310  return $this->mLocked;
2311  }
2312 
2318  public function isHidden() {
2319  if ( $this->mHideName !== null ) {
2320  return (bool)$this->mHideName;
2321  }
2322  $this->getBlockedStatus();
2323  if ( !$this->mHideName ) {
2324  // Reset for hook
2325  $this->mHideName = false;
2326  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ], '1.34' );
2327  }
2328  return (bool)$this->mHideName;
2329  }
2330 
2335  public function getId() {
2336  if ( $this->mId === null && $this->mName !== null &&
2337  ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
2338  ) {
2339  // Special case, we know the user is anonymous
2340  return 0;
2341  }
2342 
2343  if ( !$this->isItemLoaded( 'id' ) ) {
2344  // Don't load if this was initialized from an ID
2345  $this->load();
2346  }
2347 
2348  return (int)$this->mId;
2349  }
2350 
2355  public function setId( $v ) {
2356  $this->mId = $v;
2357  $this->clearInstanceCache( 'id' );
2358  }
2359 
2364  public function getName() {
2365  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2366  // Special case optimisation
2367  return $this->mName;
2368  }
2369 
2370  $this->load();
2371  if ( $this->mName === false ) {
2372  // Clean up IPs
2373  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2374  }
2375 
2376  return $this->mName;
2377  }
2378 
2392  public function setName( $str ) {
2393  $this->load();
2394  $this->mName = $str;
2395  }
2396 
2403  public function getActorId( IDatabase $dbw = null ) {
2404  if ( !$this->isItemLoaded( 'actor' ) ) {
2405  $this->load();
2406  }
2407 
2408  if ( !$this->mActorId && $dbw ) {
2409  $migration = MediaWikiServices::getInstance()->getActorMigration();
2410  $this->mActorId = $migration->getNewActorId( $dbw, $this );
2411 
2412  $this->invalidateCache();
2413  $this->setItemLoaded( 'actor' );
2414  }
2415 
2416  return (int)$this->mActorId;
2417  }
2418 
2423  public function getTitleKey() {
2424  return str_replace( ' ', '_', $this->getName() );
2425  }
2426 
2431  public function getNewtalk() {
2432  $this->load();
2433 
2434  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2435  if ( $this->mNewtalk === -1 ) {
2436  $this->mNewtalk = false; # reset talk page status
2437 
2438  // Check memcached separately for anons, who have no
2439  // entire User object stored in there.
2440  if ( !$this->mId ) {
2441  global $wgDisableAnonTalk;
2442  if ( $wgDisableAnonTalk ) {
2443  // Anon newtalk disabled by configuration.
2444  $this->mNewtalk = false;
2445  } else {
2446  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2447  }
2448  } else {
2449  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2450  }
2451  }
2452 
2453  return (bool)$this->mNewtalk;
2454  }
2455 
2469  public function getNewMessageLinks() {
2470  // Avoid PHP 7.1 warning of passing $this by reference
2471  $user = $this;
2472  $talks = [];
2473  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2474  return $talks;
2475  }
2476 
2477  if ( !$this->getNewtalk() ) {
2478  return [];
2479  }
2480  $utp = $this->getTalkPage();
2481  $dbr = wfGetDB( DB_REPLICA );
2482  // Get the "last viewed rev" timestamp from the oldest message notification
2483  $timestamp = $dbr->selectField( 'user_newtalk',
2484  'MIN(user_last_timestamp)',
2485  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2486  __METHOD__ );
2487  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2488  return [
2489  [
2491  'link' => $utp->getLocalURL(),
2492  'rev' => $rev
2493  ]
2494  ];
2495  }
2496 
2502  public function getNewMessageRevisionId() {
2503  $newMessageRevisionId = null;
2504  $newMessageLinks = $this->getNewMessageLinks();
2505 
2506  // Note: getNewMessageLinks() never returns more than a single link
2507  // and it is always for the same wiki, but we double-check here in
2508  // case that changes some time in the future.
2509  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2510  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2511  && $newMessageLinks[0]['rev']
2512  ) {
2514  $newMessageRevision = $newMessageLinks[0]['rev'];
2515  $newMessageRevisionId = $newMessageRevision->getId();
2516  }
2517 
2518  return $newMessageRevisionId;
2519  }
2520 
2529  protected function checkNewtalk( $field, $id ) {
2530  $dbr = wfGetDB( DB_REPLICA );
2531 
2532  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2533 
2534  return $ok !== false;
2535  }
2536 
2544  protected function updateNewtalk( $field, $id, $curRev = null ) {
2545  // Get timestamp of the talk page revision prior to the current one
2546  $prevRev = $curRev ? $curRev->getPrevious() : false;
2547  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2548  // Mark the user as having new messages since this revision
2549  $dbw = wfGetDB( DB_MASTER );
2550  $dbw->insert( 'user_newtalk',
2551  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2552  __METHOD__,
2553  [ 'IGNORE' ] );
2554  if ( $dbw->affectedRows() ) {
2555  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2556  return true;
2557  }
2558 
2559  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2560  return false;
2561  }
2562 
2569  protected function deleteNewtalk( $field, $id ) {
2570  $dbw = wfGetDB( DB_MASTER );
2571  $dbw->delete( 'user_newtalk',
2572  [ $field => $id ],
2573  __METHOD__ );
2574  if ( $dbw->affectedRows() ) {
2575  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2576  return true;
2577  }
2578 
2579  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2580  return false;
2581  }
2582 
2589  public function setNewtalk( $val, $curRev = null ) {
2590  if ( wfReadOnly() ) {
2591  return;
2592  }
2593 
2594  $this->load();
2595  $this->mNewtalk = $val;
2596 
2597  if ( $this->isAnon() ) {
2598  $field = 'user_ip';
2599  $id = $this->getName();
2600  } else {
2601  $field = 'user_id';
2602  $id = $this->getId();
2603  }
2604 
2605  if ( $val ) {
2606  $changed = $this->updateNewtalk( $field, $id, $curRev );
2607  } else {
2608  $changed = $this->deleteNewtalk( $field, $id );
2609  }
2610 
2611  if ( $changed ) {
2612  $this->invalidateCache();
2613  }
2614  }
2615 
2622  private function newTouchedTimestamp() {
2623  $time = time();
2624  if ( $this->mTouched ) {
2625  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2626  }
2627 
2628  return wfTimestamp( TS_MW, $time );
2629  }
2630 
2641  public function clearSharedCache( $mode = 'refresh' ) {
2642  if ( !$this->getId() ) {
2643  return;
2644  }
2645 
2646  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2647  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2648  $key = $this->getCacheKey( $cache );
2649 
2650  if ( $mode === 'refresh' ) {
2651  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2652  } else {
2653  $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2654  function () use ( $cache, $key ) {
2655  $cache->delete( $key );
2656  },
2657  __METHOD__
2658  );
2659  }
2660  }
2661 
2667  public function invalidateCache() {
2668  $this->touch();
2669  $this->clearSharedCache( 'changed' );
2670  }
2671 
2684  public function touch() {
2685  $id = $this->getId();
2686  if ( $id ) {
2687  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2688  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2689  $cache->touchCheckKey( $key );
2690  $this->mQuickTouched = null;
2691  }
2692  }
2693 
2699  public function validateCache( $timestamp ) {
2700  return ( $timestamp >= $this->getTouched() );
2701  }
2702 
2711  public function getTouched() {
2712  $this->load();
2713 
2714  if ( $this->mId ) {
2715  if ( $this->mQuickTouched === null ) {
2716  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2717  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2718 
2719  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2720  }
2721 
2722  return max( $this->mTouched, $this->mQuickTouched );
2723  }
2724 
2725  return $this->mTouched;
2726  }
2727 
2733  public function getDBTouched() {
2734  $this->load();
2735 
2736  return $this->mTouched;
2737  }
2738 
2755  public function setPassword( $str ) {
2756  wfDeprecated( __METHOD__, '1.27' );
2757  return $this->setPasswordInternal( $str );
2758  }
2759 
2768  public function setInternalPassword( $str ) {
2769  wfDeprecated( __METHOD__, '1.27' );
2770  $this->setPasswordInternal( $str );
2771  }
2772 
2781  private function setPasswordInternal( $str ) {
2782  $manager = AuthManager::singleton();
2783 
2784  // If the user doesn't exist yet, fail
2785  if ( !$manager->userExists( $this->getName() ) ) {
2786  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2787  }
2788 
2789  $status = $this->changeAuthenticationData( [
2790  'username' => $this->getName(),
2791  'password' => $str,
2792  'retype' => $str,
2793  ] );
2794  if ( !$status->isGood() ) {
2796  ->info( __METHOD__ . ': Password change rejected: '
2797  . $status->getWikiText( null, null, 'en' ) );
2798  return false;
2799  }
2800 
2801  $this->setOption( 'watchlisttoken', false );
2802  SessionManager::singleton()->invalidateSessionsForUser( $this );
2803 
2804  return true;
2805  }
2806 
2819  public function changeAuthenticationData( array $data ) {
2820  $manager = AuthManager::singleton();
2821  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2822  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2823 
2824  $status = Status::newGood( 'ignored' );
2825  foreach ( $reqs as $req ) {
2826  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2827  }
2828  if ( $status->getValue() === 'ignored' ) {
2829  $status->warning( 'authenticationdatachange-ignored' );
2830  }
2831 
2832  if ( $status->isGood() ) {
2833  foreach ( $reqs as $req ) {
2834  $manager->changeAuthenticationData( $req );
2835  }
2836  }
2837  return $status;
2838  }
2839 
2846  public function getToken( $forceCreation = true ) {
2848 
2849  $this->load();
2850  if ( !$this->mToken && $forceCreation ) {
2851  $this->setToken();
2852  }
2853 
2854  if ( !$this->mToken ) {
2855  // The user doesn't have a token, return null to indicate that.
2856  return null;
2857  }
2858 
2859  if ( $this->mToken === self::INVALID_TOKEN ) {
2860  // We return a random value here so existing token checks are very
2861  // likely to fail.
2862  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2863  }
2864 
2865  if ( $wgAuthenticationTokenVersion === null ) {
2866  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2867  return $this->mToken;
2868  }
2869 
2870  // $wgAuthenticationTokenVersion in use, so hmac it.
2871  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2872 
2873  // The raw hash can be overly long. Shorten it up.
2874  $len = max( 32, self::TOKEN_LENGTH );
2875  if ( strlen( $ret ) < $len ) {
2876  // Should never happen, even md5 is 128 bits
2877  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2878  }
2879 
2880  return substr( $ret, -$len );
2881  }
2882 
2889  public function setToken( $token = false ) {
2890  $this->load();
2891  if ( $this->mToken === self::INVALID_TOKEN ) {
2893  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2894  } elseif ( !$token ) {
2895  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2896  } else {
2897  $this->mToken = $token;
2898  }
2899  }
2900 
2905  public function getEmail() {
2906  $this->load();
2907  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2908  return $this->mEmail;
2909  }
2910 
2916  $this->load();
2917  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2919  }
2920 
2925  public function setEmail( $str ) {
2926  $this->load();
2927  if ( $str == $this->mEmail ) {
2928  return;
2929  }
2930  $this->invalidateEmail();
2931  $this->mEmail = $str;
2932  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2933  }
2934 
2942  public function setEmailWithConfirmation( $str ) {
2944 
2945  if ( !$wgEnableEmail ) {
2946  return Status::newFatal( 'emaildisabled' );
2947  }
2948 
2949  $oldaddr = $this->getEmail();
2950  if ( $str === $oldaddr ) {
2951  return Status::newGood( true );
2952  }
2953 
2954  $type = $oldaddr != '' ? 'changed' : 'set';
2955  $notificationResult = null;
2956 
2957  if ( $wgEmailAuthentication && $type === 'changed' ) {
2958  // Send the user an email notifying the user of the change in registered
2959  // email address on their previous email address
2960  $change = $str != '' ? 'changed' : 'removed';
2961  $notificationResult = $this->sendMail(
2962  wfMessage( 'notificationemail_subject_' . $change )->text(),
2963  wfMessage( 'notificationemail_body_' . $change,
2964  $this->getRequest()->getIP(),
2965  $this->getName(),
2966  $str )->text()
2967  );
2968  }
2969 
2970  $this->setEmail( $str );
2971 
2972  if ( $str !== '' && $wgEmailAuthentication ) {
2973  // Send a confirmation request to the new address if needed
2974  $result = $this->sendConfirmationMail( $type );
2975 
2976  if ( $notificationResult !== null ) {
2977  $result->merge( $notificationResult );
2978  }
2979 
2980  if ( $result->isGood() ) {
2981  // Say to the caller that a confirmation and notification mail has been sent
2982  $result->value = 'eauth';
2983  }
2984  } else {
2985  $result = Status::newGood( true );
2986  }
2987 
2988  return $result;
2989  }
2990 
2995  public function getRealName() {
2996  if ( !$this->isItemLoaded( 'realname' ) ) {
2997  $this->load();
2998  }
2999 
3000  return $this->mRealName;
3001  }
3002 
3007  public function setRealName( $str ) {
3008  $this->load();
3009  $this->mRealName = $str;
3010  }
3011 
3022  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3023  global $wgHiddenPrefs;
3024  $this->loadOptions();
3025 
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
3031  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3032  return self::getDefaultOption( $oname );
3033  }
3034 
3035  if ( array_key_exists( $oname, $this->mOptions ) ) {
3036  return $this->mOptions[$oname];
3037  }
3038 
3039  return $defaultOverride;
3040  }
3041 
3050  public function getOptions( $flags = 0 ) {
3051  global $wgHiddenPrefs;
3052  $this->loadOptions();
3053  $options = $this->mOptions;
3054 
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
3060  foreach ( $wgHiddenPrefs as $pref ) {
3061  $default = self::getDefaultOption( $pref );
3062  if ( $default !== null ) {
3063  $options[$pref] = $default;
3064  }
3065  }
3066 
3067  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3068  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3069  }
3070 
3071  return $options;
3072  }
3073 
3081  public function getBoolOption( $oname ) {
3082  return (bool)$this->getOption( $oname );
3083  }
3084 
3093  public function getIntOption( $oname, $defaultOverride = 0 ) {
3094  $val = $this->getOption( $oname );
3095  if ( $val == '' ) {
3096  $val = $defaultOverride;
3097  }
3098  return intval( $val );
3099  }
3100 
3109  public function setOption( $oname, $val ) {
3110  $this->loadOptions();
3111 
3112  // Explicitly NULL values should refer to defaults
3113  if ( is_null( $val ) ) {
3114  $val = self::getDefaultOption( $oname );
3115  }
3116 
3117  $this->mOptions[$oname] = $val;
3118  }
3119 
3130  public function getTokenFromOption( $oname ) {
3131  global $wgHiddenPrefs;
3132 
3133  $id = $this->getId();
3134  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3135  return false;
3136  }
3137 
3138  $token = $this->getOption( $oname );
3139  if ( !$token ) {
3140  // Default to a value based on the user token to avoid space
3141  // wasted on storing tokens for all users. When this option
3142  // is set manually by the user, only then is it stored.
3143  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3144  }
3145 
3146  return $token;
3147  }
3148 
3158  public function resetTokenFromOption( $oname ) {
3159  global $wgHiddenPrefs;
3160  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3161  return false;
3162  }
3163 
3164  $token = MWCryptRand::generateHex( 40 );
3165  $this->setOption( $oname, $token );
3166  return $token;
3167  }
3168 
3192  public static function listOptionKinds() {
3193  return [
3194  'registered',
3195  'registered-multiselect',
3196  'registered-checkmatrix',
3197  'userjs',
3198  'special',
3199  'unused'
3200  ];
3201  }
3202 
3215  public function getOptionKinds( IContextSource $context, $options = null ) {
3216  $this->loadOptions();
3217  if ( $options === null ) {
3218  $options = $this->mOptions;
3219  }
3220 
3221  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3222  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3223  $mapping = [];
3224 
3225  // Pull out the "special" options, so they don't get converted as
3226  // multiselect or checkmatrix.
3227  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3228  foreach ( $specialOptions as $name => $value ) {
3229  unset( $prefs[$name] );
3230  }
3231 
3232  // Multiselect and checkmatrix options are stored in the database with
3233  // one key per option, each having a boolean value. Extract those keys.
3234  $multiselectOptions = [];
3235  foreach ( $prefs as $name => $info ) {
3236  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3237  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3238  $opts = HTMLFormField::flattenOptions( $info['options'] );
3239  $prefix = $info['prefix'] ?? $name;
3240 
3241  foreach ( $opts as $value ) {
3242  $multiselectOptions["$prefix$value"] = true;
3243  }
3244 
3245  unset( $prefs[$name] );
3246  }
3247  }
3248  $checkmatrixOptions = [];
3249  foreach ( $prefs as $name => $info ) {
3250  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3251  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3252  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3253  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3254  $prefix = $info['prefix'] ?? $name;
3255 
3256  foreach ( $columns as $column ) {
3257  foreach ( $rows as $row ) {
3258  $checkmatrixOptions["$prefix$column-$row"] = true;
3259  }
3260  }
3261 
3262  unset( $prefs[$name] );
3263  }
3264  }
3265 
3266  // $value is ignored
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';
3278  } else {
3279  $mapping[$key] = 'unused';
3280  }
3281  }
3282 
3283  return $mapping;
3284  }
3285 
3300  public function resetOptions(
3301  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3302  IContextSource $context = null
3303  ) {
3304  $this->load();
3305  $defaultOptions = self::getDefaultOptions();
3306 
3307  if ( !is_array( $resetKinds ) ) {
3308  $resetKinds = [ $resetKinds ];
3309  }
3310 
3311  if ( in_array( 'all', $resetKinds ) ) {
3312  $newOptions = $defaultOptions;
3313  } else {
3314  if ( $context === null ) {
3316  }
3317 
3318  $optionKinds = $this->getOptionKinds( $context );
3319  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3320  $newOptions = [];
3321 
3322  // Use default values for the options that should be deleted, and
3323  // copy old values for the ones that shouldn't.
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];
3328  }
3329  } else {
3330  $newOptions[$key] = $value;
3331  }
3332  }
3333  }
3334 
3335  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3336 
3337  $this->mOptions = $newOptions;
3338  $this->mOptionsLoaded = true;
3339  }
3340 
3345  public function getDatePreference() {
3346  // Important migration for old data rows
3347  if ( is_null( $this->mDatePreference ) ) {
3348  global $wgLang;
3349  $value = $this->getOption( 'date' );
3350  $map = $wgLang->getDatePreferenceMigrationMap();
3351  if ( isset( $map[$value] ) ) {
3352  $value = $map[$value];
3353  }
3354  $this->mDatePreference = $value;
3355  }
3356  return $this->mDatePreference;
3357  }
3358 
3365  public function requiresHTTPS() {
3366  global $wgForceHTTPS, $wgSecureLogin;
3367  if ( $wgForceHTTPS ) {
3368  return true;
3369  }
3370  if ( !$wgSecureLogin ) {
3371  return false;
3372  }
3373  $https = $this->getBoolOption( 'prefershttps' );
3374  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3375  if ( $https ) {
3376  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3377  }
3378 
3379  return $https;
3380  }
3381 
3387  public function getStubThreshold() {
3388  global $wgMaxArticleSize; # Maximum article size, in Kb
3389  $threshold = $this->getIntOption( 'stubthreshold' );
3390  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3391  // If they have set an impossible value, disable the preference
3392  // so we can use the parser cache again.
3393  $threshold = 0;
3394  }
3395  return $threshold;
3396  }
3397 
3406  public function getRights() {
3407  return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
3408  }
3409 
3416  public function getGroups() {
3417  $this->load();
3418  $this->loadGroups();
3419  return array_keys( $this->mGroupMemberships );
3420  }
3421 
3429  public function getGroupMemberships() {
3430  $this->load();
3431  $this->loadGroups();
3432  return $this->mGroupMemberships;
3433  }
3434 
3442  public function getEffectiveGroups( $recache = false ) {
3443  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3444  $this->mEffectiveGroups = array_unique( array_merge(
3445  $this->getGroups(), // explicit groups
3446  $this->getAutomaticGroups( $recache ) // implicit groups
3447  ) );
3448  // Avoid PHP 7.1 warning of passing $this by reference
3449  $user = $this;
3450  // Hook for additional groups
3451  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3452  // Force reindexation of groups when a hook has unset one of them
3453  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3454  }
3455  return $this->mEffectiveGroups;
3456  }
3457 
3465  public function getAutomaticGroups( $recache = false ) {
3466  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3467  $this->mImplicitGroups = [ '*' ];
3468  if ( $this->getId() ) {
3469  $this->mImplicitGroups[] = 'user';
3470 
3471  $this->mImplicitGroups = array_unique( array_merge(
3472  $this->mImplicitGroups,
3474  ) );
3475  }
3476  if ( $recache ) {
3477  // Assure data consistency with rights/groups,
3478  // as getEffectiveGroups() depends on this function
3479  $this->mEffectiveGroups = null;
3480  }
3481  }
3482  return $this->mImplicitGroups;
3483  }
3484 
3494  public function getFormerGroups() {
3495  $this->load();
3496 
3497  if ( is_null( $this->mFormerGroups ) ) {
3498  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3499  ? wfGetDB( DB_MASTER )
3500  : wfGetDB( DB_REPLICA );
3501  $res = $db->select( 'user_former_groups',
3502  [ 'ufg_group' ],
3503  [ 'ufg_user' => $this->mId ],
3504  __METHOD__ );
3505  $this->mFormerGroups = [];
3506  foreach ( $res as $row ) {
3507  $this->mFormerGroups[] = $row->ufg_group;
3508  }
3509  }
3510 
3511  return $this->mFormerGroups;
3512  }
3513 
3518  public function getEditCount() {
3519  if ( !$this->getId() ) {
3520  return null;
3521  }
3522 
3523  if ( $this->mEditCount === null ) {
3524  /* Populate the count, if it has not been populated yet */
3525  $dbr = wfGetDB( DB_REPLICA );
3526  // check if the user_editcount field has been initialized
3527  $count = $dbr->selectField(
3528  'user', 'user_editcount',
3529  [ 'user_id' => $this->mId ],
3530  __METHOD__
3531  );
3532 
3533  if ( $count === null ) {
3534  // it has not been initialized. do so.
3535  $count = $this->initEditCountInternal( $dbr );
3536  }
3537  $this->mEditCount = $count;
3538  }
3539  return (int)$this->mEditCount;
3540  }
3541 
3553  public function addGroup( $group, $expiry = null ) {
3554  $this->load();
3555  $this->loadGroups();
3556 
3557  if ( $expiry ) {
3558  $expiry = wfTimestamp( TS_MW, $expiry );
3559  }
3560 
3561  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3562  return false;
3563  }
3564 
3565  // create the new UserGroupMembership and put it in the DB
3566  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3567  if ( !$ugm->insert( true ) ) {
3568  return false;
3569  }
3570 
3571  $this->mGroupMemberships[$group] = $ugm;
3572 
3573  // Refresh the groups caches, and clear the rights cache so it will be
3574  // refreshed on the next call to $this->getRights().
3575  $this->getEffectiveGroups( true );
3576  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3577  $this->invalidateCache();
3578 
3579  return true;
3580  }
3581 
3588  public function removeGroup( $group ) {
3589  $this->load();
3590 
3591  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3592  return false;
3593  }
3594 
3595  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3596  // delete the membership entry
3597  if ( !$ugm || !$ugm->delete() ) {
3598  return false;
3599  }
3600 
3601  $this->loadGroups();
3602  unset( $this->mGroupMemberships[$group] );
3603 
3604  // Refresh the groups caches, and clear the rights cache so it will be
3605  // refreshed on the next call to $this->getRights().
3606  $this->getEffectiveGroups( true );
3607  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3608  $this->invalidateCache();
3609 
3610  return true;
3611  }
3612 
3622  public function isRegistered() {
3623  return $this->getId() != 0;
3624  }
3625 
3630  public function isLoggedIn() {
3631  return $this->isRegistered();
3632  }
3633 
3638  public function isAnon() {
3639  return !$this->isRegistered();
3640  }
3641 
3646  public function isBot() {
3647  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3648  return true;
3649  }
3650 
3651  $isBot = false;
3652  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3653 
3654  return $isBot;
3655  }
3656 
3666  public function isAllowedAny( ...$permissions ) {
3667  return MediaWikiServices::getInstance()
3668  ->getPermissionManager()
3669  ->userHasAnyRight( $this, ...$permissions );
3670  }
3671 
3678  public function isAllowedAll( ...$permissions ) {
3679  return MediaWikiServices::getInstance()
3680  ->getPermissionManager()
3681  ->userHasAllRights( $this, ...$permissions );
3682  }
3683 
3694  public function isAllowed( $action = '' ) {
3695  return MediaWikiServices::getInstance()->getPermissionManager()
3696  ->userHasRight( $this, $action );
3697  }
3698 
3703  public function useRCPatrol() {
3704  global $wgUseRCPatrol;
3705  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3706  }
3707 
3712  public function useNPPatrol() {
3714  return (
3716  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3717  );
3718  }
3719 
3724  public function useFilePatrol() {
3726  return (
3728  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3729  );
3730  }
3731 
3737  public function getRequest() {
3738  if ( $this->mRequest ) {
3739  return $this->mRequest;
3740  }
3741 
3742  global $wgRequest;
3743  return $wgRequest;
3744  }
3745 
3754  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3755  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3756  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3757  }
3758  return false;
3759  }
3760 
3768  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3769  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3770  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3771  $this,
3772  [ $title->getSubjectPage(), $title->getTalkPage() ]
3773  );
3774  }
3775  $this->invalidateCache();
3776  }
3777 
3785  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3786  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3787  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3788  $store->removeWatch( $this, $title->getSubjectPage() );
3789  $store->removeWatch( $this, $title->getTalkPage() );
3790  }
3791  $this->invalidateCache();
3792  }
3793 
3802  public function clearNotification( &$title, $oldid = 0 ) {
3804 
3805  // Do nothing if the database is locked to writes
3806  if ( wfReadOnly() ) {
3807  return;
3808  }
3809 
3810  // Do nothing if not allowed to edit the watchlist
3811  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3812  return;
3813  }
3814 
3815  // If we're working on user's talk page, we should update the talk page message indicator
3816  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3817  // Avoid PHP 7.1 warning of passing $this by reference
3818  $user = $this;
3819  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3820  return;
3821  }
3822 
3823  // Try to update the DB post-send and only if needed...
3824  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3825  if ( !$this->getNewtalk() ) {
3826  return; // no notifications to clear
3827  }
3828 
3829  // Delete the last notifications (they stack up)
3830  $this->setNewtalk( false );
3831 
3832  // If there is a new, unseen, revision, use its timestamp
3833  if ( $oldid ) {
3834  $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3835  $oldRev = $rl->getRevisionById( $oldid, Title::READ_LATEST );
3836  if ( $oldRev ) {
3837  $newRev = $rl->getNextRevision( $oldRev );
3838  if ( $newRev ) {
3839  // TODO: actually no need to wrap in a revision,
3840  // setNewtalk really only needs a RevRecord
3841  $this->setNewtalk( true, new Revision( $newRev ) );
3842  }
3843  }
3844  }
3845  } );
3846  }
3847 
3848  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3849  return;
3850  }
3851 
3852  if ( $this->isAnon() ) {
3853  // Nothing else to do...
3854  return;
3855  }
3856 
3857  // Only update the timestamp if the page is being watched.
3858  // The query to find out if it is watched is cached both in memcached and per-invocation,
3859  // and when it does have to be executed, it can be on a replica DB
3860  // If this is the user's newtalk page, we always update the timestamp
3861  $force = '';
3862  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3863  $force = 'force';
3864  }
3865 
3866  MediaWikiServices::getInstance()->getWatchedItemStore()
3867  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3868  }
3869 
3876  public function clearAllNotifications() {
3878  // Do nothing if not allowed to edit the watchlist
3879  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3880  return;
3881  }
3882 
3883  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3884  $this->setNewtalk( false );
3885  return;
3886  }
3887 
3888  $id = $this->getId();
3889  if ( !$id ) {
3890  return;
3891  }
3892 
3893  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
3894  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
3895 
3896  // We also need to clear here the "you have new message" notification for the own
3897  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3898  }
3899 
3905  public function getExperienceLevel() {
3906  global $wgLearnerEdits,
3910 
3911  if ( $this->isAnon() ) {
3912  return false;
3913  }
3914 
3915  $editCount = $this->getEditCount();
3916  $registration = $this->getRegistration();
3917  $now = time();
3918  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3919  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3920 
3921  if ( $editCount < $wgLearnerEdits ||
3922  $registration > $learnerRegistration ) {
3923  return 'newcomer';
3924  }
3925 
3926  if ( $editCount > $wgExperiencedUserEdits &&
3927  $registration <= $experiencedRegistration
3928  ) {
3929  return 'experienced';
3930  }
3931 
3932  return 'learner';
3933  }
3934 
3943  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3944  $this->load();
3945  if ( $this->mId == 0 ) {
3946  return;
3947  }
3948 
3949  $session = $this->getRequest()->getSession();
3950  if ( $request && $session->getRequest() !== $request ) {
3951  $session = $session->sessionWithRequest( $request );
3952  }
3953  $delay = $session->delaySave();
3954 
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"
3960  );
3961  return;
3962  }
3963  $session->setUser( $this );
3964  }
3965 
3966  $session->setRememberUser( $rememberMe );
3967  if ( $secure !== null ) {
3968  $session->setForceHTTPS( $secure );
3969  }
3970 
3971  $session->persist();
3972 
3973  ScopedCallback::consume( $delay );
3974  }
3975 
3979  public function logout() {
3980  // Avoid PHP 7.1 warning of passing $this by reference
3981  $user = $this;
3982  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
3983  $this->doLogout();
3984  }
3985  }
3986 
3991  public function doLogout() {
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"
4001  );
4002  // But we still may as well make this user object anon
4003  $this->clearInstanceCache( 'defaults' );
4004  $error = 'wronguser';
4005  } else {
4006  $this->clearInstanceCache( 'defaults' );
4007  $delay = $session->delaySave();
4008  $session->unpersist(); // Clear cookies (T127436)
4009  $session->setLoggedOutTimestamp( time() );
4010  $session->setUser( new User );
4011  $session->set( 'wsUserID', 0 ); // Other code expects this
4012  $session->resetAllTokens();
4013  ScopedCallback::consume( $delay );
4014  $error = false;
4015  }
4016  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4017  'event' => 'logout',
4018  'successful' => $error === false,
4019  'status' => $error ?: 'success',
4020  ] );
4021  }
4022 
4027  public function saveSettings() {
4028  if ( wfReadOnly() ) {
4029  // @TODO: caller should deal with this instead!
4030  // This should really just be an exception.
4032  null,
4033  "Could not update user with ID '{$this->mId}'; DB is read-only."
4034  ) );
4035  return;
4036  }
4037 
4038  $this->load();
4039  if ( $this->mId == 0 ) {
4040  return; // anon
4041  }
4042 
4043  // Get a new user_touched that is higher than the old one.
4044  // This will be used for a CAS check as a last-resort safety
4045  // check against race conditions and replica DB lag.
4046  $newTouched = $this->newTouchedTimestamp();
4047 
4048  $dbw = wfGetDB( DB_MASTER );
4049  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
4050  $dbw->update( 'user',
4051  [ /* SET */
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, [ /* WHERE */
4061  'user_id' => $this->mId,
4062  ] ), $fname
4063  );
4064 
4065  if ( !$dbw->affectedRows() ) {
4066  // Maybe the problem was a missed cache update; clear it to be safe
4067  $this->clearSharedCache( 'refresh' );
4068  // User was changed in the meantime or loaded with stale data
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 ]
4073  );
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."
4076  );
4077  }
4078 
4079  $dbw->update(
4080  'actor',
4081  [ 'actor_name' => $this->mName ],
4082  [ 'actor_user' => $this->mId ],
4083  $fname
4084  );
4085  } );
4086 
4087  $this->mTouched = $newTouched;
4088  $this->saveOptions();
4089 
4090  Hooks::run( 'UserSaveSettings', [ $this ] );
4091  $this->clearSharedCache( 'changed' );
4092  $this->getUserPage()->purgeSquid();
4093  }
4094 
4101  public function idForName( $flags = 0 ) {
4102  $s = trim( $this->getName() );
4103  if ( $s === '' ) {
4104  return 0;
4105  }
4106 
4107  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4108  ? wfGetDB( DB_MASTER )
4109  : wfGetDB( DB_REPLICA );
4110 
4111  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4112  ? [ 'LOCK IN SHARE MODE' ]
4113  : [];
4114 
4115  $id = $db->selectField( 'user',
4116  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4117 
4118  return (int)$id;
4119  }
4120 
4136  public static function createNew( $name, $params = [] ) {
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] );
4141  }
4142  }
4143 
4144  $user = new User;
4145  $user->load();
4146  $user->setToken(); // init token
4147  if ( isset( $params['options'] ) ) {
4148  $user->mOptions = $params['options'] + (array)$user->mOptions;
4149  unset( $params['options'] );
4150  }
4151  $dbw = wfGetDB( DB_MASTER );
4152 
4153  $noPass = PasswordFactory::newInvalidPassword()->toString();
4154 
4155  $fields = [
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() ),
4166  ];
4167  foreach ( $params as $name => $value ) {
4168  $fields["user_$name"] = $value;
4169  }
4170 
4171  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
4172  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4173  if ( $dbw->affectedRows() ) {
4174  $newUser = self::newFromId( $dbw->insertId() );
4175  $newUser->mName = $fields['user_name'];
4176  $newUser->updateActorId( $dbw );
4177  // Load the user from master to avoid replica lag
4178  $newUser->load( self::READ_LATEST );
4179  } else {
4180  $newUser = null;
4181  }
4182  return $newUser;
4183  } );
4184  }
4185 
4212  public function addToDatabase() {
4213  $this->load();
4214  if ( !$this->mToken ) {
4215  $this->setToken(); // init token
4216  }
4217 
4218  if ( !is_string( $this->mName ) ) {
4219  throw new RuntimeException( "User name field is not set." );
4220  }
4221 
4222  $this->mTouched = $this->newTouchedTimestamp();
4223 
4224  $dbw = wfGetDB( DB_MASTER );
4225  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
4226  $noPass = PasswordFactory::newInvalidPassword()->toString();
4227  $dbw->insert( 'user',
4228  [
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 ),
4239  ], $fname,
4240  [ 'IGNORE' ]
4241  );
4242  if ( !$dbw->affectedRows() ) {
4243  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4244  $this->mId = $dbw->selectField(
4245  'user',
4246  'user_id',
4247  [ 'user_name' => $this->mName ],
4248  $fname,
4249  [ 'LOCK IN SHARE MODE' ]
4250  );
4251  $loaded = false;
4252  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
4253  $loaded = true;
4254  }
4255  if ( !$loaded ) {
4256  throw new MWException( $fname . ": hit a key conflict attempting " .
4257  "to insert user '{$this->mName}' row, but it was not present in select!" );
4258  }
4259  return Status::newFatal( 'userexists' );
4260  }
4261  $this->mId = $dbw->insertId();
4262  self::$idCacheByName[$this->mName] = $this->mId;
4263  $this->updateActorId( $dbw );
4264 
4265  return Status::newGood();
4266  } );
4267  if ( !$status->isGood() ) {
4268  return $status;
4269  }
4270 
4271  // Clear instance cache other than user table data and actor, which is already accurate
4272  $this->clearInstanceCache();
4273 
4274  $this->saveOptions();
4275  return Status::newGood();
4276  }
4277 
4282  private function updateActorId( IDatabase $dbw ) {
4283  $dbw->insert(
4284  'actor',
4285  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4286  __METHOD__
4287  );
4288  $this->mActorId = (int)$dbw->insertId();
4289  }
4290 
4296  public function spreadAnyEditBlock() {
4297  if ( $this->isLoggedIn() && $this->getBlock() ) {
4298  return $this->spreadBlock();
4299  }
4300 
4301  return false;
4302  }
4303 
4309  protected function spreadBlock() {
4310  wfDebug( __METHOD__ . "()\n" );
4311  $this->load();
4312  if ( $this->mId == 0 ) {
4313  return false;
4314  }
4315 
4316  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
4317  if ( !$userblock ) {
4318  return false;
4319  }
4320 
4321  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4322  }
4323 
4328  public function isBlockedFromCreateAccount() {
4329  $this->getBlockedStatus();
4330  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4331  return $this->mBlock;
4332  }
4333 
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(
4339  null, $this->getRequest()->getIP()
4340  );
4341  }
4342  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
4343  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4344  ? $this->mBlockedFromCreateAccount
4345  : false;
4346  }
4347 
4352  public function isBlockedFromEmailuser() {
4353  $this->getBlockedStatus();
4354  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4355  }
4356 
4363  public function isBlockedFromUpload() {
4364  $this->getBlockedStatus();
4365  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4366  }
4367 
4372  public function isAllowedToCreateAccount() {
4373  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4374  }
4375 
4381  public function getUserPage() {
4382  return Title::makeTitle( NS_USER, $this->getName() );
4383  }
4384 
4390  public function getTalkPage() {
4391  $title = $this->getUserPage();
4392  return $title->getTalkPage();
4393  }
4394 
4400  public function isNewbie() {
4401  return !$this->isAllowed( 'autoconfirmed' );
4402  }
4403 
4410  public function checkPassword( $password ) {
4411  wfDeprecated( __METHOD__, '1.27' );
4412 
4413  $manager = AuthManager::singleton();
4414  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4415  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4416  [
4417  'username' => $this->getName(),
4418  'password' => $password,
4419  ]
4420  );
4421  $res = $manager->beginAuthentication( $reqs, 'null:' );
4422  switch ( $res->status ) {
4423  case AuthenticationResponse::PASS:
4424  return true;
4425  case AuthenticationResponse::FAIL:
4426  // Hope it's not a PreAuthenticationProvider that failed...
4427  LoggerFactory::getInstance( 'authentication' )
4428  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4429  return false;
4430  default:
4431  throw new BadMethodCallException(
4432  'AuthManager returned a response unsupported by ' . __METHOD__
4433  );
4434  }
4435  }
4436 
4445  public function checkTemporaryPassword( $plaintext ) {
4446  wfDeprecated( __METHOD__, '1.27' );
4447  // Can't check the temporary password individually.
4448  return $this->checkPassword( $plaintext );
4449  }
4450 
4462  public function getEditTokenObject( $salt = '', $request = null ) {
4463  if ( $this->isAnon() ) {
4464  return new LoggedOutEditToken();
4465  }
4466 
4467  if ( !$request ) {
4468  $request = $this->getRequest();
4469  }
4470  return $request->getSession()->getToken( $salt );
4471  }
4472 
4486  public function getEditToken( $salt = '', $request = null ) {
4487  return $this->getEditTokenObject( $salt, $request )->toString();
4488  }
4489 
4502  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4503  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4504  }
4505 
4516  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4517  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4518  return $this->matchEditToken( $val, $salt, $request, $maxage );
4519  }
4520 
4528  public function sendConfirmationMail( $type = 'created' ) {
4529  global $wgLang;
4530  $expiration = null; // gets passed-by-ref and defined in next line.
4531  $token = $this->confirmationToken( $expiration );
4532  $url = $this->confirmationTokenUrl( $token );
4533  $invalidateURL = $this->invalidationTokenUrl( $token );
4534  $this->saveSettings();
4535 
4536  if ( $type == 'created' || $type === false ) {
4537  $message = 'confirmemail_body';
4538  $type = 'created';
4539  } elseif ( $type === true ) {
4540  $message = 'confirmemail_body_changed';
4541  $type = 'changed';
4542  } else {
4543  // Messages: confirmemail_body_changed, confirmemail_body_set
4544  $message = 'confirmemail_body_' . $type;
4545  }
4546 
4547  $mail = [
4548  'subject' => wfMessage( 'confirmemail_subject' )->text(),
4549  'body' => wfMessage( $message,
4550  $this->getRequest()->getIP(),
4551  $this->getName(),
4552  $url,
4553  $wgLang->userTimeAndDate( $expiration, $this ),
4554  $invalidateURL,
4555  $wgLang->userDate( $expiration, $this ),
4556  $wgLang->userTime( $expiration, $this ) )->text(),
4557  'from' => null,
4558  'replyTo' => null,
4559  ];
4560  $info = [
4561  'type' => $type,
4562  'ip' => $this->getRequest()->getIP(),
4563  'confirmURL' => $url,
4564  'invalidateURL' => $invalidateURL,
4565  'expiration' => $expiration
4566  ];
4567 
4568  Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4569  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4570  }
4571 
4583  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4584  global $wgPasswordSender;
4585 
4586  if ( $from instanceof User ) {
4587  $sender = MailAddress::newFromUser( $from );
4588  } else {
4589  $sender = new MailAddress( $wgPasswordSender,
4590  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4591  }
4592  $to = MailAddress::newFromUser( $this );
4593 
4594  return UserMailer::send( $to, $sender, $subject, $body, [
4595  'replyTo' => $replyto,
4596  ] );
4597  }
4598 
4609  protected function confirmationToken( &$expiration ) {
4611  $now = time();
4612  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4613  $expiration = wfTimestamp( TS_MW, $expires );
4614  $this->load();
4615  $token = MWCryptRand::generateHex( 32 );
4616  $hash = md5( $token );
4617  $this->mEmailToken = $hash;
4618  $this->mEmailTokenExpires = $expiration;
4619  return $token;
4620  }
4621 
4627  protected function confirmationTokenUrl( $token ) {
4628  return $this->getTokenUrl( 'ConfirmEmail', $token );
4629  }
4630 
4636  protected function invalidationTokenUrl( $token ) {
4637  return $this->getTokenUrl( 'InvalidateEmail', $token );
4638  }
4639 
4654  protected function getTokenUrl( $page, $token ) {
4655  // Hack to bypass localization of 'Special:'
4656  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4657  return $title->getCanonicalURL();
4658  }
4659 
4667  public function confirmEmail() {
4668  // Check if it's already confirmed, so we don't touch the database
4669  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4670  if ( !$this->isEmailConfirmed() ) {
4672  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4673  }
4674  return true;
4675  }
4676 
4684  public function invalidateEmail() {
4685  $this->load();
4686  $this->mEmailToken = null;
4687  $this->mEmailTokenExpires = null;
4688  $this->setEmailAuthenticationTimestamp( null );
4689  $this->mEmail = '';
4690  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4691  return true;
4692  }
4693 
4698  public function setEmailAuthenticationTimestamp( $timestamp ) {
4699  $this->load();
4700  $this->mEmailAuthenticated = $timestamp;
4701  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4702  }
4703 
4709  public function canSendEmail() {
4711  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4712  return false;
4713  }
4714  $canSend = $this->isEmailConfirmed();
4715  // Avoid PHP 7.1 warning of passing $this by reference
4716  $user = $this;
4717  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4718  return $canSend;
4719  }
4720 
4726  public function canReceiveEmail() {
4727  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4728  }
4729 
4740  public function isEmailConfirmed() {
4741  global $wgEmailAuthentication;
4742  $this->load();
4743  // Avoid PHP 7.1 warning of passing $this by reference
4744  $user = $this;
4745  $confirmed = true;
4746  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4747  if ( $this->isAnon() ) {
4748  return false;
4749  }
4750  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4751  return false;
4752  }
4754  return false;
4755  }
4756  return true;
4757  }
4758 
4759  return $confirmed;
4760  }
4761 
4766  public function isEmailConfirmationPending() {
4767  global $wgEmailAuthentication;
4768  return $wgEmailAuthentication &&
4769  !$this->isEmailConfirmed() &&
4770  $this->mEmailToken &&
4771  $this->mEmailTokenExpires > wfTimestamp();
4772  }
4773 
4781  public function getRegistration() {
4782  if ( $this->isAnon() ) {
4783  return false;
4784  }
4785  $this->load();
4786  return $this->mRegistration;
4787  }
4788 
4795  public function getFirstEditTimestamp() {
4796  return $this->getEditTimestamp( true );
4797  }
4798 
4806  public function getLatestEditTimestamp() {
4807  return $this->getEditTimestamp( false );
4808  }
4809 
4817  private function getEditTimestamp( $first ) {
4818  if ( $this->getId() == 0 ) {
4819  return false; // anons
4820  }
4821  $dbr = wfGetDB( DB_REPLICA );
4822  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
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'],
4828  $tsField,
4829  [ $actorWhere['conds'] ],
4830  __METHOD__,
4831  [ 'ORDER BY' => "$tsField $sortOrder" ],
4832  $actorWhere['joins']
4833  );
4834  if ( !$time ) {
4835  return false; // no edits
4836  }
4837  return wfTimestamp( TS_MW, $time );
4838  }
4839 
4849  public static function getGroupPermissions( $groups ) {
4850  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4851  }
4852 
4862  public static function getGroupsWithPermission( $role ) {
4863  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4864  }
4865 
4881  public static function groupHasPermission( $group, $role ) {
4882  return MediaWikiServices::getInstance()->getPermissionManager()
4883  ->groupHasPermission( $group, $role );
4884  }
4885 
4904  public static function isEveryoneAllowed( $right ) {
4905  return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4906  }
4907 
4914  public static function getAllGroups() {
4916  return array_values( array_diff(
4917  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4918  self::getImplicitGroups()
4919  ) );
4920  }
4921 
4929  public static function getAllRights() {
4930  return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4931  }
4932 
4939  public static function getImplicitGroups() {
4940  global $wgImplicitGroups;
4941  return $wgImplicitGroups;
4942  }
4943 
4953  public static function changeableByGroup( $group ) {
4955 
4956  $groups = [
4957  'add' => [],
4958  'remove' => [],
4959  'add-self' => [],
4960  'remove-self' => []
4961  ];
4962 
4963  if ( empty( $wgAddGroups[$group] ) ) {
4964  // Don't add anything to $groups
4965  } elseif ( $wgAddGroups[$group] === true ) {
4966  // You get everything
4967  $groups['add'] = self::getAllGroups();
4968  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4969  $groups['add'] = $wgAddGroups[$group];
4970  }
4971 
4972  // Same thing for remove
4973  if ( empty( $wgRemoveGroups[$group] ) ) {
4974  // Do nothing
4975  } elseif ( $wgRemoveGroups[$group] === true ) {
4976  $groups['remove'] = self::getAllGroups();
4977  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4978  $groups['remove'] = $wgRemoveGroups[$group];
4979  }
4980 
4981  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4982  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4983  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4984  if ( is_int( $key ) ) {
4985  $wgGroupsAddToSelf['user'][] = $value;
4986  }
4987  }
4988  }
4989 
4990  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4991  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4992  if ( is_int( $key ) ) {
4993  $wgGroupsRemoveFromSelf['user'][] = $value;
4994  }
4995  }
4996  }
4997 
4998  // Now figure out what groups the user can add to him/herself
4999  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5000  // Do nothing
5001  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5002  // No idea WHY this would be used, but it's there
5003  $groups['add-self'] = self::getAllGroups();
5004  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5005  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5006  }
5007 
5008  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5009  // Do nothing
5010  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5011  $groups['remove-self'] = self::getAllGroups();
5012  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5013  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5014  }
5015 
5016  return $groups;
5017  }
5018 
5026  public function changeableGroups() {
5027  if ( $this->isAllowed( 'userrights' ) ) {
5028  // This group gives the right to modify everything (reverse-
5029  // compatibility with old "userrights lets you change
5030  // everything")
5031  // Using array_merge to make the groups reindexed
5032  $all = array_merge( self::getAllGroups() );
5033  return [
5034  'add' => $all,
5035  'remove' => $all,
5036  'add-self' => [],
5037  'remove-self' => []
5038  ];
5039  }
5040 
5041  // Okay, it's not so simple, we will have to go through the arrays
5042  $groups = [
5043  'add' => [],
5044  'remove' => [],
5045  'add-self' => [],
5046  'remove-self' => []
5047  ];
5048  $addergroups = $this->getEffectiveGroups();
5049 
5050  foreach ( $addergroups as $addergroup ) {
5051  $groups = array_merge_recursive(
5052  $groups, $this->changeableByGroup( $addergroup )
5053  );
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'] );
5058  }
5059  return $groups;
5060  }
5061 
5065  public function incEditCount() {
5066  if ( $this->isAnon() ) {
5067  return; // sanity
5068  }
5069 
5071  new UserEditCountUpdate( $this, 1 ),
5073  );
5074  }
5075 
5081  public function setEditCountInternal( $count ) {
5082  $this->mEditCount = $count;
5083  }
5084 
5092  public function initEditCountInternal( IDatabase $dbr ) {
5093  // Pull from a replica DB to be less cruel to servers
5094  // Accuracy isn't the point anyway here
5095  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5096  $count = (int)$dbr->selectField(
5097  [ 'revision' ] + $actorWhere['tables'],
5098  'COUNT(*)',
5099  [ $actorWhere['conds'] ],
5100  __METHOD__,
5101  [],
5102  $actorWhere['joins']
5103  );
5104 
5105  $dbw = wfGetDB( DB_MASTER );
5106  $dbw->update(
5107  'user',
5108  [ 'user_editcount' => $count ],
5109  [
5110  'user_id' => $this->getId(),
5111  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5112  ],
5113  __METHOD__
5114  );
5115 
5116  return $count;
5117  }
5118 
5126  public static function getRightDescription( $right ) {
5127  $key = "right-$right";
5128  $msg = wfMessage( $key );
5129  return $msg->isDisabled() ? $right : $msg->text();
5130  }
5131 
5139  public static function getGrantName( $grant ) {
5140  $key = "grant-$grant";
5141  $msg = wfMessage( $key );
5142  return $msg->isDisabled() ? $grant : $msg->text();
5143  }
5144 
5165  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5166  return true; // disabled
5167  }
5168 
5177  public function addNewUserLogEntryAutoCreate() {
5178  wfDeprecated( __METHOD__, '1.27' );
5179  $this->addNewUserLogEntry( 'autocreate' );
5180 
5181  return true;
5182  }
5183 
5189  protected function loadOptions( $data = null ) {
5190  $this->load();
5191 
5192  if ( $this->mOptionsLoaded ) {
5193  return;
5194  }
5195 
5196  $this->mOptions = self::getDefaultOptions();
5197 
5198  if ( !$this->getId() ) {
5199  // For unlogged-in users, load language/variant options from request.
5200  // There's no need to do it for logged-in users: they can set preferences,
5201  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5202  // so don't override user's choice (especially when the user chooses site default).
5203  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5204  $this->mOptions['variant'] = $variant;
5205  $this->mOptions['language'] = $variant;
5206  $this->mOptionsLoaded = true;
5207  return;
5208  }
5209 
5210  // Maybe load from the object
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;
5215  }
5216  } else {
5217  if ( !is_array( $data ) ) {
5218  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5219  // Load from database
5220  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5221  ? wfGetDB( DB_MASTER )
5222  : wfGetDB( DB_REPLICA );
5223 
5224  $res = $dbr->select(
5225  'user_properties',
5226  [ 'up_property', 'up_value' ],
5227  [ 'up_user' => $this->getId() ],
5228  __METHOD__
5229  );
5230 
5231  $this->mOptionOverrides = [];
5232  $data = [];
5233  foreach ( $res as $row ) {
5234  // Convert '0' to 0. PHP's boolean conversion considers them both
5235  // false, but e.g. JavaScript considers the former as true.
5236  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5237  // and convert all values here.
5238  if ( $row->up_value === '0' ) {
5239  $row->up_value = 0;
5240  }
5241  $data[$row->up_property] = $row->up_value;
5242  }
5243  }
5244 
5245  foreach ( $data as $property => $value ) {
5246  $this->mOptionOverrides[$property] = $value;
5247  $this->mOptions[$property] = $value;
5248  }
5249  }
5250 
5251  // Replace deprecated language codes
5252  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5253  $this->mOptions['language']
5254  );
5255 
5256  $this->mOptionsLoaded = true;
5257 
5258  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5259  }
5260 
5266  protected function saveOptions() {
5267  $this->loadOptions();
5268 
5269  // Not using getOptions(), to keep hidden preferences in database
5270  $saveOptions = $this->mOptions;
5271 
5272  // Allow hooks to abort, for instance to save to a global profile.
5273  // Reset options to default state before saving.
5274  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5275  return;
5276  }
5277 
5278  $userId = $this->getId();
5279 
5280  $insert_rows = []; // all the new preference rows
5281  foreach ( $saveOptions as $key => $value ) {
5282  // Don't bother storing default values
5283  $defaultOption = self::getDefaultOption( $key );
5284  if ( ( $defaultOption === null && $value !== false && $value !== null )
5285  || $value != $defaultOption
5286  ) {
5287  $insert_rows[] = [
5288  'up_user' => $userId,
5289  'up_property' => $key,
5290  'up_value' => $value,
5291  ];
5292  }
5293  }
5294 
5295  $dbw = wfGetDB( DB_MASTER );
5296 
5297  $res = $dbw->select( 'user_properties',
5298  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5299 
5300  // Find prior rows that need to be removed or updated. These rows will
5301  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5302  $keysDelete = [];
5303  foreach ( $res as $row ) {
5304  if ( !isset( $saveOptions[$row->up_property] )
5305  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5306  ) {
5307  $keysDelete[] = $row->up_property;
5308  }
5309  }
5310 
5311  if ( count( $keysDelete ) ) {
5312  // Do the DELETE by PRIMARY KEY for prior rows.
5313  // In the past a very large portion of calls to this function are for setting
5314  // 'rememberpassword' for new accounts (a preference that has since been removed).
5315  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5316  // caused gap locks on [max user ID,+infinity) which caused high contention since
5317  // updates would pile up on each other as they are for higher (newer) user IDs.
5318  // It might not be necessary these days, but it shouldn't hurt either.
5319  $dbw->delete( 'user_properties',
5320  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5321  }
5322  // Insert the new preference rows
5323  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5324  }
5325 
5332  public static function selectFields() {
5333  wfDeprecated( __METHOD__, '1.31' );
5334  return [
5335  'user_id',
5336  'user_name',
5337  'user_real_name',
5338  'user_email',
5339  'user_touched',
5340  'user_token',
5341  'user_email_authenticated',
5342  'user_email_token',
5343  'user_email_token_expires',
5344  'user_registration',
5345  'user_editcount',
5346  ];
5347  }
5348 
5358  public static function getQueryInfo() {
5359  $ret = [
5360  'tables' => [ 'user', 'user_actor' => 'actor' ],
5361  'fields' => [
5362  'user_id',
5363  'user_name',
5364  'user_real_name',
5365  'user_email',
5366  'user_touched',
5367  'user_token',
5368  'user_email_authenticated',
5369  'user_email_token',
5370  'user_email_token_expires',
5371  'user_registration',
5372  'user_editcount',
5373  'user_actor.actor_id',
5374  ],
5375  'joins' => [
5376  'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
5377  ],
5378  ];
5379 
5380  return $ret;
5381  }
5382 
5390  static function newFatalPermissionDeniedStatus( $permission ) {
5391  global $wgLang;
5392 
5393  $groups = [];
5394  foreach ( MediaWikiServices::getInstance()
5396  ->getGroupsWithPermission( $permission ) as $group ) {
5397  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5398  }
5399 
5400  if ( $groups ) {
5401  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5402  }
5403 
5404  return Status::newFatal( 'badaccess-group0' );
5405  }
5406 
5416  public function getInstanceForUpdate() {
5417  if ( !$this->getId() ) {
5418  return null; // anon
5419  }
5420 
5421  $user = self::newFromId( $this->getId() );
5422  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5423  return null;
5424  }
5425 
5426  return $user;
5427  }
5428 
5436  public function equals( UserIdentity $user ) {
5437  // XXX it's not clear whether central ID providers are supposed to obey this
5438  return $this->getName() === $user->getName();
5439  }
5440 
5446  public function isAllowUsertalk() {
5447  return $this->mAllowUsertalk;
5448  }
5449 
5450 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1734
User\saveOptions
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5266
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:4924
User\$mNewtalk
int bool $mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:179
LanguageCode\replaceDeprecatedCodes
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date,...
Definition: LanguageCode.php:164
User\updateNewtalk
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2544
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:391
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:306
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2431
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:49
User\inDnsBlacklist
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1831
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:542
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:316
Wikimedia\Rdbms\IDatabase\affectedRows
affectedRows()
Get the number of rows affected by the last write query.
User\confirmationTokenUrl
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4627
User\__set
__set( $name, $value)
Definition: User.php:260
$wgProxyList
$wgProxyList
Big list of banned IP addresses.
Definition: DefaultSettings.php:6027
WikiMap\getCurrentWikiDbDomain
static getCurrentWikiDbDomain()
Definition: WikiMap.php:292
wfCanIPUseHTTPS
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
Definition: GlobalFunctions.php:2927
User\$mToken
string $mToken
Definition: User.php:134
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:342
User\resetGetDefaultOptionsForTestsOnly
static resetGetDefaultOptionsForTestsOnly()
Reset the process cache of default user options.
Definition: User.php:1680
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1112
User\getId
getId()
Get the user's ID.
Definition: User.php:2335
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3724
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2316
MWCryptHash\hmac
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
Definition: MWCryptHash.php:106
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:3638
User\$mBlockedFromCreateAccount
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:213
User\$mBlockreason
string $mBlockreason
Definition: User.php:187
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4654
User\isRegistered
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3622
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2403
User\$mImplicitGroups
array $mImplicitGroups
Definition: User.php:191
User\isLocallyBlockedProxy
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1879
User\$mCacheVars
static string[] $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:92
$wgRevokePermissions
$wgRevokePermissions
Permission keys revoked from users in each group.
Definition: DefaultSettings.php:5291
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3158
User\isBlockedFrom
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2216
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1501
WikiMap\isCurrentWikiId
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:312
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5390
User\getEditTokenObject
getEditTokenObject( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4462
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:7088
IP\isInRanges
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:655
User\isBot
isBot()
Definition: User.php:3646
User\$mBlock
AbstractBlock $mBlock
Definition: User.php:207
User\newTouchedTimestamp
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2622
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9017
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3518
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:4309
User\incEditCount
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition: User.php:5065
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:677
UserMailer\send
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Definition: UserMailer.php:115
User\getOptionKinds
getOptionKinds(IContextSource $context, $options=null)
Return an associative array mapping preferences keys to the kind of a preference they're used for.
Definition: User.php:3215
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4766
MediaWiki\Logger\LoggerFactory\getInstance
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
Definition: LoggerFactory.php:92
User\getBlockId
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2243
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:3093
true
return true
Definition: router.php:92
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:3050
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:130
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:232
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1849
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2846
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:4296
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2502
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:210
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1826
User\$mOptions
array $mOptions
Definition: User.php:201
User\loadOptions
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5189
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:4856
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:85
Autopromote\getAutopromoteGroups
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:37
User\setEmailWithConfirmation
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:2942
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1714
User\$mHideName
bool $mHideName
Definition: User.php:199
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3387
Sanitizer\validateEmail
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:2169
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1170
User\$mLocked
bool $mLocked
Definition: User.php:197
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:518
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1389
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1263
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4698
IP\isIPv6
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
User\$mEmail
string $mEmail
Definition: User.php:128
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:4381
User\$mOptionOverrides
array $mOptionOverrides
Definition: User.php:148
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3416
$s
$s
Definition: mergeMessageFileList.php:185
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:574
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1078
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3712
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
User\getDatePreference
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3345
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:289
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:2925
User\idForName
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:4101
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:959
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
User\sendConfirmationMail
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4528
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4881
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2915
User\pingLimiter
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1954
UserEditCountUpdate
Handles increment the edit count for a given set of users.
Definition: UserEditCountUpdate.php:29
$res
$res
Definition: testCompression.php:52
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3703
$base
$base
Definition: generateLocalAutoload.php:11
User\invalidateEmail
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4684
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:699
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6981
User\loadGroups
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1511
User\$mHash
string $mHash
Definition: User.php:185
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:6997
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1007
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
User\isIPRange
isIPRange()
Is the user an IP range?
Definition: User.php:944
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
User\deleteNewtalk
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2569
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:136
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
MediaWiki\User\UserIdentity\getActorId
getActorId()
User\getRights
getRights()
Get the permissions this user has.
Definition: User.php:3406
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4136
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3737
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
User\getAutomaticGroups
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3465
User\INVALID_TOKEN
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:61
User\setPassword
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2755
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1692
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5416
$dbr
$dbr
Definition: testCompression.php:50
$wgMaxNameChars
$wgMaxNameChars
Maximum number of bytes in username.
Definition: DefaultSettings.php:4829
IDBAccessObject\READ_LOCKING
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:64
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Specify the difference engine to use.
Definition: DefaultSettings.php:9016
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:740
UserGroupMembership\getMembershipsForUser
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
Definition: UserGroupMembership.php:311
Revision
Definition: Revision.php:40
NS_MAIN
const NS_MAIN
Definition: Defines.php:69
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3553
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:32
LoggedOutEditToken
Value object representing a logged-out user's edit token.
Definition: LoggedOutEditToken.php:35
User\matchEditToken
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4502
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2905
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:54
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:4390
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4212
IP\isValidRange
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:125
Wikimedia\Rdbms\IDatabase\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
User\invalidateCache
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2667
User\$reservedUsernames
static string[] false $reservedUsernames
Cache for self::isUsableName()
Definition: User.php:114
User\setInternalPassword
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2768
MWException
MediaWiki exception.
Definition: MWException.php:26
User\invalidationTokenUrl
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4636
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2303
User\$mDatePreference
string $mDatePreference
Definition: User.php:181
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:4962
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1044
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
User\checkPasswordValidity
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1138
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:104
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4667
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1285
UserGroupMembership\getLink
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
Definition: UserGroupMembership.php:376
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2234
getPermissionManager
getPermissionManager()
WikiMap\getWikiIdFromDbDomain
static getWikiIdFromDbDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:268
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2589
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2555
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4372
MediaWiki\Auth\AuthenticationResponse
This is a value object to hold authentication response data.
Definition: AuthenticationResponse.php:37
User\logout
logout()
Log this user out.
Definition: User.php:3979
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4609
User\initEditCountInternal
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition: User.php:5092
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:432
$wgLang
$wgLang
Definition: Setup.php:880
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1865
User\getImplicitGroups
static getImplicitGroups()
Get a list of implicit groups TODO: Should we deprecate this? It's trivial, but we don't want to enco...
Definition: User.php:4939
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2318
User\$mBlockedby
string $mBlockedby
Definition: User.php:183
UserGroupMembership\newFromRow
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
Definition: UserGroupMembership.php:93
IP\getSubnet
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition: IP.php:729
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4352
User\loadDefaults
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition: User.php:1236
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:933
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2699
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3442
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1927
$t
$t
Definition: make-normalization-table.php:143
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:70
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3588
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4726
$wgImplicitGroups
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
Definition: DefaultSettings.php:5296
User\$mOptionsLoaded
bool $mOptionsLoaded
Whether the cache variables have been loaded.
Definition: User.php:155
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4400
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2684
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:34
User\clearInstanceCache
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1642
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1708
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:78
User\TOKEN_LENGTH
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:56
$wgDefaultSkin
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
Definition: DefaultSettings.php:3339
$wgReservedUsernames
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
Definition: DefaultSettings.php:4835
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1878
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3768
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:132
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:599
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2200
User\setName
setName( $str)
Set the user name.
Definition: User.php:2392
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:125
User\resetOptions
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3300
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:42
User\$mGroupMemberships
UserGroupMembership[] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:146
UserPasswordPolicy
Check if a user's password complies with any password policies that apply to that user,...
Definition: UserPasswordPolicy.php:28
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:7008
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:913
User\__get
& __get( $name)
Definition: User.php:243
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5545
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3802
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:4027
User\addNewUserLogEntry
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5165
User\$defOpt
static array null $defOpt
Is the user an IP range?
Definition: User.php:1670
User\isAllowedAll
isAllowedAll(... $permissions)
Definition: User.php:3678
User\$defOptLang
static string null $defOptLang
Is the user an IP range?
Definition: User.php:1672
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:931
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4795
$wgAutopromoteOnceLogInRC
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
Definition: DefaultSettings.php:5516
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:73
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:3007
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2469
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:613
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:72
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3494
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:851
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\isAllowedAny
isAllowedAny(... $permissions)
Check if user is allowed to access a feature / make an action.
Definition: User.php:3666
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1296
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:5711
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2256
$wgForceHTTPS
bool $wgForceHTTPS
If this is true, when an insecure HTTP request is received, always redirect to HTTPS.
Definition: DefaultSettings.php:147
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:3022
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
User\isDnsBlacklisted
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1818
Wikimedia\Rdbms\IDatabase\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
User\getTouched
getTouched()
Get the user touched timestamp.
Definition: User.php:2711
User\$mId
int $mId
Cache variables.
Definition: User.php:119
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2270
User\__toString
__toString()
Definition: User.php:239
MediaWiki\Block\SystemBlock
System blocks are temporary blocks that are created on enforcement (e.g.
Definition: SystemBlock.php:35
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1604
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:116
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:2995
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4282
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:3943
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4849
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5026
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3876
User\VERSION
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:67
User\matchEditTokenNoSuffix
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4516
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5540
User\checkPassword
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4410
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4914
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3785
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4929
Wikimedia\Rdbms\IDatabase\update
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:36
User\isBlockedFromUpload
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition: User.php:4363
$wgInvalidUsernameCharacters
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
Definition: DefaultSettings.php:4931
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:431
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:5358
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2225
User\$mLoadedItems
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:160
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:9014
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2733
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:85
IP\isIPv4
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:42
User\getEditTimestamp
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition: User.php:4817
$context
$context
Definition: load.php:45
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4953
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:437
Autopromote\getAutopromoteOnceGroups
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:65
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2355
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3905
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1758
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4781
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:241
IP\sanitizeIP
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:139
User\isEveryoneAllowed
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4904
LanguageConverter\$languagesWithVariants
static array $languagesWithVariants
languages supporting variants
Definition: LanguageConverter.php:41
MediaWiki\User\UserIdentity\getId
getId()
MediaWiki\Block\AbstractBlock\appliesToRight
appliesToRight( $right)
Determine whether the block prevents a given right.
Definition: AbstractBlock.php:257
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5126
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2819
$cache
$cache
Definition: mcc.php:33
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:5793
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3630
User\checkTemporaryPassword
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4445
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:32
User\$mActorId
int null $mActorId
Definition: User.php:123
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2423
User\purge
static purge( $dbDomain, $userId)
Definition: User.php:421
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2889
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:871
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:913
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:140
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1042
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1180
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4740
User\$mGlobalBlock
AbstractBlock $mGlobalBlock
Definition: User.php:195
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5446
User\getBlockedStatus
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1748
User\$idCacheByName
static int[] $idCacheByName
Definition: User.php:219
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5139
UserCache\singleton
static singleton()
Definition: UserCache.php:34
User\clearSharedCache
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2641
User\isBlocked
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:2189
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:296
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:557
$keys
$keys
Definition: testCompression.php:67
NS_USER
const NS_USER
Definition: Defines.php:71
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:455
$wgLearnerMemberSince
$wgLearnerMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9015
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1535
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4583
User\getTokenFromOption
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:3130
User\$mEmailToken
string $mEmailToken
Definition: User.php:138
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:37
Wikimedia\Rdbms\IDatabase\timestampOrNull
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
$wgGroupsRemoveFromSelf
$wgGroupsRemoveFromSelf
Definition: DefaultSettings.php:5324
User\equals
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5436
User\checkNewtalk
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2529
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1336
HTMLFormField\flattenOptions
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
Definition: HTMLFormField.php:1093
User\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5332
User\$mName
string $mName
Definition: User.php:121
User\$mRegistration
string $mRegistration
Definition: User.php:142
$wgGroupPermissions
$wgGroupPermissions['sysop']['replacetext']
Definition: ReplaceText.php:56
MediaWiki\Block\AbstractBlock
Definition: AbstractBlock.php:34
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for the wiki.
Definition: DefaultSettings.php:4500
User\addNewUserLogEntryAutoCreate
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5177
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:653
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:83
User\$mRequest
WebRequest $mRequest
Definition: User.php:204
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:751
CentralIdLookup\AUDIENCE_RAW
const AUDIENCE_RAW
Definition: CentralIdLookup.php:33
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3754
UserGroupMembership\getMembership
static getMembership( $userId, $group, IDatabase $db=null)
Returns a UserGroupMembership object that pertains to the given user and group, or false if the user ...
Definition: UserGroupMembership.php:343
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:3081
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
$wgGroupsAddToSelf
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
Definition: DefaultSettings.php:5319
User\isUsableName
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:1008
User\$mFormerGroups
array $mFormerGroups
Definition: User.php:193
User\makeUpdateConditions
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1586
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1694
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4328
User\getEditToken
getEditToken( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4486
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3365
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1275
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:124
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3109
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:216
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:861
ExternalUserNames\isExternal
static isExternal( $username)
Tells whether the username is external or not.
Definition: ExternalUserNames.php:137
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2364
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:4950
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3991
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:3429
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4709
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1082
Wikimedia\Rdbms\IDatabase\delete
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
CentralIdLookup\factoryNonLocal
static factoryNonLocal()
Returns a CentralIdLookup that is guaranteed to be non-local.
Definition: CentralIdLookup.php:82
$wgNamespacesToBeSearchedDefault
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
Definition: DefaultSettings.php:6654
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:443
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
User\getLatestEditTimestamp
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4806
User\$mEffectiveGroups
array $mEffectiveGroups
Definition: User.php:189
User\setEditCountInternal
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:5081
User\setPasswordInternal
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2781
$wgDisableAnonTalk
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
Definition: DefaultSettings.php:7094
UserGroupMembership
Represents a "user group membership" – a specific instance of a user belonging to a group.
Definition: UserGroupMembership.php:37
IP\isIPAddress
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
User\$mEditCount
int $mEditCount
Definition: User.php:144
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:136
if
if($IP===false)
Definition: initImageData.php:4
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4862
User\$mFrom
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:173
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3694
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3192
$type
$type
Definition: testCompression.php:48
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:691
User\trackBlockWithCookie
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition: User.php:1325