MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
29 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
43 use Wikimedia\Assert\Assert;
44 use Wikimedia\Assert\PreconditionException;
45 use Wikimedia\IPUtils;
49 use Wikimedia\ScopedCallback;
50 
67  use ProtectedHookAccessorTrait;
69 
73  public const TOKEN_LENGTH = 32;
74 
78  public const INVALID_TOKEN = '*** INVALID ***';
79 
84  private const VERSION = 16;
85 
91  public const GETOPTIONS_EXCLUDE_DEFAULTS = UserOptionsLookup::EXCLUDE_DEFAULTS;
92 
96  public const CHECK_USER_RIGHTS = true;
97 
101  public const IGNORE_USER_RIGHTS = false;
102 
110  protected static $mCacheVars = [
111  // user table
112  'mId',
113  'mName',
114  'mRealName',
115  'mEmail',
116  'mTouched',
117  'mToken',
118  'mEmailAuthenticated',
119  'mEmailToken',
120  'mEmailTokenExpires',
121  'mRegistration',
122  'mEditCount',
123  // actor table
124  'mActorId',
125  ];
126 
128  // Some of these are public, including for use by the UserFactory, but they generally
129  // should not be set manually
130  // @{
132  public $mId;
134  public $mName;
140  public $mActorId;
142  public $mRealName;
143 
145  public $mEmail;
147  public $mTouched;
149  protected $mQuickTouched;
151  protected $mToken;
155  protected $mEmailToken;
159  protected $mRegistration;
161  protected $mEditCount;
162  // @}
163 
164  // @{
168  protected $mLoadedItems = [];
169  // @}
170 
181  public $mFrom;
182 
187  protected $mDatePreference;
194  public $mBlockedby;
196  protected $mHash;
202  protected $mBlockreason;
204  protected $mGlobalBlock;
206  protected $mLocked;
213  public $mHideName;
214 
216  private $mRequest;
217 
223  public $mBlock;
224 
226  protected $mAllowUsertalk;
227 
229  private $mBlockedFromCreateAccount = false;
230 
232  protected $queryFlagsUsed = self::READ_NORMAL;
233 
236 
238  public static $idCacheByName = [];
239 
255  public function __construct() {
256  $this->clearInstanceCache( 'defaults' );
257  }
258 
265  public function getWikiId() {
266  return self::LOCAL;
267  }
268 
272  public function __toString() {
273  return $this->getName();
274  }
275 
276  public function &__get( $name ) {
277  // A shortcut for $mRights deprecation phase
278  if ( $name === 'mRights' ) {
279  $copy = $this->getRights();
280  return $copy;
281  } elseif ( $name === 'mOptions' ) {
282  wfDeprecated( 'User::$mOptions', '1.35' );
283  $options = $this->getOptions();
284  return $options;
285  } elseif ( !property_exists( $this, $name ) ) {
286  // T227688 - do not break $u->foo['bar'] = 1
287  wfLogWarning( 'tried to get non-existent property' );
288  $this->$name = null;
289  return $this->$name;
290  } else {
291  wfLogWarning( 'tried to get non-visible property' );
292  $null = null;
293  return $null;
294  }
295  }
296 
297  public function __set( $name, $value ) {
298  // A shortcut for $mRights deprecation phase, only known legitimate use was for
299  // testing purposes, other uses seem bad in principle
300  if ( $name === 'mRights' ) {
301  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
302  $this,
303  $value === null ? [] : $value
304  );
305  } elseif ( $name === 'mOptions' ) {
306  wfDeprecated( 'User::$mOptions', '1.35' );
307  MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $this );
308  foreach ( $value as $key => $val ) {
309  $this->setOption( $key, $val );
310  }
311  } elseif ( !property_exists( $this, $name ) ) {
312  $this->$name = $value;
313  } else {
314  wfLogWarning( 'tried to set non-visible property' );
315  }
316  }
317 
318  public function __sleep(): array {
319  return array_diff(
320  array_keys( get_object_vars( $this ) ),
321  [
322  'mThisAsAuthority' // memoization, will be recreated on demand.
323  ]
324  );
325  }
326 
341  public function isSafeToLoad() {
342  global $wgFullyInitialised;
343 
344  // The user is safe to load if:
345  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
346  // * mLoadedItems === true (already loaded)
347  // * mFrom !== 'session' (sessions not involved at all)
348 
349  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
350  $this->mLoadedItems === true || $this->mFrom !== 'session';
351  }
352 
358  public function load( $flags = self::READ_NORMAL ) {
359  global $wgFullyInitialised;
360 
361  if ( $this->mLoadedItems === true ) {
362  return;
363  }
364 
365  // Set it now to avoid infinite recursion in accessors
366  $oldLoadedItems = $this->mLoadedItems;
367  $this->mLoadedItems = true;
368  $this->queryFlagsUsed = $flags;
369 
370  // If this is called too early, things are likely to break.
371  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
373  ->warning( 'User::loadFromSession called before the end of Setup.php', [
374  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
375  ] );
376  $this->loadDefaults();
377  $this->mLoadedItems = $oldLoadedItems;
378  return;
379  }
380 
381  switch ( $this->mFrom ) {
382  case 'defaults':
383  $this->loadDefaults();
384  break;
385  case 'id':
386  // Make sure this thread sees its own changes, if the ID isn't 0
387  if ( $this->mId != 0 ) {
388  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
389  if ( $lb->hasOrMadeRecentMasterChanges() ) {
390  $flags |= self::READ_LATEST;
391  $this->queryFlagsUsed = $flags;
392  }
393  }
394 
395  $this->loadFromId( $flags );
396  break;
397  case 'actor':
398  case 'name':
399  // Make sure this thread sees its own changes
400  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
401  if ( $lb->hasOrMadeRecentMasterChanges() ) {
402  $flags |= self::READ_LATEST;
403  $this->queryFlagsUsed = $flags;
404  }
405 
406  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
407  $row = wfGetDB( $index )->selectRow(
408  'actor',
409  [ 'actor_id', 'actor_user', 'actor_name' ],
410  $this->mFrom === 'name'
411  // make sure to use normalized form of IP for anonymous users
412  ? [ 'actor_name' => IPUtils::sanitizeIP( $this->mName ) ]
413  : [ 'actor_id' => $this->mActorId ],
414  __METHOD__,
415  $options
416  );
417 
418  if ( !$row ) {
419  // Ugh.
420  $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
421  } elseif ( $row->actor_user ) {
422  $this->mId = $row->actor_user;
423  $this->loadFromId( $flags );
424  } else {
425  $this->loadDefaults( $row->actor_name, $row->actor_id );
426  }
427  break;
428  case 'session':
429  if ( !$this->loadFromSession() ) {
430  // Loading from session failed. Load defaults.
431  $this->loadDefaults();
432  }
433  $this->getHookRunner()->onUserLoadAfterLoadFromSession( $this );
434  break;
435  default:
436  throw new UnexpectedValueException(
437  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
438  }
439  }
440 
446  public function loadFromId( $flags = self::READ_NORMAL ) {
447  if ( $this->mId == 0 ) {
448  // Anonymous users are not in the database (don't need cache)
449  $this->loadDefaults();
450  return false;
451  }
452 
453  // Try cache (unless this needs data from the master DB).
454  // NOTE: if this thread called saveSettings(), the cache was cleared.
455  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
456  if ( $latest ) {
457  if ( !$this->loadFromDatabase( $flags ) ) {
458  // Can't load from ID
459  return false;
460  }
461  } else {
462  $this->loadFromCache();
463  }
464 
465  $this->mLoadedItems = true;
466  $this->queryFlagsUsed = $flags;
467 
468  return true;
469  }
470 
476  public static function purge( $dbDomain, $userId ) {
477  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
478  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
479  $cache->delete( $key );
480  }
481 
487  protected function getCacheKey( WANObjectCache $cache ) {
488  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
489 
490  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
491  }
492 
499  $id = $this->getId();
500 
501  return $id ? [ $this->getCacheKey( $cache ) ] : [];
502  }
503 
510  protected function loadFromCache() {
511  global $wgFullyInitialised;
512 
513  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
514  $data = $cache->getWithSetCallback(
515  $this->getCacheKey( $cache ),
516  $cache::TTL_HOUR,
517  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache, $wgFullyInitialised ) {
518  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
519  wfDebug( "User: cache miss for user {$this->mId}" );
520 
521  $this->loadFromDatabase( self::READ_NORMAL );
522 
523  $data = [];
524  foreach ( self::$mCacheVars as $name ) {
525  $data[$name] = $this->$name;
526  }
527 
528  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
529 
530  if ( $wgFullyInitialised ) {
531  $groupMemberships = MediaWikiServices::getInstance()
532  ->getUserGroupManager()
533  ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
534 
535  // if a user group membership is about to expire, the cache needs to
536  // expire at that time (T163691)
537  foreach ( $groupMemberships as $ugm ) {
538  if ( $ugm->getExpiry() ) {
539  $secondsUntilExpiry =
540  wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
541 
542  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
543  $ttl = $secondsUntilExpiry;
544  }
545  }
546  }
547  }
548 
549  return $data;
550  },
551  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
552  );
553 
554  // Restore from cache
555  foreach ( self::$mCacheVars as $name ) {
556  $this->$name = $data[$name];
557  }
558 
559  return true;
560  }
561 
562  /***************************************************************************/
563  // region newFrom*() static factory methods
584  public static function newFromName( $name, $validate = 'valid' ) {
585  // Backwards compatibility with strings / false
586  $validationLevels = [
587  'valid' => UserFactory::RIGOR_VALID,
588  'usable' => UserFactory::RIGOR_USABLE,
589  'creatable' => UserFactory::RIGOR_CREATABLE
590  ];
591  if ( $validate === true ) {
592  $validate = 'valid';
593  }
594  if ( $validate === false ) {
595  $validation = UserFactory::RIGOR_NONE;
596  } elseif ( array_key_exists( $validate, $validationLevels ) ) {
597  $validation = $validationLevels[ $validate ];
598  } else {
599  // Not a recognized value, probably a test for unsupported validation
600  // levels, regardless, just pass it along
601  $validation = $validate;
602  }
603 
604  $user = MediaWikiServices::getInstance()
605  ->getUserFactory()
606  ->newFromName( (string)$name, $validation );
607 
608  // UserFactory returns null instead of false
609  if ( $user === null ) {
610  $user = false;
611  }
612  return $user;
613  }
614 
623  public static function newFromId( $id ) {
624  return MediaWikiServices::getInstance()
625  ->getUserFactory()
626  ->newFromId( (int)$id );
627  }
628 
638  public static function newFromActorId( $id ) {
639  return MediaWikiServices::getInstance()
640  ->getUserFactory()
641  ->newFromActorId( (int)$id );
642  }
643 
655  public static function newFromIdentity( UserIdentity $identity ) {
656  // Don't use the service if we already have a User object,
657  // so that User::newFromIdentity calls don't break things in unit tests.
658  if ( $identity instanceof User ) {
659  return $identity;
660  }
661 
662  return MediaWikiServices::getInstance()
663  ->getUserFactory()
664  ->newFromUserIdentity( $identity );
665  }
666 
682  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
683  return MediaWikiServices::getInstance()
684  ->getUserFactory()
685  ->newFromAnyId( $userId, $userName, $actorId, $dbDomain );
686  }
687 
701  public static function newFromConfirmationCode( $code, $flags = self::READ_NORMAL ) {
702  return MediaWikiServices::getInstance()
703  ->getUserFactory()
704  ->newFromConfirmationCode( (string)$code, $flags );
705  }
706 
714  public static function newFromSession( WebRequest $request = null ) {
715  $user = new User;
716  $user->mFrom = 'session';
717  $user->mRequest = $request;
718  return $user;
719  }
720 
736  public static function newFromRow( $row, $data = null ) {
737  $user = new User;
738  $user->loadFromRow( $row, $data );
739  return $user;
740  }
741 
777  public static function newSystemUser( $name, $options = [] ) {
778  $options += [
779  'validate' => UserNameUtils::RIGOR_VALID,
780  'create' => true,
781  'steal' => false,
782  ];
783 
784  // Username validation
785  // Backwards compatibility with strings / false
786  $validationLevels = [
787  'valid' => UserNameUtils::RIGOR_VALID,
788  'usable' => UserNameUtils::RIGOR_USABLE,
789  'creatable' => UserNameUtils::RIGOR_CREATABLE
790  ];
791  $validate = $options['validate'];
792 
793  // @phan-suppress-next-line PhanSuspiciousValueComparison
794  if ( $validate === false ) {
795  $validation = UserNameUtils::RIGOR_NONE;
796  } elseif ( array_key_exists( $validate, $validationLevels ) ) {
797  $validation = $validationLevels[ $validate ];
798  } else {
799  // Not a recognized value, probably a test for unsupported validation
800  // levels, regardless, just pass it along
801  $validation = $validate;
802  }
803 
804  if ( $validation !== UserNameUtils::RIGOR_VALID ) {
806  __METHOD__ . ' options["validation"] parameter must be omitted or set to "valid".',
807  '1.36'
808  );
809  }
810  $services = MediaWikiServices::getInstance();
811  $userNameUtils = $services->getUserNameUtils();
812 
813  $name = $userNameUtils->getCanonical( (string)$name, $validation );
814  if ( $name === false ) {
815  return null;
816  }
817 
818  $loadBalancer = $services->getDBLoadBalancer();
819  $dbr = $loadBalancer->getConnectionRef( DB_REPLICA );
820 
821  $userQuery = self::getQueryInfo();
822  $row = $dbr->selectRow(
823  $userQuery['tables'],
824  $userQuery['fields'],
825  [ 'user_name' => $name ],
826  __METHOD__,
827  [],
828  $userQuery['joins']
829  );
830  if ( !$row ) {
831  // Try the master database...
832  $dbw = $loadBalancer->getConnectionRef( DB_MASTER );
833  $row = $dbw->selectRow(
834  $userQuery['tables'],
835  $userQuery['fields'],
836  [ 'user_name' => $name ],
837  __METHOD__,
838  [],
839  $userQuery['joins']
840  );
841  }
842 
843  if ( !$row ) {
844  // No user. Create it?
845  if ( !$options['create'] ) {
846  // No.
847  return null;
848  }
849 
850  // If it's a reserved user that had an anonymous actor created for it at
851  // some point, we need special handling.
852  if ( !$userNameUtils->isValid( $name ) || $userNameUtils->isUsable( $name ) ) {
853  // Not reserved, so just create it.
854  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
855  }
856 
857  // It is reserved. Check for an anonymous actor row.
858  $dbw = $loadBalancer->getConnectionRef( DB_MASTER );
859  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $name ) {
860  $row = $dbw->selectRow(
861  'actor',
862  [ 'actor_id' ],
863  [ 'actor_name' => $name, 'actor_user' => null ],
864  $fname,
865  [ 'FOR UPDATE' ]
866  );
867  if ( !$row ) {
868  // No anonymous actor.
869  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
870  }
871 
872  // There is an anonymous actor. Delete the actor row so we can create the user,
873  // then restore the old actor_id so as to not break existing references.
874  // @todo If MediaWiki ever starts using foreign keys for `actor`, this will break things.
875  $dbw->delete( 'actor', [ 'actor_id' => $row->actor_id ], $fname );
876  $user = self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
877  $dbw->update(
878  'actor',
879  [ 'actor_id' => $row->actor_id ],
880  [ 'actor_id' => $user->getActorId() ],
881  $fname
882  );
883  $user->clearInstanceCache( 'id' );
884  $user->invalidateCache();
885  return $user;
886  } );
887  }
888 
889  $user = self::newFromRow( $row );
890 
891  if ( !$user->isSystemUser() ) {
892  // User exists. Steal it?
893  if ( !$options['steal'] ) {
894  return null;
895  }
896 
897  $services->getAuthManager()->revokeAccessForUser( $name );
898 
899  $user->invalidateEmail();
900  $user->mToken = self::INVALID_TOKEN;
901  $user->saveSettings();
902  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
903  }
904 
905  return $user;
906  }
907 
909  // endregion -- end of newFrom*() static factory methods
910 
916  public static function whoIs( $id ) {
917  return UserCache::singleton()->getProp( $id, 'name' );
918  }
919 
926  public static function whoIsReal( $id ) {
927  return UserCache::singleton()->getProp( $id, 'real_name' );
928  }
929 
936  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
937  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
938  $name = (string)$name;
939  $nt = Title::makeTitleSafe( NS_USER, $name );
940  if ( $nt === null ) {
941  // Illegal name
942  return null;
943  }
944 
945  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
946  return self::$idCacheByName[$name] === null ? null : (int)self::$idCacheByName[$name];
947  }
948 
949  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
950  $db = wfGetDB( $index );
951 
952  $s = $db->selectRow(
953  'user',
954  [ 'user_id' ],
955  [ 'user_name' => $nt->getText() ],
956  __METHOD__,
957  $options
958  );
959 
960  if ( $s === false ) {
961  $result = null;
962  } else {
963  $result = (int)$s->user_id;
964  }
965 
966  if ( count( self::$idCacheByName ) >= 1000 ) {
967  self::$idCacheByName = [];
968  }
969 
970  self::$idCacheByName[$name] = $result;
971 
972  return $result;
973  }
974 
978  public static function resetIdByNameCache() {
979  self::$idCacheByName = [];
980  }
981 
1000  public static function isIP( $name ) {
1001  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
1002  || IPUtils::isIPv6( $name );
1003  }
1004 
1012  public function isIPRange() {
1013  return IPUtils::isValidRange( $this->mName );
1014  }
1015 
1028  public static function isValidUserName( $name ) {
1029  return MediaWikiServices::getInstance()->getUserNameUtils()->isValid( $name );
1030  }
1031 
1044  public static function isUsableName( $name ) {
1045  return MediaWikiServices::getInstance()->getUserNameUtils()->isUsable( $name );
1046  }
1047 
1058  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1059  if ( $groups === [] ) {
1060  return UserArrayFromResult::newFromIDs( [] );
1061  }
1062 
1063  $groups = array_unique( (array)$groups );
1064  $limit = min( 5000, $limit );
1065 
1066  $conds = [ 'ug_group' => $groups ];
1067  if ( $after !== null ) {
1068  $conds[] = 'ug_user > ' . (int)$after;
1069  }
1070 
1071  $dbr = wfGetDB( DB_REPLICA );
1072  $ids = $dbr->selectFieldValues(
1073  'user_groups',
1074  'ug_user',
1075  $conds,
1076  __METHOD__,
1077  [
1078  'DISTINCT' => true,
1079  'ORDER BY' => 'ug_user',
1080  'LIMIT' => $limit,
1081  ]
1082  ) ?: [];
1083  return UserArray::newFromIDs( $ids );
1084  }
1085 
1099  public static function isCreatableName( $name ) {
1100  return MediaWikiServices::getInstance()->getUserNameUtils()->isCreatable( $name );
1101  }
1102 
1109  public function isValidPassword( $password ) {
1110  // simple boolean wrapper for checkPasswordValidity
1111  return $this->checkPasswordValidity( $password )->isGood();
1112  }
1113 
1135  public function checkPasswordValidity( $password ) {
1136  global $wgPasswordPolicy;
1137 
1138  $upp = new UserPasswordPolicy(
1139  $wgPasswordPolicy['policies'],
1140  $wgPasswordPolicy['checks']
1141  );
1142 
1143  $status = Status::newGood( [] );
1144  $result = false; // init $result to false for the internal checks
1145 
1146  if ( !$this->getHookRunner()->onIsValidPassword( $password, $result, $this ) ) {
1147  $status->error( $result );
1148  return $status;
1149  }
1150 
1151  if ( $result === false ) {
1152  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1153  return $status;
1154  }
1155 
1156  if ( $result === true ) {
1157  return $status;
1158  }
1159 
1160  $status->error( $result );
1161  return $status; // the isValidPassword hook set a string $result and returned true
1162  }
1163 
1179  public static function getCanonicalName( $name, $validate = 'valid' ) {
1180  // Backwards compatibility with strings / false
1181  $validationLevels = [
1182  'valid' => UserNameUtils::RIGOR_VALID,
1183  'usable' => UserNameUtils::RIGOR_USABLE,
1184  'creatable' => UserNameUtils::RIGOR_CREATABLE
1185  ];
1186 
1187  if ( $validate === false ) {
1188  $validation = UserNameUtils::RIGOR_NONE;
1189  } elseif ( array_key_exists( $validate, $validationLevels ) ) {
1190  $validation = $validationLevels[ $validate ];
1191  } else {
1192  // Not a recognized value, probably a test for unsupported validation
1193  // levels, regardless, just pass it along
1194  $validation = $validate;
1195  }
1196 
1197  return MediaWikiServices::getInstance()
1198  ->getUserNameUtils()
1199  ->getCanonical( (string)$name, $validation );
1200  }
1201 
1211  public function loadDefaults( $name = false, $actorId = null ) {
1212  $this->mId = 0;
1213  $this->mName = $name;
1214  $this->mActorId = $actorId;
1215  $this->mRealName = '';
1216  $this->mEmail = '';
1217 
1218  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1219  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1220  if ( $loggedOut !== 0 ) {
1221  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1222  } else {
1223  $this->mTouched = '1'; # Allow any pages to be cached
1224  }
1225 
1226  $this->mToken = null; // Don't run cryptographic functions till we need a token
1227  $this->mEmailAuthenticated = null;
1228  $this->mEmailToken = '';
1229  $this->mEmailTokenExpires = null;
1230  $this->mRegistration = wfTimestamp( TS_MW );
1231 
1232  $this->getHookRunner()->onUserLoadDefaults( $this, $name );
1233  }
1234 
1247  public function isItemLoaded( $item, $all = 'all' ) {
1248  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1249  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1250  }
1251 
1259  public function setItemLoaded( $item ) {
1260  if ( is_array( $this->mLoadedItems ) ) {
1261  $this->mLoadedItems[$item] = true;
1262  }
1263  }
1264 
1270  private function loadFromSession() {
1271  // MediaWiki\Session\Session already did the necessary authentication of the user
1272  // returned here, so just use it if applicable.
1273  $session = $this->getRequest()->getSession();
1274  $user = $session->getUser();
1275  if ( $user->isRegistered() ) {
1276  $this->loadFromUserObject( $user );
1277 
1278  // Other code expects these to be set in the session, so set them.
1279  $session->set( 'wsUserID', $this->getId() );
1280  $session->set( 'wsUserName', $this->getName() );
1281  $session->set( 'wsToken', $this->getToken() );
1282 
1283  return true;
1284  }
1285 
1286  return false;
1287  }
1288 
1296  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1297  // Paranoia
1298  $this->mId = intval( $this->mId );
1299 
1300  if ( !$this->mId ) {
1301  // Anonymous users are not in the database
1302  $this->loadDefaults();
1303  return false;
1304  }
1305 
1306  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1307  $db = wfGetDB( $index );
1308 
1309  $userQuery = self::getQueryInfo();
1310  $s = $db->selectRow(
1311  $userQuery['tables'],
1312  $userQuery['fields'],
1313  [ 'user_id' => $this->mId ],
1314  __METHOD__,
1315  $options,
1316  $userQuery['joins']
1317  );
1318 
1319  $this->queryFlagsUsed = $flags;
1320  $this->getHookRunner()->onUserLoadFromDatabase( $this, $s );
1321 
1322  if ( $s !== false ) {
1323  // Initialise user table data
1324  $this->loadFromRow( $s );
1325  $this->getEditCount(); // revalidation for nulls
1326  return true;
1327  }
1328 
1329  // Invalid user_id
1330  $this->mId = 0;
1331  $this->loadDefaults( 'Unknown user' );
1332 
1333  return false;
1334  }
1335 
1348  protected function loadFromRow( $row, $data = null ) {
1349  if ( !is_object( $row ) ) {
1350  throw new InvalidArgumentException( '$row must be an object' );
1351  }
1352 
1353  $all = true;
1354 
1355  if ( isset( $row->actor_id ) ) {
1356  $this->mActorId = (int)$row->actor_id;
1357  if ( $this->mActorId !== 0 ) {
1358  $this->mFrom = 'actor';
1359  }
1360  $this->setItemLoaded( 'actor' );
1361  } else {
1362  $all = false;
1363  }
1364 
1365  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1366  $this->mName = $row->user_name;
1367  $this->mFrom = 'name';
1368  $this->setItemLoaded( 'name' );
1369  } else {
1370  $all = false;
1371  }
1372 
1373  if ( isset( $row->user_real_name ) ) {
1374  $this->mRealName = $row->user_real_name;
1375  $this->setItemLoaded( 'realname' );
1376  } else {
1377  $all = false;
1378  }
1379 
1380  if ( isset( $row->user_id ) ) {
1381  $this->mId = intval( $row->user_id );
1382  if ( $this->mId !== 0 ) {
1383  $this->mFrom = 'id';
1384  }
1385  $this->setItemLoaded( 'id' );
1386  } else {
1387  $all = false;
1388  }
1389 
1390  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1391  self::$idCacheByName[$row->user_name] = $row->user_id;
1392  }
1393 
1394  if ( isset( $row->user_editcount ) ) {
1395  $this->mEditCount = $row->user_editcount;
1396  } else {
1397  $all = false;
1398  }
1399 
1400  if ( isset( $row->user_touched ) ) {
1401  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1402  } else {
1403  $all = false;
1404  }
1405 
1406  if ( isset( $row->user_token ) ) {
1407  // The definition for the column is binary(32), so trim the NULs
1408  // that appends. The previous definition was char(32), so trim
1409  // spaces too.
1410  $this->mToken = rtrim( $row->user_token, " \0" );
1411  if ( $this->mToken === '' ) {
1412  $this->mToken = null;
1413  }
1414  } else {
1415  $all = false;
1416  }
1417 
1418  if ( isset( $row->user_email ) ) {
1419  $this->mEmail = $row->user_email;
1420  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1421  $this->mEmailToken = $row->user_email_token;
1422  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1423  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1424  } else {
1425  $all = false;
1426  }
1427 
1428  if ( $all ) {
1429  $this->mLoadedItems = true;
1430  }
1431 
1432  if ( is_array( $data ) ) {
1433 
1434  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1435  MediaWikiServices::getInstance()
1436  ->getUserGroupManager()
1437  ->loadGroupMembershipsFromArray(
1438  $this,
1439  $data['user_groups'],
1440  $this->queryFlagsUsed
1441  );
1442  }
1443  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1444  MediaWikiServices::getInstance()
1445  ->getUserOptionsManager()
1446  ->loadUserOptions( $this, $this->queryFlagsUsed, $data['user_properties'] );
1447  }
1448  }
1449  }
1450 
1456  protected function loadFromUserObject( $user ) {
1457  $user->load();
1458  foreach ( self::$mCacheVars as $var ) {
1459  $this->$var = $user->$var;
1460  }
1461  }
1462 
1478  public function addAutopromoteOnceGroups( $event ) {
1479  return MediaWikiServices::getInstance()
1480  ->getUserGroupManager()
1481  ->addUserToAutopromoteOnceGroups( $this, $event );
1482  }
1483 
1493  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1494  if ( $this->mTouched ) {
1495  // CAS check: only update if the row wasn't changed sicne it was loaded.
1496  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1497  }
1498 
1499  return $conditions;
1500  }
1501 
1512  public function checkAndSetTouched() {
1513  $this->load();
1514 
1515  if ( !$this->mId ) {
1516  return false; // anon
1517  }
1518 
1519  // Get a new user_touched that is higher than the old one
1520  $newTouched = $this->newTouchedTimestamp();
1521 
1522  $dbw = wfGetDB( DB_MASTER );
1523  $dbw->update( 'user',
1524  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1525  $this->makeUpdateConditions( $dbw, [
1526  'user_id' => $this->mId,
1527  ] ),
1528  __METHOD__
1529  );
1530  $success = ( $dbw->affectedRows() > 0 );
1531 
1532  if ( $success ) {
1533  $this->mTouched = $newTouched;
1534  $this->clearSharedCache( 'changed' );
1535  } else {
1536  // Clears on failure too since that is desired if the cache is stale
1537  $this->clearSharedCache( 'refresh' );
1538  }
1539 
1540  return $success;
1541  }
1542 
1550  public function clearInstanceCache( $reloadFrom = false ) {
1551  global $wgFullyInitialised;
1552 
1553  $this->mDatePreference = null;
1554  $this->mBlockedby = -1; # Unset
1555  $this->mHash = false;
1556  $this->mEditCount = null;
1557  $this->mThisAsAuthority = null;
1558 
1559  if ( $wgFullyInitialised && $this->mFrom ) {
1560  $services = MediaWikiServices::getInstance();
1561  $services->getPermissionManager()->invalidateUsersRightsCache( $this );
1562  $services->getUserOptionsManager()->clearUserOptionsCache( $this );
1563  $services->getTalkPageNotificationManager()->clearInstanceCache( $this );
1564  $services->getUserGroupManager()->clearCache( $this );
1565  $services->getUserEditTracker()->clearUserEditCache( $this );
1566  }
1567 
1568  if ( $reloadFrom ) {
1569  $this->mLoadedItems = [];
1570  $this->mFrom = $reloadFrom;
1571  }
1572  }
1573 
1581  public static function getDefaultOptions() {
1582  return MediaWikiServices::getInstance()
1583  ->getUserOptionsLookup()
1584  ->getDefaultOptions();
1585  }
1586 
1594  public static function getDefaultOption( $opt ) {
1595  return MediaWikiServices::getInstance()
1596  ->getUserOptionsLookup()
1597  ->getDefaultOption( $opt );
1598  }
1599 
1611  private function getBlockedStatus( $fromReplica = true, $disableIpBlockExemptChecking = false ) {
1612  if ( $this->mBlockedby != -1 ) {
1613  return;
1614  }
1615 
1616  wfDebug( __METHOD__ . ": checking blocked status for " . $this->getName() );
1617 
1618  // Initialize data...
1619  // Otherwise something ends up stomping on $this->mBlockedby when
1620  // things get lazy-loaded later, causing false positive block hits
1621  // due to -1 !== 0. Probably session-related... Nothing should be
1622  // overwriting mBlockedby, surely?
1623  $this->load();
1624 
1625  // TODO: Block checking shouldn't really be done from the User object. Block
1626  // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1627  // which need more knowledge of the request context than the User should have.
1628  // Since we do currently check blocks from the User, we have to do the following
1629  // here:
1630  // - Check if this is the user associated with the main request
1631  // - If so, pass the relevant request information to the block manager
1632  $request = null;
1633  if ( $this->isGlobalSessionUser() ) {
1634  // This is the global user, so we need to pass the request
1635  $request = $this->getRequest();
1636  }
1637 
1638  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1639  $this,
1640  $request,
1641  $fromReplica,
1642  $disableIpBlockExemptChecking
1643  );
1644 
1645  if ( $block ) {
1646  $this->mBlock = $block;
1647  $this->mBlockedby = $block->getByName();
1648  $this->mBlockreason = $block->getReason();
1649  $this->mHideName = $block->getHideName();
1650  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1651  } else {
1652  $this->mBlock = null;
1653  $this->mBlockedby = '';
1654  $this->mBlockreason = '';
1655  $this->mHideName = 0;
1656  $this->mAllowUsertalk = false;
1657  }
1658  }
1659 
1665  public function isPingLimitable() {
1666  global $wgRateLimitsExcludedIPs;
1667  if ( IPUtils::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1668  // No other good way currently to disable rate limits
1669  // for specific IPs. :P
1670  // But this is a crappy hack and should die.
1671  return false;
1672  }
1673  return !$this->isAllowed( 'noratelimit' );
1674  }
1675 
1692  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1693  $logger = \MediaWiki\Logger\LoggerFactory::getInstance( 'ratelimit' );
1694 
1695  // Call the 'PingLimiter' hook
1696  $result = false;
1697  if ( !$this->getHookRunner()->onPingLimiter( $this, $action, $result, $incrBy ) ) {
1698  return $result;
1699  }
1700 
1701  global $wgRateLimits;
1702  if ( !isset( $wgRateLimits[$action] ) ) {
1703  return false;
1704  }
1705 
1706  $limits = array_merge(
1707  [ '&can-bypass' => true ],
1708  $wgRateLimits[$action]
1709  );
1710 
1711  // Some groups shouldn't trigger the ping limiter, ever
1712  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1713  return false;
1714  }
1715 
1716  $logger->debug( __METHOD__ . ": limiting $action rate for {$this->getName()}" );
1717 
1718  $keys = [];
1719  $id = $this->getId();
1720  $isNewbie = $this->isNewbie();
1722 
1723  if ( $id == 0 ) {
1724  // "shared anon" limit, for all anons combined
1725  if ( isset( $limits['anon'] ) ) {
1726  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1727  }
1728  } else {
1729  // "global per name" limit, across sites
1730  if ( isset( $limits['user-global'] ) ) {
1732 
1733  $centralId = $lookup
1734  ? $lookup->centralIdFromLocalUser( $this, CentralIdLookup::AUDIENCE_RAW )
1735  : 0;
1736 
1737  if ( $centralId ) {
1738  // We don't have proper realms, use provider ID.
1739  $realm = $lookup->getProviderId();
1740 
1741  $globalKey = $cache->makeGlobalKey( 'limiter', $action, 'user-global',
1742  $realm, $centralId );
1743  } else {
1744  // Fall back to a local key for a local ID
1745  $globalKey = $cache->makeKey( 'limiter', $action, 'user-global',
1746  'local', $id );
1747  }
1748  $keys[$globalKey] = $limits['user-global'];
1749  }
1750  }
1751 
1752  if ( $isNewbie ) {
1753  // "per ip" limit for anons and newbie users
1754  if ( isset( $limits['ip'] ) ) {
1755  $ip = $this->getRequest()->getIP();
1756  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
1757  }
1758  // "per subnet" limit for anons and newbie users
1759  if ( isset( $limits['subnet'] ) ) {
1760  $ip = $this->getRequest()->getIP();
1761  $subnet = IPUtils::getSubnet( $ip );
1762  if ( $subnet !== false ) {
1763  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
1764  }
1765  }
1766  }
1767 
1768  // determine the "per user account" limit
1769  $userLimit = false;
1770  if ( $id !== 0 && isset( $limits['user'] ) ) {
1771  // default limit for logged-in users
1772  $userLimit = $limits['user'];
1773  }
1774  // limits for newbie logged-in users (overrides all the normal user limits)
1775  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1776  $userLimit = $limits['newbie'];
1777  } else {
1778  // Check for group-specific limits
1779  // If more than one group applies, use the highest allowance (if higher than the default)
1780  foreach ( $this->getGroups() as $group ) {
1781  if ( isset( $limits[$group] ) ) {
1782  if ( $userLimit === false
1783  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1784  ) {
1785  $userLimit = $limits[$group];
1786  }
1787  }
1788  }
1789  }
1790 
1791  // Set the user limit key
1792  if ( $userLimit !== false ) {
1793  // phan is confused because &can-bypass's value is a bool, so it assumes
1794  // that $userLimit is also a bool here.
1795  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1796  list( $max, $period ) = $userLimit;
1797  $logger->debug( __METHOD__ . ": effective user limit: $max in {$period}s" );
1798  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
1799  }
1800 
1801  // ip-based limits for all ping-limitable users
1802  if ( isset( $limits['ip-all'] ) ) {
1803  $ip = $this->getRequest()->getIP();
1804  // ignore if user limit is more permissive
1805  if ( $isNewbie || $userLimit === false
1806  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1807  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip-all', $ip )] = $limits['ip-all'];
1808  }
1809  }
1810 
1811  // subnet-based limits for all ping-limitable users
1812  if ( isset( $limits['subnet-all'] ) ) {
1813  $ip = $this->getRequest()->getIP();
1814  $subnet = IPUtils::getSubnet( $ip );
1815  if ( $subnet !== false ) {
1816  // ignore if user limit is more permissive
1817  if ( $isNewbie || $userLimit === false
1818  || $limits['ip-all'][0] / $limits['ip-all'][1]
1819  > $userLimit[0] / $userLimit[1] ) {
1820  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet-all', $subnet )] = $limits['subnet-all'];
1821  }
1822  }
1823  }
1824 
1825  // XXX: We may want to use $cache->getCurrentTime() here, but that would make it
1826  // harder to test for T246991. Also $cache->getCurrentTime() is documented
1827  // as being for testing only, so it apparently should not be called here.
1828  $now = MWTimestamp::time();
1829  $clockFudge = 3; // avoid log spam when a clock is slightly off
1830 
1831  $triggered = false;
1832  foreach ( $keys as $key => $limit ) {
1833 
1834  // Do the update in a merge callback, for atomicity.
1835  // To use merge(), we need to explicitly track the desired expiry timestamp.
1836  // This tracking was introduced to investigate T246991. Once it is no longer needed,
1837  // we could go back to incrWithInit(), though that has more potential for race
1838  // conditions between the get() and incrWithInit() calls.
1839  $cache->merge(
1840  $key,
1841  function ( $cache, $key, $data, &$expiry )
1842  use ( $action, $logger, &$triggered, $now, $clockFudge, $limit, $incrBy )
1843  {
1844  // phan is confused because &can-bypass's value is a bool, so it assumes
1845  // that $userLimit is also a bool here.
1846  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1847  list( $max, $period ) = $limit;
1848 
1849  $expiry = $now + (int)$period;
1850  $count = 0;
1851 
1852  // Already pinged?
1853  if ( $data ) {
1854  // NOTE: in order to investigate T246991, we write the expiry time
1855  // into the payload, along with the count.
1856  $fields = explode( '|', $data );
1857  $storedCount = (int)( $fields[0] ?? 0 );
1858  $storedExpiry = (int)( $fields[1] ?? PHP_INT_MAX );
1859 
1860  // Found a stale entry. This should not happen!
1861  if ( $storedExpiry < ( $now + $clockFudge ) ) {
1862  $logger->info(
1863  'User::pingLimiter: '
1864  . 'Stale rate limit entry, cache key failed to expire (T246991)',
1865  [
1866  'action' => $action,
1867  'user' => $this->getName(),
1868  'limit' => $max,
1869  'period' => $period,
1870  'count' => $storedCount,
1871  'key' => $key,
1872  'expiry' => MWTimestamp::convert( TS_DB, $storedExpiry ),
1873  ]
1874  );
1875  } else {
1876  // NOTE: We'll keep the original expiry when bumping counters,
1877  // resulting in a kind of fixed-window throttle.
1878  $expiry = min( $storedExpiry, $now + (int)$period );
1879  $count = $storedCount;
1880  }
1881  }
1882 
1883  // Limit exceeded!
1884  if ( $count >= $max ) {
1885  if ( !$triggered ) {
1886  $logger->info(
1887  'User::pingLimiter: User tripped rate limit',
1888  [
1889  'action' => $action,
1890  'user' => $this->getName(),
1891  'ip' => $this->getRequest()->getIP(),
1892  'limit' => $max,
1893  'period' => $period,
1894  'count' => $count,
1895  'key' => $key
1896  ]
1897  );
1898  }
1899 
1900  $triggered = true;
1901  }
1902 
1903  $count += $incrBy;
1904  $data = "$count|$expiry";
1905  return $data;
1906  }
1907  );
1908  }
1909 
1910  return $triggered;
1911  }
1912 
1924  public function isBlocked( $fromReplica = true ) {
1925  return $this->getBlock( $fromReplica ) instanceof AbstractBlock;
1926  }
1927 
1936  public function getBlock( $fromReplica = true, $disableIpBlockExemptChecking = false ) {
1937  $this->getBlockedStatus( $fromReplica, $disableIpBlockExemptChecking );
1938  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
1939  }
1940 
1952  public function isBlockedFrom( $title, $fromReplica = false ) {
1953  return MediaWikiServices::getInstance()->getPermissionManager()
1954  ->isBlockedFrom( $this, $title, $fromReplica );
1955  }
1956 
1961  public function blockedBy() {
1962  $this->getBlockedStatus();
1963  return $this->mBlockedby;
1964  }
1965 
1972  public function blockedFor() {
1973  $this->getBlockedStatus();
1974  return $this->mBlockreason;
1975  }
1976 
1981  public function getBlockId() {
1982  $this->getBlockedStatus();
1983  return ( $this->mBlock ? $this->mBlock->getId() : false );
1984  }
1985 
1994  public function isBlockedGlobally( $ip = '' ) {
1995  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
1996  }
1997 
2008  public function getGlobalBlock( $ip = '' ) {
2009  if ( $this->mGlobalBlock !== null ) {
2010  return $this->mGlobalBlock ?: null;
2011  }
2012  // User is already an IP?
2013  if ( IPUtils::isIPAddress( $this->getName() ) ) {
2014  $ip = $this->getName();
2015  } elseif ( !$ip ) {
2016  $ip = $this->getRequest()->getIP();
2017  }
2018  $blocked = false;
2019  $block = null;
2020  $this->getHookRunner()->onUserIsBlockedGlobally( $this, $ip, $blocked, $block );
2021 
2022  if ( $blocked && $block === null ) {
2023  // back-compat: UserIsBlockedGlobally didn't have $block param first
2024  $block = new SystemBlock( [
2025  'address' => $ip,
2026  'systemBlock' => 'global-block'
2027  ] );
2028  }
2029 
2030  $this->mGlobalBlock = $blocked ? $block : false;
2031  return $this->mGlobalBlock ?: null;
2032  }
2033 
2039  public function isLocked() {
2040  if ( $this->mLocked !== null ) {
2041  return $this->mLocked;
2042  }
2043  // Reset for hook
2044  $this->mLocked = false;
2045  $this->getHookRunner()->onUserIsLocked( $this, $this->mLocked );
2046  return $this->mLocked;
2047  }
2048 
2054  public function isHidden() {
2055  if ( $this->mHideName !== null ) {
2056  return (bool)$this->mHideName;
2057  }
2058  $this->getBlockedStatus();
2059  return (bool)$this->mHideName;
2060  }
2061 
2067  public function getId() : int {
2068  if ( $this->mId === null && $this->mName !== null &&
2069  ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
2070  ) {
2071  // Special case, we know the user is anonymous
2072  // Note that "external" users are "local" (they have an actor ID that is relative to
2073  // the local wiki).
2074  return 0;
2075  }
2076 
2077  if ( !$this->isItemLoaded( 'id' ) ) {
2078  // Don't load if this was initialized from an ID
2079  $this->load();
2080  }
2081 
2082  return (int)$this->mId;
2083  }
2084 
2095  public function getUserId( $wikiId = self::LOCAL ) : int {
2096  $this->assertWiki( $wikiId );
2097  return $this->getId();
2098  }
2099 
2104  public function setId( $v ) {
2105  $this->mId = $v;
2106  $this->clearInstanceCache( 'id' );
2107  }
2108 
2113  public function getName() : string {
2114  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2115  // Special case optimisation
2116  return $this->mName;
2117  }
2118 
2119  $this->load();
2120  if ( $this->mName === false ) {
2121  // Clean up IPs
2122  $this->mName = IPUtils::sanitizeIP( $this->getRequest()->getIP() );
2123  }
2124 
2125  return $this->mName;
2126  }
2127 
2141  public function setName( $str ) {
2142  $this->load();
2143  $this->mName = $str;
2144  }
2145 
2156  public function getActorId( $dbwOrWikiId = self::LOCAL ) : int {
2157  if ( $dbwOrWikiId instanceof IDatabase ) {
2158  wfDeprecatedMsg( 'Passing parameter of type IDatabase', '1.36' );
2159  } else {
2160  $this->assertWiki( $dbwOrWikiId );
2161  }
2162 
2163  if ( !$this->isItemLoaded( 'actor' ) ) {
2164  $this->load();
2165  }
2166 
2167  if ( !$this->mActorId && $dbwOrWikiId instanceof IDatabase ) {
2168  MediaWikiServices::getInstance()
2169  ->getActorStoreFactory()
2170  ->getActorNormalization( $dbwOrWikiId->getDomainID() )
2171  ->acquireActorId( $this, $dbwOrWikiId );
2172  // acquireActorId will call setActorId on $this
2173  Assert::postcondition(
2174  $this->mActorId !== null,
2175  "Failed to acquire actor ID for user id {$this->mId} name {$this->mName}"
2176  );
2177  }
2178 
2179  return (int)$this->mActorId;
2180  }
2181 
2192  public function setActorId( int $actorId ) {
2193  $this->mActorId = $actorId;
2194  $this->invalidateCache();
2195  $this->setItemLoaded( 'actor' );
2196  }
2197 
2202  public function getTitleKey() {
2203  return str_replace( ' ', '_', $this->getName() );
2204  }
2205 
2211  public function getNewtalk() {
2212  wfDeprecated( __METHOD__, '1.35' );
2213  return MediaWikiServices::getInstance()
2214  ->getTalkPageNotificationManager()
2215  ->userHasNewMessages( $this );
2216  }
2217 
2234  public function getNewMessageLinks() {
2235  wfDeprecated( __METHOD__, '1.35' );
2236  $talks = [];
2237  if ( !$this->getHookRunner()->onUserRetrieveNewTalks( $this, $talks ) ) {
2238  return $talks;
2239  }
2240 
2241  $services = MediaWikiServices::getInstance();
2242  $userHasNewMessages = $services->getTalkPageNotificationManager()
2243  ->userHasNewMessages( $this );
2244  if ( !$userHasNewMessages ) {
2245  return [];
2246  }
2247  $utp = $this->getTalkPage();
2248  $timestamp = $services->getTalkPageNotificationManager()
2249  ->getLatestSeenMessageTimestamp( $this );
2250  $rev = null;
2251  if ( $timestamp ) {
2252  $revRecord = $services->getRevisionLookup()
2253  ->getRevisionByTimestamp( $utp, $timestamp );
2254  if ( $revRecord ) {
2255  $rev = new Revision( $revRecord );
2256  }
2257  }
2258  return [
2259  [
2260  'wiki' => WikiMap::getCurrentWikiId(),
2261  'link' => $utp->getLocalURL(),
2262  'rev' => $rev
2263  ]
2264  ];
2265  }
2266 
2273  public function getNewMessageRevisionId() {
2274  wfDeprecated( __METHOD__, '1.35' );
2275  $newMessageRevisionId = null;
2276  $newMessageLinks = $this->getNewMessageLinks();
2277 
2278  // Note: getNewMessageLinks() never returns more than a single link
2279  // and it is always for the same wiki, but we double-check here in
2280  // case that changes some time in the future.
2281  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2282  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2283  && $newMessageLinks[0]['rev']
2284  ) {
2286  $newMessageRevision = $newMessageLinks[0]['rev'];
2287  $newMessageRevisionId = $newMessageRevision->getId();
2288  }
2289 
2290  return $newMessageRevisionId;
2291  }
2292 
2301  public function setNewtalk( $val, $curRev = null ) {
2302  wfDeprecated( __METHOD__, '1.35' );
2303  if ( $curRev && $curRev instanceof Revision ) {
2304  $curRev = $curRev->getRevisionRecord();
2305  }
2306  if ( $val ) {
2307  MediaWikiServices::getInstance()
2308  ->getTalkPageNotificationManager()
2309  ->setUserHasNewMessages( $this, $curRev );
2310  } else {
2311  MediaWikiServices::getInstance()
2312  ->getTalkPageNotificationManager()
2313  ->removeUserHasNewMessages( $this );
2314  }
2315  }
2316 
2323  private function newTouchedTimestamp() {
2324  $time = time();
2325  if ( $this->mTouched ) {
2326  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2327  }
2328 
2329  return wfTimestamp( TS_MW, $time );
2330  }
2331 
2342  public function clearSharedCache( $mode = 'refresh' ) {
2343  if ( !$this->getId() ) {
2344  return;
2345  }
2346 
2347  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2348  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2349  $key = $this->getCacheKey( $cache );
2350 
2351  if ( $mode === 'refresh' ) {
2352  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2353  } else {
2354  $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2355  static function () use ( $cache, $key ) {
2356  $cache->delete( $key );
2357  },
2358  __METHOD__
2359  );
2360  }
2361  }
2362 
2368  public function invalidateCache() {
2369  $this->touch();
2370  $this->clearSharedCache( 'changed' );
2371  }
2372 
2385  public function touch() {
2386  $id = $this->getId();
2387  if ( $id ) {
2388  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2389  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2390  $cache->touchCheckKey( $key );
2391  $this->mQuickTouched = null;
2392  }
2393  }
2394 
2400  public function validateCache( $timestamp ) {
2401  return ( $timestamp >= $this->getTouched() );
2402  }
2403 
2412  public function getTouched() {
2413  $this->load();
2414 
2415  if ( $this->mId ) {
2416  if ( $this->mQuickTouched === null ) {
2417  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2418  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2419 
2420  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2421  }
2422 
2423  return max( $this->mTouched, $this->mQuickTouched );
2424  }
2425 
2426  return $this->mTouched;
2427  }
2428 
2434  public function getDBTouched() {
2435  $this->load();
2436 
2437  return $this->mTouched;
2438  }
2439 
2452  public function changeAuthenticationData( array $data ) {
2453  $manager = MediaWikiServices::getInstance()->getAuthManager();
2454  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2455  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2456 
2457  $status = Status::newGood( 'ignored' );
2458  foreach ( $reqs as $req ) {
2459  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2460  }
2461  if ( $status->getValue() === 'ignored' ) {
2462  $status->warning( 'authenticationdatachange-ignored' );
2463  }
2464 
2465  if ( $status->isGood() ) {
2466  foreach ( $reqs as $req ) {
2467  $manager->changeAuthenticationData( $req );
2468  }
2469  }
2470  return $status;
2471  }
2472 
2479  public function getToken( $forceCreation = true ) {
2481 
2482  $this->load();
2483  if ( !$this->mToken && $forceCreation ) {
2484  $this->setToken();
2485  }
2486 
2487  if ( !$this->mToken ) {
2488  // The user doesn't have a token, return null to indicate that.
2489  return null;
2490  }
2491 
2492  if ( $this->mToken === self::INVALID_TOKEN ) {
2493  // We return a random value here so existing token checks are very
2494  // likely to fail.
2495  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2496  }
2497 
2498  if ( $wgAuthenticationTokenVersion === null ) {
2499  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2500  return $this->mToken;
2501  }
2502 
2503  // $wgAuthenticationTokenVersion in use, so hmac it.
2504  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2505 
2506  // The raw hash can be overly long. Shorten it up.
2507  $len = max( 32, self::TOKEN_LENGTH );
2508  if ( strlen( $ret ) < $len ) {
2509  // Should never happen, even md5 is 128 bits
2510  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2511  }
2512 
2513  return substr( $ret, -$len );
2514  }
2515 
2522  public function setToken( $token = false ) {
2523  $this->load();
2524  if ( $this->mToken === self::INVALID_TOKEN ) {
2526  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2527  } elseif ( !$token ) {
2528  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2529  } else {
2530  $this->mToken = $token;
2531  }
2532  }
2533 
2538  public function getEmail() {
2539  $this->load();
2540  $this->getHookRunner()->onUserGetEmail( $this, $this->mEmail );
2541  return $this->mEmail;
2542  }
2543 
2549  $this->load();
2550  $this->getHookRunner()->onUserGetEmailAuthenticationTimestamp(
2551  $this, $this->mEmailAuthenticated );
2553  }
2554 
2559  public function setEmail( $str ) {
2560  $this->load();
2561  if ( $str == $this->getEmail() ) {
2562  return;
2563  }
2564  $this->invalidateEmail();
2565  $this->mEmail = $str;
2566  $this->getHookRunner()->onUserSetEmail( $this, $this->mEmail );
2567  }
2568 
2576  public function setEmailWithConfirmation( $str ) {
2578 
2579  if ( !$wgEnableEmail ) {
2580  return Status::newFatal( 'emaildisabled' );
2581  }
2582 
2583  $oldaddr = $this->getEmail();
2584  if ( $str === $oldaddr ) {
2585  return Status::newGood( true );
2586  }
2587 
2588  $type = $oldaddr != '' ? 'changed' : 'set';
2589  $notificationResult = null;
2590 
2591  if ( $wgEmailAuthentication && $type === 'changed' ) {
2592  // Send the user an email notifying the user of the change in registered
2593  // email address on their previous email address
2594  $change = $str != '' ? 'changed' : 'removed';
2595  $notificationResult = $this->sendMail(
2596  wfMessage( 'notificationemail_subject_' . $change )->text(),
2597  wfMessage( 'notificationemail_body_' . $change,
2598  $this->getRequest()->getIP(),
2599  $this->getName(),
2600  $str )->text()
2601  );
2602  }
2603 
2604  $this->setEmail( $str );
2605 
2606  if ( $str !== '' && $wgEmailAuthentication ) {
2607  // Send a confirmation request to the new address if needed
2608  $result = $this->sendConfirmationMail( $type );
2609 
2610  if ( $notificationResult !== null ) {
2611  $result->merge( $notificationResult );
2612  }
2613 
2614  if ( $result->isGood() ) {
2615  // Say to the caller that a confirmation and notification mail has been sent
2616  $result->value = 'eauth';
2617  }
2618  } else {
2619  $result = Status::newGood( true );
2620  }
2621 
2622  return $result;
2623  }
2624 
2629  public function getRealName() {
2630  if ( !$this->isItemLoaded( 'realname' ) ) {
2631  $this->load();
2632  }
2633 
2634  return $this->mRealName;
2635  }
2636 
2641  public function setRealName( $str ) {
2642  $this->load();
2643  $this->mRealName = $str;
2644  }
2645 
2658  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2659  if ( $oname === null ) {
2660  return null; // b/c
2661  }
2662  return MediaWikiServices::getInstance()
2663  ->getUserOptionsLookup()
2664  ->getOption( $this, $oname, $defaultOverride, $ignoreHidden );
2665  }
2666 
2676  public function getOptions( $flags = 0 ) {
2677  return MediaWikiServices::getInstance()
2678  ->getUserOptionsLookup()
2679  ->getOptions( $this, $flags );
2680  }
2681 
2690  public function getBoolOption( $oname ) {
2691  return MediaWikiServices::getInstance()
2692  ->getUserOptionsLookup()
2693  ->getBoolOption( $this, $oname );
2694  }
2695 
2705  public function getIntOption( $oname, $defaultOverride = 0 ) {
2706  if ( $oname === null ) {
2707  return null; // b/c
2708  }
2709  return MediaWikiServices::getInstance()
2710  ->getUserOptionsLookup()
2711  ->getIntOption( $this, $oname, $defaultOverride );
2712  }
2713 
2723  public function setOption( $oname, $val ) {
2724  MediaWikiServices::getInstance()
2725  ->getUserOptionsManager()
2726  ->setOption( $this, $oname, $val );
2727  }
2728 
2739  public function getTokenFromOption( $oname ) {
2740  global $wgHiddenPrefs;
2741 
2742  $id = $this->getId();
2743  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2744  return false;
2745  }
2746 
2747  $token = $this->getOption( $oname );
2748  if ( !$token ) {
2749  // Default to a value based on the user token to avoid space
2750  // wasted on storing tokens for all users. When this option
2751  // is set manually by the user, only then is it stored.
2752  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2753  }
2754 
2755  return $token;
2756  }
2757 
2767  public function resetTokenFromOption( $oname ) {
2768  global $wgHiddenPrefs;
2769  if ( in_array( $oname, $wgHiddenPrefs ) ) {
2770  return false;
2771  }
2772 
2773  $token = MWCryptRand::generateHex( 40 );
2774  $this->setOption( $oname, $token );
2775  return $token;
2776  }
2777 
2802  public static function listOptionKinds() {
2803  return MediaWikiServices::getInstance()
2804  ->getUserOptionsManager()
2805  ->listOptionKinds();
2806  }
2807 
2821  public function getOptionKinds( IContextSource $context, $options = null ) {
2822  return MediaWikiServices::getInstance()
2823  ->getUserOptionsManager()
2824  ->getOptionKinds( $this, $context, $options );
2825  }
2826 
2842  public function resetOptions(
2843  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
2844  IContextSource $context = null
2845  ) {
2846  MediaWikiServices::getInstance()
2847  ->getUserOptionsManager()
2848  ->resetOptions(
2849  $this,
2850  $context ?? RequestContext::getMain(),
2851  $resetKinds
2852  );
2853  }
2854 
2859  public function getDatePreference() {
2860  // Important migration for old data rows
2861  if ( $this->mDatePreference === null ) {
2862  global $wgLang;
2863  $value = $this->getOption( 'date' );
2864  $map = $wgLang->getDatePreferenceMigrationMap();
2865  if ( isset( $map[$value] ) ) {
2866  $value = $map[$value];
2867  }
2868  $this->mDatePreference = $value;
2869  }
2870  return $this->mDatePreference;
2871  }
2872 
2879  public function requiresHTTPS() {
2880  global $wgForceHTTPS, $wgSecureLogin;
2881  if ( $wgForceHTTPS ) {
2882  return true;
2883  }
2884  if ( !$wgSecureLogin ) {
2885  return false;
2886  }
2887  $https = $this->getBoolOption( 'prefershttps' );
2888  $this->getHookRunner()->onUserRequiresHTTPS( $this, $https );
2889  if ( $https ) {
2890  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
2891  }
2892 
2893  return $https;
2894  }
2895 
2901  public function getStubThreshold() {
2902  global $wgMaxArticleSize; # Maximum article size, in Kb
2903  $threshold = $this->getIntOption( 'stubthreshold' );
2904  if ( $threshold > $wgMaxArticleSize * 1024 ) {
2905  // If they have set an impossible value, disable the preference
2906  // so we can use the parser cache again.
2907  $threshold = 0;
2908  }
2909  return $threshold;
2910  }
2911 
2920  public function getRights() {
2921  return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
2922  }
2923 
2932  public function getGroups() {
2933  return MediaWikiServices::getInstance()
2934  ->getUserGroupManager()
2935  ->getUserGroups( $this, $this->queryFlagsUsed );
2936  }
2937 
2947  public function getGroupMemberships() {
2948  return MediaWikiServices::getInstance()
2949  ->getUserGroupManager()
2950  ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
2951  }
2952 
2963  public function getEffectiveGroups( $recache = false ) {
2964  return MediaWikiServices::getInstance()
2965  ->getUserGroupManager()
2966  ->getUserEffectiveGroups( $this, $this->queryFlagsUsed, $recache );
2967  }
2968 
2979  public function getAutomaticGroups( $recache = false ) {
2980  return MediaWikiServices::getInstance()
2981  ->getUserGroupManager()
2982  ->getUserImplicitGroups( $this, $this->queryFlagsUsed, $recache );
2983  }
2984 
2996  public function getFormerGroups() {
2997  return MediaWikiServices::getInstance()
2998  ->getUserGroupManager()
2999  ->getUserFormerGroups( $this, $this->queryFlagsUsed );
3000  }
3001 
3006  public function getEditCount() {
3007  if ( !$this->getId() ) {
3008  return null;
3009  }
3010 
3011  if ( $this->mEditCount === null ) {
3012  $this->mEditCount = MediaWikiServices::getInstance()
3013  ->getUserEditTracker()
3014  ->getUserEditCount( $this );
3015  }
3016  return (int)$this->mEditCount;
3017  }
3018 
3032  public function addGroup( $group, $expiry = null ) {
3033  return MediaWikiServices::getInstance()
3034  ->getUserGroupManager()
3035  ->addUserToGroup( $this, $group, $expiry, true );
3036  }
3037 
3047  public function removeGroup( $group ) {
3048  return MediaWikiServices::getInstance()
3049  ->getUserGroupManager()
3050  ->removeUserFromGroup( $this, $group );
3051  }
3052 
3061  public function isRegistered() : bool {
3062  return $this->getId() != 0;
3063  }
3064 
3071  public function isLoggedIn() {
3072  return $this->isRegistered();
3073  }
3074 
3079  public function isAnon() {
3080  return !$this->isRegistered();
3081  }
3082 
3087  public function isBot() {
3088  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3089  return true;
3090  }
3091 
3092  $isBot = false;
3093  $this->getHookRunner()->onUserIsBot( $this, $isBot );
3094 
3095  return $isBot;
3096  }
3097 
3107  public function isSystemUser() {
3108  $this->load();
3109  if ( $this->getEmail() || $this->mToken !== self::INVALID_TOKEN ||
3110  MediaWikiServices::getInstance()->getAuthManager()->userCanAuthenticate( $this->mName )
3111  ) {
3112  return false;
3113  }
3114  return true;
3115  }
3116 
3117  public function isAllowedAny( ...$permissions ): bool {
3118  return $this->getThisAsAuthority()->isAllowedAny( ...$permissions );
3119  }
3120 
3121  public function isAllowedAll( ...$permissions ): bool {
3122  return $this->getThisAsAuthority()->isAllowedAll( ...$permissions );
3123  }
3124 
3125  public function isAllowed( string $permission ): bool {
3126  return $this->getThisAsAuthority()->isAllowed( $permission );
3127  }
3128 
3133  public function useRCPatrol() {
3134  global $wgUseRCPatrol;
3135  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3136  }
3137 
3142  public function useNPPatrol() {
3144  return (
3146  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3147  );
3148  }
3149 
3154  public function useFilePatrol() {
3156  return (
3158  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3159  );
3160  }
3161 
3167  public function getRequest() {
3168  if ( $this->mRequest ) {
3169  return $this->mRequest;
3170  }
3171  return RequestContext::getMain()->getRequest();
3172  }
3173 
3182  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3183  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3184  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3185  }
3186  return false;
3187  }
3188 
3199  public function isTempWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ): bool {
3200  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3201  return MediaWikiServices::getInstance()->getWatchedItemStore()
3202  ->isTempWatched( $this, $title );
3203  }
3204  return false;
3205  }
3206 
3216  public function addWatch(
3217  $title,
3218  $checkRights = self::CHECK_USER_RIGHTS,
3219  ?string $expiry = null
3220  ) {
3221  if ( !$title->isWatchable() ) {
3222  return;
3223  }
3224 
3225  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3226  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3227  $store->addWatch( $this, $title->getSubjectPage(), $expiry );
3228  if ( $title->canHaveTalkPage() ) {
3229  $store->addWatch( $this, $title->getTalkPage(), $expiry );
3230  }
3231  }
3232  $this->invalidateCache();
3233  }
3234 
3242  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3243  if ( !$title->isWatchable() ) {
3244  return;
3245  }
3246 
3247  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3248  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3249  $store->removeWatch( $this, $title->getSubjectPage() );
3250  if ( $title->canHaveTalkPage() ) {
3251  $store->removeWatch( $this, $title->getTalkPage() );
3252  }
3253  }
3254  $this->invalidateCache();
3255  }
3256 
3268  public function clearNotification( &$title, $oldid = 0 ) {
3269  MediaWikiServices::getInstance()
3270  ->getWatchlistNotificationManager()
3271  ->clearTitleUserNotifications( $this, $title, $oldid );
3272  }
3273 
3283  public function clearAllNotifications() {
3284  wfDeprecated( __METHOD__, '1.35' );
3285  MediaWikiServices::getInstance()
3286  ->getWatchlistNotificationManager()
3287  ->clearAllUserNotifications( $this );
3288  }
3289 
3295  public function getExperienceLevel() {
3296  global $wgLearnerEdits,
3300 
3301  if ( $this->isAnon() ) {
3302  return false;
3303  }
3304 
3305  $editCount = $this->getEditCount();
3306  $registration = $this->getRegistration();
3307  $now = time();
3308  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3309  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3310  if ( $registration === null ) {
3311  // for some very old accounts, this information is missing in the database
3312  // treat them as old enough to be 'experienced'
3313  $registration = $experiencedRegistration;
3314  }
3315 
3316  if ( $editCount < $wgLearnerEdits ||
3317  $registration > $learnerRegistration ) {
3318  return 'newcomer';
3319  }
3320 
3321  if ( $editCount > $wgExperiencedUserEdits &&
3322  $registration <= $experiencedRegistration
3323  ) {
3324  return 'experienced';
3325  }
3326 
3327  return 'learner';
3328  }
3329 
3338  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3339  $this->load();
3340  if ( $this->mId == 0 ) {
3341  return;
3342  }
3343 
3344  $session = $this->getRequest()->getSession();
3345  if ( $request && $session->getRequest() !== $request ) {
3346  $session = $session->sessionWithRequest( $request );
3347  }
3348  $delay = $session->delaySave();
3349 
3350  if ( !$session->getUser()->equals( $this ) ) {
3351  if ( !$session->canSetUser() ) {
3353  ->warning( __METHOD__ .
3354  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3355  );
3356  return;
3357  }
3358  $session->setUser( $this );
3359  }
3360 
3361  $session->setRememberUser( $rememberMe );
3362  if ( $secure !== null ) {
3363  $session->setForceHTTPS( $secure );
3364  }
3365 
3366  $session->persist();
3367 
3368  ScopedCallback::consume( $delay );
3369  }
3370 
3374  public function logout() {
3375  // Avoid PHP 7.1 warning of passing $this by reference
3376  $user = $this;
3377  if ( $this->getHookRunner()->onUserLogout( $user ) ) {
3378  $this->doLogout();
3379  }
3380  }
3381 
3386  public function doLogout() {
3387  $session = $this->getRequest()->getSession();
3388  if ( !$session->canSetUser() ) {
3390  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3391  $error = 'immutable';
3392  } elseif ( !$session->getUser()->equals( $this ) ) {
3394  ->warning( __METHOD__ .
3395  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3396  );
3397  // But we still may as well make this user object anon
3398  $this->clearInstanceCache( 'defaults' );
3399  $error = 'wronguser';
3400  } else {
3401  $this->clearInstanceCache( 'defaults' );
3402  $delay = $session->delaySave();
3403  $session->unpersist(); // Clear cookies (T127436)
3404  $session->setLoggedOutTimestamp( time() );
3405  $session->setUser( new User );
3406  $session->set( 'wsUserID', 0 ); // Other code expects this
3407  $session->resetAllTokens();
3408  ScopedCallback::consume( $delay );
3409  $error = false;
3410  }
3411  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3412  'event' => 'logout',
3413  'successful' => $error === false,
3414  'status' => $error ?: 'success',
3415  ] );
3416  }
3417 
3422  public function saveSettings() {
3423  if ( wfReadOnly() ) {
3424  // @TODO: caller should deal with this instead!
3425  // This should really just be an exception.
3427  null,
3428  "Could not update user with ID '{$this->mId}'; DB is read-only."
3429  ) );
3430  return;
3431  }
3432 
3433  $this->load();
3434  if ( $this->mId == 0 ) {
3435  return; // anon
3436  }
3437 
3438  // Get a new user_touched that is higher than the old one.
3439  // This will be used for a CAS check as a last-resort safety
3440  // check against race conditions and replica DB lag.
3441  $newTouched = $this->newTouchedTimestamp();
3442 
3443  $dbw = wfGetDB( DB_MASTER );
3444  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
3445  $dbw->update( 'user',
3446  [ /* SET */
3447  'user_name' => $this->mName,
3448  'user_real_name' => $this->mRealName,
3449  'user_email' => $this->mEmail,
3450  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3451  'user_touched' => $dbw->timestamp( $newTouched ),
3452  'user_token' => strval( $this->mToken ),
3453  'user_email_token' => $this->mEmailToken,
3454  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3455  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
3456  'user_id' => $this->mId,
3457  ] ), $fname
3458  );
3459 
3460  if ( !$dbw->affectedRows() ) {
3461  // Maybe the problem was a missed cache update; clear it to be safe
3462  $this->clearSharedCache( 'refresh' );
3463  // User was changed in the meantime or loaded with stale data
3464  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
3465  LoggerFactory::getInstance( 'preferences' )->warning(
3466  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
3467  [ 'user_id' => $this->mId, 'db_flag' => $from ]
3468  );
3469  throw new MWException( "CAS update failed on user_touched. " .
3470  "The version of the user to be saved is older than the current version."
3471  );
3472  }
3473 
3474  $dbw->update(
3475  'actor',
3476  [ 'actor_name' => $this->mName ],
3477  [ 'actor_user' => $this->mId ],
3478  $fname
3479  );
3480  } );
3481 
3482  $this->mTouched = $newTouched;
3483  MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3484 
3485  $this->getHookRunner()->onUserSaveSettings( $this );
3486  $this->clearSharedCache( 'changed' );
3487  $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3488  $hcu->purgeTitleUrls( $this->getUserPage(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
3489  }
3490 
3497  public function idForName( $flags = self::READ_NORMAL ) {
3498  $s = trim( $this->getName() );
3499  if ( $s === '' ) {
3500  return 0;
3501  }
3502 
3503  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
3504  $db = wfGetDB( $index );
3505 
3506  $id = $db->selectField( 'user',
3507  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
3508 
3509  return (int)$id;
3510  }
3511 
3527  public static function createNew( $name, $params = [] ) {
3528  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
3529  if ( isset( $params[$field] ) ) {
3530  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
3531  unset( $params[$field] );
3532  }
3533  }
3534 
3535  $user = new User;
3536  $user->load();
3537  $user->setToken(); // init token
3538  if ( isset( $params['options'] ) ) {
3539  MediaWikiServices::getInstance()
3540  ->getUserOptionsManager()
3541  ->loadUserOptions( $user, $user->queryFlagsUsed, $params['options'] );
3542  unset( $params['options'] );
3543  }
3544  $dbw = wfGetDB( DB_MASTER );
3545 
3546  $noPass = PasswordFactory::newInvalidPassword()->toString();
3547 
3548  $fields = [
3549  'user_name' => $name,
3550  'user_password' => $noPass,
3551  'user_newpassword' => $noPass,
3552  'user_email' => $user->mEmail,
3553  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3554  'user_real_name' => $user->mRealName,
3555  'user_token' => strval( $user->mToken ),
3556  'user_registration' => $dbw->timestamp( $user->mRegistration ),
3557  'user_editcount' => 0,
3558  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3559  ];
3560  foreach ( $params as $name => $value ) {
3561  $fields["user_$name"] = $value;
3562  }
3563 
3564  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
3565  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
3566  if ( $dbw->affectedRows() ) {
3567  $newUser = self::newFromId( $dbw->insertId() );
3568  $newUser->mName = $fields['user_name'];
3569  $newUser->updateActorId( $dbw );
3570  // Load the user from master to avoid replica lag
3571  $newUser->load( self::READ_LATEST );
3572  } else {
3573  $newUser = null;
3574  }
3575  return $newUser;
3576  } );
3577  }
3578 
3605  public function addToDatabase() {
3606  $this->load();
3607  if ( !$this->mToken ) {
3608  $this->setToken(); // init token
3609  }
3610 
3611  if ( !is_string( $this->mName ) ) {
3612  throw new RuntimeException( "User name field is not set." );
3613  }
3614 
3615  $this->mTouched = $this->newTouchedTimestamp();
3616 
3617  $dbw = wfGetDB( DB_MASTER );
3618  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
3619  $noPass = PasswordFactory::newInvalidPassword()->toString();
3620  $dbw->insert( 'user',
3621  [
3622  'user_name' => $this->mName,
3623  'user_password' => $noPass,
3624  'user_newpassword' => $noPass,
3625  'user_email' => $this->mEmail,
3626  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3627  'user_real_name' => $this->mRealName,
3628  'user_token' => strval( $this->mToken ),
3629  'user_registration' => $dbw->timestamp( $this->mRegistration ),
3630  'user_editcount' => 0,
3631  'user_touched' => $dbw->timestamp( $this->mTouched ),
3632  ], $fname,
3633  [ 'IGNORE' ]
3634  );
3635  if ( !$dbw->affectedRows() ) {
3636  // Use locking reads to bypass any REPEATABLE-READ snapshot.
3637  $this->mId = $dbw->selectField(
3638  'user',
3639  'user_id',
3640  [ 'user_name' => $this->mName ],
3641  $fname,
3642  [ 'LOCK IN SHARE MODE' ]
3643  );
3644  $loaded = false;
3645  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
3646  $loaded = true;
3647  }
3648  if ( !$loaded ) {
3649  throw new MWException( $fname . ": hit a key conflict attempting " .
3650  "to insert user '{$this->mName}' row, but it was not present in select!" );
3651  }
3652  return Status::newFatal( 'userexists' );
3653  }
3654  $this->mId = $dbw->insertId();
3655  self::$idCacheByName[$this->mName] = $this->mId;
3656  $this->updateActorId( $dbw );
3657 
3658  return Status::newGood();
3659  } );
3660  if ( !$status->isGood() ) {
3661  return $status;
3662  }
3663 
3664  // Clear instance cache other than user table data and actor, which is already accurate
3665  $this->clearInstanceCache();
3666 
3667  MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3668  return Status::newGood();
3669  }
3670 
3675  private function updateActorId( IDatabase $dbw ) {
3676  $dbw->insert(
3677  'actor',
3678  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
3679  __METHOD__
3680  );
3681  $this->mActorId = (int)$dbw->insertId();
3682  }
3683 
3689  public function spreadAnyEditBlock() {
3690  if ( $this->isRegistered() && $this->getBlock() ) {
3691  return $this->spreadBlock();
3692  }
3693 
3694  return false;
3695  }
3696 
3702  protected function spreadBlock() {
3703  wfDebug( __METHOD__ . "()" );
3704  $this->load();
3705  if ( $this->mId == 0 ) {
3706  return false;
3707  }
3708 
3709  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
3710  if ( !$userblock ) {
3711  return false;
3712  }
3713 
3714  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
3715  }
3716 
3721  public function isBlockedFromCreateAccount() {
3722  $this->getBlockedStatus();
3723  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
3724  return $this->mBlock;
3725  }
3726 
3727  # T15611: if the IP address the user is trying to create an account from is
3728  # blocked with createaccount disabled, prevent new account creation there even
3729  # when the user is logged in
3730  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
3731  $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
3732  null, $this->getRequest()->getIP()
3733  );
3734  }
3735  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
3736  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
3737  ? $this->mBlockedFromCreateAccount
3738  : false;
3739  }
3740 
3745  public function isBlockedFromEmailuser() {
3746  $this->getBlockedStatus();
3747  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
3748  }
3749 
3756  public function isBlockedFromUpload() {
3757  $this->getBlockedStatus();
3758  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
3759  }
3760 
3765  public function isAllowedToCreateAccount() {
3766  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
3767  }
3768 
3774  public function getUserPage() {
3775  return Title::makeTitle( NS_USER, $this->getName() );
3776  }
3777 
3783  public function getTalkPage() {
3784  $title = $this->getUserPage();
3785  return $title->getTalkPage();
3786  }
3787 
3793  public function isNewbie() {
3794  return !$this->isAllowed( 'autoconfirmed' );
3795  }
3796 
3808  public function getEditTokenObject( $salt = '', $request = null ) {
3809  if ( $this->isAnon() ) {
3810  return new LoggedOutEditToken();
3811  }
3812 
3813  if ( !$request ) {
3814  $request = $this->getRequest();
3815  }
3816  return $request->getSession()->getToken( $salt );
3817  }
3818 
3832  public function getEditToken( $salt = '', $request = null ) {
3833  return $this->getEditTokenObject( $salt, $request )->toString();
3834  }
3835 
3848  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
3849  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
3850  }
3851 
3862  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
3863  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
3864  return $this->matchEditToken( $val, $salt, $request, $maxage );
3865  }
3866 
3874  public function sendConfirmationMail( $type = 'created' ) {
3875  global $wgLang;
3876  $expiration = null; // gets passed-by-ref and defined in next line.
3877  $token = $this->confirmationToken( $expiration );
3878  $url = $this->confirmationTokenUrl( $token );
3879  $invalidateURL = $this->invalidationTokenUrl( $token );
3880  $this->saveSettings();
3881 
3882  if ( $type == 'created' || $type === false ) {
3883  $message = 'confirmemail_body';
3884  $type = 'created';
3885  } elseif ( $type === true ) {
3886  $message = 'confirmemail_body_changed';
3887  $type = 'changed';
3888  } else {
3889  // Messages: confirmemail_body_changed, confirmemail_body_set
3890  $message = 'confirmemail_body_' . $type;
3891  }
3892 
3893  $mail = [
3894  'subject' => wfMessage( 'confirmemail_subject' )->text(),
3895  'body' => wfMessage( $message,
3896  $this->getRequest()->getIP(),
3897  $this->getName(),
3898  $url,
3899  $wgLang->userTimeAndDate( $expiration, $this ),
3900  $invalidateURL,
3901  $wgLang->userDate( $expiration, $this ),
3902  $wgLang->userTime( $expiration, $this ) )->text(),
3903  'from' => null,
3904  'replyTo' => null,
3905  ];
3906  $info = [
3907  'type' => $type,
3908  'ip' => $this->getRequest()->getIP(),
3909  'confirmURL' => $url,
3910  'invalidateURL' => $invalidateURL,
3911  'expiration' => $expiration
3912  ];
3913 
3914  $this->getHookRunner()->onUserSendConfirmationMail( $this, $mail, $info );
3915  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
3916  }
3917 
3929  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
3930  global $wgPasswordSender;
3931 
3932  if ( $from instanceof User ) {
3933  $sender = MailAddress::newFromUser( $from );
3934  } else {
3935  $sender = new MailAddress( $wgPasswordSender,
3936  wfMessage( 'emailsender' )->inContentLanguage()->text() );
3937  }
3938  $to = MailAddress::newFromUser( $this );
3939 
3940  return UserMailer::send( $to, $sender, $subject, $body, [
3941  'replyTo' => $replyto,
3942  ] );
3943  }
3944 
3955  protected function confirmationToken( &$expiration ) {
3957  $now = time();
3958  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
3959  $expiration = wfTimestamp( TS_MW, $expires );
3960  $this->load();
3961  $token = MWCryptRand::generateHex( 32 );
3962  $hash = md5( $token );
3963  $this->mEmailToken = $hash;
3964  $this->mEmailTokenExpires = $expiration;
3965  return $token;
3966  }
3967 
3973  protected function confirmationTokenUrl( $token ) {
3974  return $this->getTokenUrl( 'ConfirmEmail', $token );
3975  }
3976 
3982  protected function invalidationTokenUrl( $token ) {
3983  return $this->getTokenUrl( 'InvalidateEmail', $token );
3984  }
3985 
4000  protected function getTokenUrl( $page, $token ) {
4001  // Hack to bypass localization of 'Special:'
4002  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4003  return $title->getCanonicalURL();
4004  }
4005 
4013  public function confirmEmail() {
4014  // Check if it's already confirmed, so we don't touch the database
4015  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4016  if ( !$this->isEmailConfirmed() ) {
4018  $this->getHookRunner()->onConfirmEmailComplete( $this );
4019  }
4020  return true;
4021  }
4022 
4030  public function invalidateEmail() {
4031  $this->load();
4032  $this->mEmailToken = null;
4033  $this->mEmailTokenExpires = null;
4034  $this->setEmailAuthenticationTimestamp( null );
4035  $this->mEmail = '';
4036  $this->getHookRunner()->onInvalidateEmailComplete( $this );
4037  return true;
4038  }
4039 
4044  public function setEmailAuthenticationTimestamp( $timestamp ) {
4045  $this->load();
4046  $this->mEmailAuthenticated = $timestamp;
4047  $this->getHookRunner()->onUserSetEmailAuthenticationTimestamp(
4048  $this, $this->mEmailAuthenticated );
4049  }
4050 
4056  public function canSendEmail() {
4058  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4059  return false;
4060  }
4061  $canSend = $this->isEmailConfirmed();
4062  $this->getHookRunner()->onUserCanSendEmail( $this, $canSend );
4063  return $canSend;
4064  }
4065 
4071  public function canReceiveEmail() {
4072  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4073  }
4074 
4085  public function isEmailConfirmed() {
4086  global $wgEmailAuthentication;
4087  $this->load();
4088  // Avoid PHP 7.1 warning of passing $this by reference
4089  $user = $this;
4090  $confirmed = true;
4091  if ( $this->getHookRunner()->onEmailConfirmed( $user, $confirmed ) ) {
4092  if ( $this->isAnon() ) {
4093  return false;
4094  }
4095  if ( !Sanitizer::validateEmail( $this->getEmail() ) ) {
4096  return false;
4097  }
4098  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4099  return false;
4100  }
4101  return true;
4102  }
4103 
4104  return $confirmed;
4105  }
4106 
4111  public function isEmailConfirmationPending() {
4112  global $wgEmailAuthentication;
4113  return $wgEmailAuthentication &&
4114  !$this->isEmailConfirmed() &&
4115  $this->mEmailToken &&
4116  $this->mEmailTokenExpires > wfTimestamp();
4117  }
4118 
4126  public function getRegistration() {
4127  if ( $this->isAnon() ) {
4128  return false;
4129  }
4130  $this->load();
4131  return $this->mRegistration;
4132  }
4133 
4140  public function getFirstEditTimestamp() {
4141  return MediaWikiServices::getInstance()
4142  ->getUserEditTracker()
4143  ->getFirstEditTimestamp( $this );
4144  }
4145 
4153  public function getLatestEditTimestamp() {
4154  return MediaWikiServices::getInstance()
4155  ->getUserEditTracker()
4156  ->getLatestEditTimestamp( $this );
4157  }
4158 
4168  public static function getGroupPermissions( $groups ) {
4169  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4170  }
4171 
4181  public static function getGroupsWithPermission( $role ) {
4182  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4183  }
4184 
4200  public static function groupHasPermission( $group, $role ) {
4201  return MediaWikiServices::getInstance()->getPermissionManager()
4202  ->groupHasPermission( $group, $role );
4203  }
4204 
4212  public static function getAllGroups() {
4213  return MediaWikiServices::getInstance()
4214  ->getUserGroupManager()
4215  ->listAllGroups();
4216  }
4217 
4222  public static function getImplicitGroups() {
4223  return MediaWikiServices::getInstance()
4224  ->getUserGroupManager()
4225  ->listAllImplicitGroups();
4226  }
4227 
4238  public static function changeableByGroup( $group ) {
4240 
4241  $groups = [
4242  'add' => [],
4243  'remove' => [],
4244  'add-self' => [],
4245  'remove-self' => []
4246  ];
4247 
4248  if ( empty( $wgAddGroups[$group] ) ) {
4249  // Don't add anything to $groups
4250  } elseif ( $wgAddGroups[$group] === true ) {
4251  // You get everything
4252  $groups['add'] = self::getAllGroups();
4253  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4254  $groups['add'] = $wgAddGroups[$group];
4255  }
4256 
4257  // Same thing for remove
4258  if ( empty( $wgRemoveGroups[$group] ) ) {
4259  // Do nothing
4260  } elseif ( $wgRemoveGroups[$group] === true ) {
4261  $groups['remove'] = self::getAllGroups();
4262  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4263  $groups['remove'] = $wgRemoveGroups[$group];
4264  }
4265 
4266  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4267  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4268  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4269  if ( is_int( $key ) ) {
4270  $wgGroupsAddToSelf['user'][] = $value;
4271  }
4272  }
4273  }
4274 
4275  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4276  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4277  if ( is_int( $key ) ) {
4278  $wgGroupsRemoveFromSelf['user'][] = $value;
4279  }
4280  }
4281  }
4282 
4283  // Now figure out what groups the user can add to him/herself
4284  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4285  // Do nothing
4286  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4287  // No idea WHY this would be used, but it's there
4288  $groups['add-self'] = self::getAllGroups();
4289  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4290  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4291  }
4292 
4293  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4294  // Do nothing
4295  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4296  $groups['remove-self'] = self::getAllGroups();
4297  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4298  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4299  }
4300 
4301  return $groups;
4302  }
4303 
4311  public function changeableGroups() {
4312  if ( $this->isAllowed( 'userrights' ) ) {
4313  // This group gives the right to modify everything (reverse-
4314  // compatibility with old "userrights lets you change
4315  // everything")
4316  // Using array_merge to make the groups reindexed
4317  $all = array_merge( self::getAllGroups() );
4318  return [
4319  'add' => $all,
4320  'remove' => $all,
4321  'add-self' => [],
4322  'remove-self' => []
4323  ];
4324  }
4325 
4326  // Okay, it's not so simple, we will have to go through the arrays
4327  $groups = [
4328  'add' => [],
4329  'remove' => [],
4330  'add-self' => [],
4331  'remove-self' => []
4332  ];
4333  $addergroups = $this->getEffectiveGroups();
4334 
4335  foreach ( $addergroups as $addergroup ) {
4336  $groups = array_merge_recursive(
4337  $groups, $this->changeableByGroup( $addergroup )
4338  );
4339  $groups['add'] = array_unique( $groups['add'] );
4340  $groups['remove'] = array_unique( $groups['remove'] );
4341  $groups['add-self'] = array_unique( $groups['add-self'] );
4342  $groups['remove-self'] = array_unique( $groups['remove-self'] );
4343  }
4344  return $groups;
4345  }
4346 
4350  public function incEditCount() {
4351  if ( $this->isAnon() ) {
4352  return; // sanity
4353  }
4354 
4356  new UserEditCountUpdate( $this, 1 ),
4357  DeferredUpdates::POSTSEND
4358  );
4359  }
4360 
4366  public function setEditCountInternal( $count ) {
4367  $this->mEditCount = $count;
4368  }
4369 
4377  public function initEditCountInternal( IDatabase $dbr ) {
4378  return MediaWikiServices::getInstance()
4379  ->getUserEditTracker()
4380  ->initializeUserEditCount( $this );
4381  }
4382 
4390  public static function getRightDescription( $right ) {
4391  $key = "right-$right";
4392  $msg = wfMessage( $key );
4393  return $msg->isDisabled() ? $right : $msg->text();
4394  }
4395 
4404  public static function getGrantName( $grant ) {
4405  wfDeprecated( __METHOD__, '1.36' );
4406  return MWGrants::grantName( $grant );
4407  }
4408 
4418  public static function getQueryInfo() {
4419  $ret = [
4420  'tables' => [ 'user', 'user_actor' => 'actor' ],
4421  'fields' => [
4422  'user_id',
4423  'user_name',
4424  'user_real_name',
4425  'user_email',
4426  'user_touched',
4427  'user_token',
4428  'user_email_authenticated',
4429  'user_email_token',
4430  'user_email_token_expires',
4431  'user_registration',
4432  'user_editcount',
4433  'user_actor.actor_id',
4434  ],
4435  'joins' => [
4436  'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
4437  ],
4438  ];
4439 
4440  return $ret;
4441  }
4442 
4450  public static function newFatalPermissionDeniedStatus( $permission ) {
4451  global $wgLang;
4452 
4453  $groups = [];
4454  foreach ( MediaWikiServices::getInstance()
4455  ->getPermissionManager()
4456  ->getGroupsWithPermission( $permission ) as $group ) {
4457  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
4458  }
4459 
4460  if ( $groups ) {
4461  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
4462  }
4463 
4464  return Status::newFatal( 'badaccess-group0' );
4465  }
4466 
4476  public function getInstanceForUpdate() {
4477  if ( !$this->getId() ) {
4478  return null; // anon
4479  }
4480 
4481  $user = self::newFromId( $this->getId() );
4482  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
4483  return null;
4484  }
4485 
4486  return $user;
4487  }
4488 
4496  public function equals( UserIdentity $user ) : bool {
4497  // XXX it's not clear whether central ID providers are supposed to obey this
4498  return $this->getName() === $user->getName();
4499  }
4500 
4506  public function isAllowUsertalk() {
4507  return $this->mAllowUsertalk;
4508  }
4509 
4514  public function getPerformer(): UserIdentity {
4515  return $this;
4516  }
4517 
4525  public function probablyCan( string $action, PageIdentity $target, PermissionStatus $status = null ): bool {
4526  return $this->getThisAsAuthority()->probablyCan( $action, $target, $status );
4527  }
4528 
4536  public function definitelyCan( string $action, PageIdentity $target, PermissionStatus $status = null ): bool {
4537  return $this->getThisAsAuthority()->definitelyCan( $action, $target, $status );
4538  }
4539 
4547  public function authorizeRead( string $action, PageIdentity $target, PermissionStatus $status = null
4548  ): bool {
4549  return $this->getThisAsAuthority()->authorizeRead( $action, $target, $status );
4550  }
4551 
4559  public function authorizeWrite( string $action, PageIdentity $target, PermissionStatus $status = null ): bool {
4560  return $this->getThisAsAuthority()->authorizeWrite( $action, $target, $status );
4561  }
4562 
4568  private function getThisAsAuthority(): Authority {
4569  if ( !$this->mThisAsAuthority ) {
4570  // TODO: For users that are not User::isGlobalSessionUser,
4571  // creating a UserAuthority here is incorrect, since it depends
4572  // on global WebRequest, but that is what we've used to do before Authority.
4573  // When PermissionManager is refactored into Authority, we need
4574  // to provide base implementation, based on just user groups/rights,
4575  // and use it here.
4576  $this->mThisAsAuthority = new UserAuthority(
4577  $this,
4578  MediaWikiServices::getInstance()->getPermissionManager()
4579  );
4580  }
4581  return $this->mThisAsAuthority;
4582  }
4583 
4588  private function isGlobalSessionUser(): bool {
4589  // The session user is set up towards the end of Setup.php. Until then,
4590  // assume it's a logged-out user.
4591  $sessionUser = RequestContext::getMain()->getUser();
4592  $globalUserName = $sessionUser->isSafeToLoad()
4593  ? $sessionUser->getName()
4594  : IPUtils::sanitizeIP( $sessionUser->getRequest()->getIP() );
4595 
4596  return $this->getName() === $globalUserName;
4597  }
4598 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1594
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=self::READ_NORMAL)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:701
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:5432
Page\PageIdentity
Interface for objects (potentially) representing an editable wiki page.
Definition: PageIdentity.php:65
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:446
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:358
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2211
MediaWiki\DAO\WikiAwareEntityTrait
trait WikiAwareEntityTrait
Definition: WikiAwareEntityTrait.php:32
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:50
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:623
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:3973
User\__set
__set( $name, $value)
Definition: User.php:297
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
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:2760
User\$mToken
string $mToken
Definition: User.php:151
User\$mBlockedby
string int $mBlockedby
Definition: User.php:194
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:272
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1109
User\getId
getId()
Get the user's ID.
Definition: User.php:2067
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3154
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2488
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:3079
User\$mBlockedFromCreateAccount
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:229
User\$mBlockreason
string $mBlockreason
TODO: This should be removed when User::BlockedFor and AbstractBlock::getReason are hard deprecated.
Definition: User.php:202
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4000
User\isRegistered
isRegistered()
Get whether the user is registered.
Definition: User.php:3061
User\$mCacheVars
static string[] $mCacheVars
List of member variables which are saved to the shared cache (memcached).
Definition: User.php:110
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:2767
User\isBlockedFrom
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:1952
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1456
WikiMap\isCurrentWikiId
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:321
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:172
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:4450
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:3808
User\isBot
isBot()
Definition: User.php:3087
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:2323
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9512
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3006
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:3702
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:87
User\incEditCount
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition: User.php:4350
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:714
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:2821
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4111
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:1981
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2705
true
return true
Definition: router.php:90
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:2676
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:147
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:255
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1832
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2479
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:3689
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2273
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:226
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1949
MediaWiki\Permissions\UserAuthority
Represents the authority of a given User.
Definition: UserAuthority.php:42
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Definition: DeferredUpdates.php:119
User\setEmailWithConfirmation
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:2576
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1837
User\$mHideName
bool $mHideName
Definition: User.php:213
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:2901
Sanitizer\validateEmail
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1708
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1135
User\$mLocked
bool $mLocked
Definition: User.php:206
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:584
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1348
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1231
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4044
User\$mEmail
string $mEmail
Definition: User.php:145
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:3774
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:2932
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:655
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1094
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3142
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:2859
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:341
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:2559
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:1028
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:3874
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4200
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2548
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:1692
User\getActorId
getActorId( $dbwOrWikiId=self::LOCAL)
Get the user's actor ID.
Definition: User.php:2156
UserEditCountUpdate
Handles increment the edit count for a given set of users.
Definition: UserEditCountUpdate.php:29
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:57
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3133
WikiMap\getCurrentWikiId
static getCurrentWikiId()
Definition: WikiMap.php:303
User\invalidateEmail
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4030
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:736
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:7433
User\$mHash
string $mHash
Definition: User.php:196
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:7449
$wgLang
$wgLang
Definition: Setup.php:784
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:35
User\isIPRange
isIPRange()
Is the user an IP range?
Definition: User.php:1012
User\probablyCan
probablyCan(string $action, PageIdentity $target, PermissionStatus $status=null)
@unstable this is a part of the Authority experiment and should not be used yet.
Definition: User.php:4525
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:70
User\getRights
getRights()
Get the permissions this user has.
Definition: User.php:2920
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:3527
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3167
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
User\getWikiId
getWikiId()
Returns self::LOCAL to indicate the user is associated with the local wiki.
Definition: User.php:265
User\getAutomaticGroups
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:2979
User\INVALID_TOKEN
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:78
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1581
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:4476
$dbr
$dbr
Definition: testCompression.php:54
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Specify the difference engine to use.
Definition: DefaultSettings.php:9511
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:777
Wikimedia\Rdbms\IDatabase\update
update( $table, $set, $conds, $fname=__METHOD__, $options=[])
Update all rows in a table that match a given condition.
Revision
Definition: Revision.php:40
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3032
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:34
MWExceptionHandler\logException
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).
Definition: MWExceptionHandler.php:666
LoggedOutEditToken
Value object representing a logged-out user's edit token.
Definition: LoggedOutEditToken.php:37
User\matchEditToken
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:3848
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2538
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:50
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:3783
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:3605
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:2368
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1066
MWException
MediaWiki exception.
Definition: MWException.php:29
User\invalidationTokenUrl
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:3982
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2039
User\$mDatePreference
string $mDatePreference
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:187
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:5470
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1034
User\authorizeWrite
authorizeWrite(string $action, PageIdentity $target, PermissionStatus $status=null)
@unstable this is a part of the Authority experiment and should not be used yet.
Definition: User.php:4559
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:1135
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4013
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1259
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:121
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:1972
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2301
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2467
$wgFullyInitialised
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:838
User\isTempWatched
isTempWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check if the article is temporarily watched.
Definition: User.php:3199
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:3765
User\logout
logout()
Log this user out.
Definition: User.php:3374
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:3955
User\initEditCountInternal
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition: User.php:4377
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:487
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1848
User\getImplicitGroups
static getImplicitGroups()
Definition: User.php:4222
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2054
User\definitelyCan
definitelyCan(string $action, PageIdentity $target, PermissionStatus $status=null)
@unstable this is a part of the Authority experiment and should not be used yet.
Definition: User.php:4536
MWGrants\grantName
static grantName( $grant, $lang=null)
Fetch the display name of the grant.
Definition: MWGrants.php:57
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:3745
User\$mBlock
AbstractBlock null $mBlock
Definition: User.php:223
User\loadDefaults
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition: User.php:1211
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:1000
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2400
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:2963
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1665
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3047
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4071
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:3793
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2385
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:38
User\clearInstanceCache
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1550
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1831
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:96
User\TOKEN_LENGTH
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:73
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:626
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1861
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:149
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:682
User\setName
setName( $str)
Set the user name.
Definition: User.php:2141
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:142
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:2842
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:52
User\getBlockedStatus
getBlockedStatus( $fromReplica=true, $disableIpBlockExemptChecking=false)
Get blocking information.
Definition: User.php:1611
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:7460
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:915
User\__get
& __get( $name)
Definition: User.php:276
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:6045
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3268
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:3422
User\isAllowedAll
isAllowedAll(... $permissions)
Checks whether this authority has any of the given permissions in general.
Definition: User.php:3121
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4140
User\authorizeRead
authorizeRead(string $action, PageIdentity $target, PermissionStatus $status=null)
@unstable this is a part of the Authority experiment and should not be used yet.
Definition: User.php:4547
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:91
MediaWiki\Permissions\Authority
@unstable
Definition: Authority.php:30
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:2641
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2234
User\getBlock
getBlock( $fromReplica=true, $disableIpBlockExemptChecking=false)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1936
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:652
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:2996
MediaWiki\DAO\WikiAwareEntity\assertWiki
assertWiki( $wikiId)
Throws if $wikiId is different from the return value of getWikiId().
$s
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
Definition: mergeMessageFileList.php:188
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:916
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\isAllowedAny
isAllowedAny(... $permissions)
Checks whether this authority has any of the given permissions in general.
Definition: User.php:3117
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1270
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:6210
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:1994
$wgForceHTTPS
bool $wgForceHTTPS
If this is true, when an insecure HTTP request is received, always redirect to HTTPS.
Definition: DefaultSettings.php:165
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2658
User\idForName
idForName( $flags=self::READ_NORMAL)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:3497
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
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:2412
User\$mId
int $mId
Cache variables.
Definition: User.php:132
User\isSystemUser
isSystemUser()
Get whether the user is a system user.
Definition: User.php:3107
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2008
User\__toString
__toString()
Definition: User.php:272
MediaWiki\Block\SystemBlock
System blocks are temporary blocks that are created on enforcement (e.g.
Definition: SystemBlock.php:33
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:53
User\getPerformer
getPerformer()
@unstable this is a part of the Authority experiment and should not be used yet.
Definition: User.php:4514
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1512
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:125
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:2629
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:3675
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:3338
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4168
User\getUserId
getUserId( $wikiId=self::LOCAL)
Definition: User.php:2095
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4311
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3283
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition: User.php:3216
User\VERSION
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:84
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:3862
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:6040
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4212
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3242
NS_USER
const NS_USER
Definition: Defines.php:66
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:3756
User\setActorId
setActorId(int $actorId)
Sets the actor id.
Definition: User.php:2192
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:476
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:4418
MediaWiki\User\UserOptionsLookup
Provides access to user options.
Definition: UserOptionsLookup.php:29
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:1961
User\$mLoadedItems
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:168
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:9509
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2434
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $rows, $fname=__METHOD__, $options=[])
Insert the given row(s) into a table.
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:93
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:57
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:42
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:34
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4238
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2104
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3295
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1881
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4126
User\isAllowed
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
Definition: User.php:3125
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:242
MediaWiki\Block\AbstractBlock\appliesToRight
appliesToRight( $right)
Determine whether the block prevents a given right.
Definition: AbstractBlock.php:291
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:4390
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2452
$cache
$cache
Definition: mcc.php:33
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:6292
User\isLoggedIn
isLoggedIn()
Get whether the user is registered.
Definition: User.php:3071
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:34
User\$mActorId
int null $mActorId
Switched from protected to public for use in UserFactory.
Definition: User.php:140
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2202
User\purge
static purge( $dbDomain, $userId)
Definition: User.php:476
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2522
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:936
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:978
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:157
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1058
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:1179
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4085
User\$mGlobalBlock
AbstractBlock $mGlobalBlock
Definition: User.php:204
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:4506
User\$idCacheByName
static int[] $idCacheByName
Definition: User.php:238
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:4404
UserCache\singleton
static singleton()
Definition: UserCache.php:48
User\clearSharedCache
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2342
User\isBlocked
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:1924
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:638
User\$mThisAsAuthority
Authority null $mThisAsAuthority
lazy-initialized Authority of this user
Definition: User.php:235
$keys
$keys
Definition: testCompression.php:72
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:510
$wgLearnerMemberSince
$wgLearnerMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9510
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1478
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:3929
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:2739
User\$mEmailToken
string $mEmailToken
Definition: User.php:155
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:5824
User\equals
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:4496
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user data from the database.
Definition: User.php:1296
User\$mName
string $mName
Definition: User.php:134
User\$mRegistration
string $mRegistration
Definition: User.php:159
MediaWiki\Block\AbstractBlock
Definition: AbstractBlock.php:37
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for the wiki.
Definition: DefaultSettings.php:4978
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:101
User\$mRequest
WebRequest $mRequest
Definition: User.php:216
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
CentralIdLookup\AUDIENCE_RAW
const AUDIENCE_RAW
Definition: CentralIdLookup.php:34
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3182
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:2690
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:5819
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:1044
User\makeUpdateConditions
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1493
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1817
User\__sleep
__sleep()
Definition: User.php:318
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:3721
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:3832
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:2879
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1247
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:66
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:2723
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:232
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:926
ExternalUserNames\isExternal
static isExternal( $username)
Tells whether the username is external or not.
Definition: ExternalUserNames.php:147
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2113
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:5458
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3386
MediaWiki\User\UserFactory
Creates User objects.
Definition: UserFactory.php:41
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:2947
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4056
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1099
Wikimedia\Rdbms\IDatabase\delete
delete( $table, $conds, $fname=__METHOD__)
Delete all rows in a table that match a condition.
User\isGlobalSessionUser
isGlobalSessionUser()
Check whether this is the global session user.
Definition: User.php:4588
CentralIdLookup\factoryNonLocal
static factoryNonLocal()
Returns a CentralIdLookup that is guaranteed to be non-local.
Definition: CentralIdLookup.php:83
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:498
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:38
User\getLatestEditTimestamp
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4153
User\setEditCountInternal
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:4366
User\$mEditCount
int $mEditCount
Definition: User.php:161
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:153
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4181
User\$mFrom
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:181
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:2802
User\getThisAsAuthority
getThisAsAuthority()
Returns the Authority of this User if it's the main request context user.
Definition: User.php:4568
$type
$type
Definition: testCompression.php:52