MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
28 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
37 use Wikimedia\IPUtils;
38 use Wikimedia\NonSerializable\NonSerializableTrait;
42 use Wikimedia\ScopedCallback;
43 
56 class User implements IDBAccessObject, UserIdentity {
57  use ProtectedHookAccessorTrait;
58  use NonSerializableTrait;
59 
63  public const TOKEN_LENGTH = 32;
64 
68  public const INVALID_TOKEN = '*** INVALID ***';
69 
74  private const VERSION = 16;
75 
81  public const GETOPTIONS_EXCLUDE_DEFAULTS = UserOptionsLookup::EXCLUDE_DEFAULTS;
82 
86  public const CHECK_USER_RIGHTS = true;
87 
91  public const IGNORE_USER_RIGHTS = false;
92 
100  protected static $mCacheVars = [
101  // user table
102  'mId',
103  'mName',
104  'mRealName',
105  'mEmail',
106  'mTouched',
107  'mToken',
108  'mEmailAuthenticated',
109  'mEmailToken',
110  'mEmailTokenExpires',
111  'mRegistration',
112  'mEditCount',
113  // actor table
114  'mActorId',
115  ];
116 
118  // Some of these are public, including for use by the UserFactory, but they generally
119  // should not be set manually
120  // @{
122  public $mId;
124  public $mName;
126  protected $mActorId;
128  public $mRealName;
129 
131  public $mEmail;
133  public $mTouched;
135  protected $mQuickTouched;
137  protected $mToken;
141  protected $mEmailToken;
145  protected $mRegistration;
147  protected $mEditCount;
148  // @}
149 
150  // @{
154  protected $mLoadedItems = [];
155  // @}
156 
167  public $mFrom;
168 
173  protected $mDatePreference;
180  public $mBlockedby;
182  protected $mHash;
188  protected $mBlockreason;
190  protected $mGlobalBlock;
192  protected $mLocked;
199  public $mHideName;
200 
202  private $mRequest;
203 
209  public $mBlock;
210 
212  protected $mAllowUsertalk;
213 
215  private $mBlockedFromCreateAccount = false;
216 
218  protected $queryFlagsUsed = self::READ_NORMAL;
219 
221  public static $idCacheByName = [];
222 
238  public function __construct() {
239  $this->clearInstanceCache( 'defaults' );
240  }
241 
245  public function __toString() {
246  return (string)$this->getName();
247  }
248 
249  public function &__get( $name ) {
250  // A shortcut for $mRights deprecation phase
251  if ( $name === 'mRights' ) {
252  $copy = $this->getRights();
253  return $copy;
254  } elseif ( $name === 'mOptions' ) {
255  wfDeprecated( 'User::$mOptions', '1.35' );
256  $options = $this->getOptions();
257  return $options;
258  } elseif ( !property_exists( $this, $name ) ) {
259  // T227688 - do not break $u->foo['bar'] = 1
260  wfLogWarning( 'tried to get non-existent property' );
261  $this->$name = null;
262  return $this->$name;
263  } else {
264  wfLogWarning( 'tried to get non-visible property' );
265  $null = null;
266  return $null;
267  }
268  }
269 
270  public function __set( $name, $value ) {
271  // A shortcut for $mRights deprecation phase, only known legitimate use was for
272  // testing purposes, other uses seem bad in principle
273  if ( $name === 'mRights' ) {
274  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
275  $this,
276  $value === null ? [] : $value
277  );
278  } elseif ( $name === 'mOptions' ) {
279  wfDeprecated( 'User::$mOptions', '1.35' );
280  MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $this );
281  foreach ( $value as $key => $val ) {
282  $this->setOption( $key, $val );
283  }
284  } elseif ( !property_exists( $this, $name ) ) {
285  $this->$name = $value;
286  } else {
287  wfLogWarning( 'tried to set non-visible property' );
288  }
289  }
290 
305  public function isSafeToLoad() {
306  global $wgFullyInitialised;
307 
308  // The user is safe to load if:
309  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
310  // * mLoadedItems === true (already loaded)
311  // * mFrom !== 'session' (sessions not involved at all)
312 
313  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
314  $this->mLoadedItems === true || $this->mFrom !== 'session';
315  }
316 
322  public function load( $flags = self::READ_NORMAL ) {
323  global $wgFullyInitialised;
324 
325  if ( $this->mLoadedItems === true ) {
326  return;
327  }
328 
329  // Set it now to avoid infinite recursion in accessors
330  $oldLoadedItems = $this->mLoadedItems;
331  $this->mLoadedItems = true;
332  $this->queryFlagsUsed = $flags;
333 
334  // If this is called too early, things are likely to break.
335  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
337  ->warning( 'User::loadFromSession called before the end of Setup.php', [
338  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
339  ] );
340  $this->loadDefaults();
341  $this->mLoadedItems = $oldLoadedItems;
342  return;
343  }
344 
345  switch ( $this->mFrom ) {
346  case 'defaults':
347  $this->loadDefaults();
348  break;
349  case 'id':
350  // Make sure this thread sees its own changes, if the ID isn't 0
351  if ( $this->mId != 0 ) {
352  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
353  if ( $lb->hasOrMadeRecentMasterChanges() ) {
354  $flags |= self::READ_LATEST;
355  $this->queryFlagsUsed = $flags;
356  }
357  }
358 
359  $this->loadFromId( $flags );
360  break;
361  case 'actor':
362  case 'name':
363  // Make sure this thread sees its own changes
364  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
365  if ( $lb->hasOrMadeRecentMasterChanges() ) {
366  $flags |= self::READ_LATEST;
367  $this->queryFlagsUsed = $flags;
368  }
369 
370  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
371  $row = wfGetDB( $index )->selectRow(
372  'actor',
373  [ 'actor_id', 'actor_user', 'actor_name' ],
374  $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
375  __METHOD__,
376  $options
377  );
378 
379  if ( !$row ) {
380  // Ugh.
381  $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
382  } elseif ( $row->actor_user ) {
383  $this->mId = $row->actor_user;
384  $this->loadFromId( $flags );
385  } else {
386  $this->loadDefaults( $row->actor_name, $row->actor_id );
387  }
388  break;
389  case 'session':
390  if ( !$this->loadFromSession() ) {
391  // Loading from session failed. Load defaults.
392  $this->loadDefaults();
393  }
394  $this->getHookRunner()->onUserLoadAfterLoadFromSession( $this );
395  break;
396  default:
397  throw new UnexpectedValueException(
398  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
399  }
400  }
401 
407  public function loadFromId( $flags = self::READ_NORMAL ) {
408  if ( $this->mId == 0 ) {
409  // Anonymous users are not in the database (don't need cache)
410  $this->loadDefaults();
411  return false;
412  }
413 
414  // Try cache (unless this needs data from the master DB).
415  // NOTE: if this thread called saveSettings(), the cache was cleared.
416  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
417  if ( $latest ) {
418  if ( !$this->loadFromDatabase( $flags ) ) {
419  // Can't load from ID
420  return false;
421  }
422  } else {
423  $this->loadFromCache();
424  }
425 
426  $this->mLoadedItems = true;
427  $this->queryFlagsUsed = $flags;
428 
429  return true;
430  }
431 
437  public static function purge( $dbDomain, $userId ) {
438  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
439  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
440  $cache->delete( $key );
441  }
442 
448  protected function getCacheKey( WANObjectCache $cache ) {
449  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
450 
451  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
452  }
453 
460  $id = $this->getId();
461 
462  return $id ? [ $this->getCacheKey( $cache ) ] : [];
463  }
464 
471  protected function loadFromCache() {
472  global $wgFullyInitialised;
473 
474  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
475  $data = $cache->getWithSetCallback(
476  $this->getCacheKey( $cache ),
477  $cache::TTL_HOUR,
478  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache, $wgFullyInitialised ) {
479  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
480  wfDebug( "User: cache miss for user {$this->mId}" );
481 
482  $this->loadFromDatabase( self::READ_NORMAL );
483 
484  $data = [];
485  foreach ( self::$mCacheVars as $name ) {
486  $data[$name] = $this->$name;
487  }
488 
489  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
490 
491  if ( $wgFullyInitialised ) {
492  $groupMemberships = MediaWikiServices::getInstance()
493  ->getUserGroupManager()
494  ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
495 
496  // if a user group membership is about to expire, the cache needs to
497  // expire at that time (T163691)
498  foreach ( $groupMemberships as $ugm ) {
499  if ( $ugm->getExpiry() ) {
500  $secondsUntilExpiry =
501  wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
502 
503  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
504  $ttl = $secondsUntilExpiry;
505  }
506  }
507  }
508  }
509 
510  return $data;
511  },
512  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
513  );
514 
515  // Restore from cache
516  foreach ( self::$mCacheVars as $name ) {
517  $this->$name = $data[$name];
518  }
519 
520  return true;
521  }
522 
524  // @{
525 
542  public static function newFromName( $name, $validate = 'valid' ) {
543  // Backwards compatibility with strings / false
544  $validationLevels = [
545  'valid' => UserNameUtils::RIGOR_VALID,
546  'usable' => UserNameUtils::RIGOR_USABLE,
547  'creatable' => UserNameUtils::RIGOR_CREATABLE
548  ];
549  if ( $validate === true ) {
550  $validate = 'valid';
551  }
552  if ( $validate === false ) {
553  $validation = UserNameUtils::RIGOR_NONE;
554  } elseif ( array_key_exists( $validate, $validationLevels ) ) {
555  $validation = $validationLevels[ $validate ];
556  } else {
557  // Not a recognized value, probably a test for unsupported validation
558  // levels, regardless, just pass it along
559  $validation = $validate;
560  }
561 
562  $user = MediaWikiServices::getInstance()
563  ->getUserFactory()
564  ->newFromName( (string)$name, $validation );
565 
566  // UserFactory returns null instead of false
567  if ( $user === null ) {
568  $user = false;
569  }
570  return $user;
571  }
572 
579  public static function newFromId( $id ) {
580  $u = new User;
581  $u->mId = $id;
582  $u->mFrom = 'id';
583  $u->setItemLoaded( 'id' );
584  return $u;
585  }
586 
594  public static function newFromActorId( $id ) {
595  $u = new User;
596  $u->mActorId = $id;
597  $u->mFrom = 'actor';
598  $u->setItemLoaded( 'actor' );
599  return $u;
600  }
601 
611  public static function newFromIdentity( UserIdentity $identity ) {
612  return MediaWikiServices::getInstance()
613  ->getUserFactory()
614  ->newFromUserIdentity( $identity );
615  }
616 
630  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
631  // Stop-gap solution for the problem described in T222212.
632  // Force the User ID and Actor ID to zero for users loaded from the database
633  // of another wiki, to prevent subtle data corruption and confusing failure modes.
634  if ( $dbDomain !== false ) {
635  $userId = 0;
636  $actorId = 0;
637  }
638 
639  $user = new User;
640  $user->mFrom = 'defaults';
641 
642  if ( $actorId !== null ) {
643  $user->mActorId = (int)$actorId;
644  if ( $user->mActorId !== 0 ) {
645  $user->mFrom = 'actor';
646  }
647  $user->setItemLoaded( 'actor' );
648  }
649 
650  if ( $userName !== null && $userName !== '' ) {
651  $user->mName = $userName;
652  $user->mFrom = 'name';
653  $user->setItemLoaded( 'name' );
654  }
655 
656  if ( $userId !== null ) {
657  $user->mId = (int)$userId;
658  if ( $user->mId !== 0 ) {
659  $user->mFrom = 'id';
660  }
661  $user->setItemLoaded( 'id' );
662  }
663 
664  if ( $user->mFrom === 'defaults' ) {
665  throw new InvalidArgumentException(
666  'Cannot create a user with no name, no ID, and no actor ID'
667  );
668  }
669 
670  return $user;
671  }
672 
684  public static function newFromConfirmationCode( $code, $flags = self::READ_NORMAL ) {
685  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
686  $db = wfGetDB( $index );
687 
688  $id = $db->selectField(
689  'user',
690  'user_id',
691  [
692  'user_email_token' => md5( $code ),
693  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
694  ],
695  __METHOD__,
696  $options
697  );
698 
699  return $id ? self::newFromId( $id ) : null;
700  }
701 
709  public static function newFromSession( WebRequest $request = null ) {
710  $user = new User;
711  $user->mFrom = 'session';
712  $user->mRequest = $request;
713  return $user;
714  }
715 
731  public static function newFromRow( $row, $data = null ) {
732  $user = new User;
733  $user->loadFromRow( $row, $data );
734  return $user;
735  }
736 
772  public static function newSystemUser( $name, $options = [] ) {
773  $options += [
774  'validate' => 'valid',
775  'create' => true,
776  'steal' => false,
777  ];
778 
779  $name = self::getCanonicalName( $name, $options['validate'] );
780  if ( $name === false ) {
781  return null;
782  }
783 
784  $dbr = wfGetDB( DB_REPLICA );
785  $userQuery = self::getQueryInfo();
786  $row = $dbr->selectRow(
787  $userQuery['tables'],
788  $userQuery['fields'],
789  [ 'user_name' => $name ],
790  __METHOD__,
791  [],
792  $userQuery['joins']
793  );
794  if ( !$row ) {
795  // Try the master database...
796  $dbw = wfGetDB( DB_MASTER );
797  $row = $dbw->selectRow(
798  $userQuery['tables'],
799  $userQuery['fields'],
800  [ 'user_name' => $name ],
801  __METHOD__,
802  [],
803  $userQuery['joins']
804  );
805  }
806 
807  if ( !$row ) {
808  // No user. Create it?
809  // @phan-suppress-next-line PhanImpossibleCondition
810  if ( !$options['create'] ) {
811  // No.
812  return null;
813  }
814 
815  // If it's a reserved user that had an anonymous actor created for it at
816  // some point, we need special handling.
817  if ( !self::isValidUserName( $name ) || self::isUsableName( $name ) ) {
818  // Not reserved, so just create it.
819  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
820  }
821 
822  // It is reserved. Check for an anonymous actor row.
823  $dbw = wfGetDB( DB_MASTER );
824  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $name ) {
825  $row = $dbw->selectRow(
826  'actor',
827  [ 'actor_id' ],
828  [ 'actor_name' => $name, 'actor_user' => null ],
829  $fname,
830  [ 'FOR UPDATE' ]
831  );
832  if ( !$row ) {
833  // No anonymous actor.
834  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
835  }
836 
837  // There is an anonymous actor. Delete the actor row so we can create the user,
838  // then restore the old actor_id so as to not break existing references.
839  // @todo If MediaWiki ever starts using foreign keys for `actor`, this will break things.
840  $dbw->delete( 'actor', [ 'actor_id' => $row->actor_id ], $fname );
841  $user = self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
842  $dbw->update(
843  'actor',
844  [ 'actor_id' => $row->actor_id ],
845  [ 'actor_id' => $user->getActorId() ],
846  $fname
847  );
848  $user->clearInstanceCache( 'id' );
849  $user->invalidateCache();
850  return $user;
851  } );
852  }
853 
854  $user = self::newFromRow( $row );
855 
856  if ( !$user->isSystemUser() ) {
857  // User exists. Steal it?
858  // @phan-suppress-next-line PhanRedundantCondition
859  if ( !$options['steal'] ) {
860  return null;
861  }
862 
863  MediaWikiServices::getInstance()->getAuthManager()->revokeAccessForUser( $name );
864 
865  $user->invalidateEmail();
866  $user->mToken = self::INVALID_TOKEN;
867  $user->saveSettings();
868  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
869  }
870 
871  return $user;
872  }
873 
874  // @}
875 
881  public static function whoIs( $id ) {
882  return UserCache::singleton()->getProp( $id, 'name' );
883  }
884 
891  public static function whoIsReal( $id ) {
892  return UserCache::singleton()->getProp( $id, 'real_name' );
893  }
894 
901  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
902  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
903  $name = (string)$name;
904  $nt = Title::makeTitleSafe( NS_USER, $name );
905  if ( $nt === null ) {
906  // Illegal name
907  return null;
908  }
909 
910  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
911  return self::$idCacheByName[$name] === null ? null : (int)self::$idCacheByName[$name];
912  }
913 
914  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
915  $db = wfGetDB( $index );
916 
917  $s = $db->selectRow(
918  'user',
919  [ 'user_id' ],
920  [ 'user_name' => $nt->getText() ],
921  __METHOD__,
922  $options
923  );
924 
925  if ( $s === false ) {
926  $result = null;
927  } else {
928  $result = (int)$s->user_id;
929  }
930 
931  if ( count( self::$idCacheByName ) >= 1000 ) {
932  self::$idCacheByName = [];
933  }
934 
935  self::$idCacheByName[$name] = $result;
936 
937  return $result;
938  }
939 
943  public static function resetIdByNameCache() {
944  self::$idCacheByName = [];
945  }
946 
965  public static function isIP( $name ) {
966  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
967  || IPUtils::isIPv6( $name );
968  }
969 
977  public function isIPRange() {
978  return IPUtils::isValidRange( $this->mName );
979  }
980 
993  public static function isValidUserName( $name ) {
994  return MediaWikiServices::getInstance()->getUserNameUtils()->isValid( $name );
995  }
996 
1009  public static function isUsableName( $name ) {
1010  return MediaWikiServices::getInstance()->getUserNameUtils()->isUsable( $name );
1011  }
1012 
1023  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1024  if ( $groups === [] ) {
1025  return UserArrayFromResult::newFromIDs( [] );
1026  }
1027 
1028  $groups = array_unique( (array)$groups );
1029  $limit = min( 5000, $limit );
1030 
1031  $conds = [ 'ug_group' => $groups ];
1032  if ( $after !== null ) {
1033  $conds[] = 'ug_user > ' . (int)$after;
1034  }
1035 
1036  $dbr = wfGetDB( DB_REPLICA );
1037  $ids = $dbr->selectFieldValues(
1038  'user_groups',
1039  'ug_user',
1040  $conds,
1041  __METHOD__,
1042  [
1043  'DISTINCT' => true,
1044  'ORDER BY' => 'ug_user',
1045  'LIMIT' => $limit,
1046  ]
1047  ) ?: [];
1048  return UserArray::newFromIDs( $ids );
1049  }
1050 
1064  public static function isCreatableName( $name ) {
1065  return MediaWikiServices::getInstance()->getUserNameUtils()->isCreatable( $name );
1066  }
1067 
1074  public function isValidPassword( $password ) {
1075  // simple boolean wrapper for checkPasswordValidity
1076  return $this->checkPasswordValidity( $password )->isGood();
1077  }
1078 
1100  public function checkPasswordValidity( $password ) {
1101  global $wgPasswordPolicy;
1102 
1103  $upp = new UserPasswordPolicy(
1104  $wgPasswordPolicy['policies'],
1105  $wgPasswordPolicy['checks']
1106  );
1107 
1108  $status = Status::newGood( [] );
1109  $result = false; // init $result to false for the internal checks
1110 
1111  if ( !$this->getHookRunner()->onIsValidPassword( $password, $result, $this ) ) {
1112  $status->error( $result );
1113  return $status;
1114  }
1115 
1116  if ( $result === false ) {
1117  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1118  return $status;
1119  }
1120 
1121  if ( $result === true ) {
1122  return $status;
1123  }
1124 
1125  $status->error( $result );
1126  return $status; // the isValidPassword hook set a string $result and returned true
1127  }
1128 
1144  public static function getCanonicalName( $name, $validate = 'valid' ) {
1145  // Backwards compatibility with strings / false
1146  $validationLevels = [
1147  'valid' => UserNameUtils::RIGOR_VALID,
1148  'usable' => UserNameUtils::RIGOR_USABLE,
1149  'creatable' => UserNameUtils::RIGOR_CREATABLE
1150  ];
1151 
1152  if ( $validate === false ) {
1153  $validation = UserNameUtils::RIGOR_NONE;
1154  } elseif ( array_key_exists( $validate, $validationLevels ) ) {
1155  $validation = $validationLevels[ $validate ];
1156  } else {
1157  // Not a recognized value, probably a test for unsupported validation
1158  // levels, regardless, just pass it along
1159  $validation = $validate;
1160  }
1161 
1162  return MediaWikiServices::getInstance()
1163  ->getUserNameUtils()
1164  ->getCanonical( (string)$name, $validation );
1165  }
1166 
1176  public function loadDefaults( $name = false, $actorId = null ) {
1177  $this->mId = 0;
1178  $this->mName = $name;
1179  $this->mActorId = $actorId;
1180  $this->mRealName = '';
1181  $this->mEmail = '';
1182 
1183  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1184  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1185  if ( $loggedOut !== 0 ) {
1186  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1187  } else {
1188  $this->mTouched = '1'; # Allow any pages to be cached
1189  }
1190 
1191  $this->mToken = null; // Don't run cryptographic functions till we need a token
1192  $this->mEmailAuthenticated = null;
1193  $this->mEmailToken = '';
1194  $this->mEmailTokenExpires = null;
1195  $this->mRegistration = wfTimestamp( TS_MW );
1196 
1197  $this->getHookRunner()->onUserLoadDefaults( $this, $name );
1198  }
1199 
1212  public function isItemLoaded( $item, $all = 'all' ) {
1213  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1214  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1215  }
1216 
1224  public function setItemLoaded( $item ) {
1225  if ( is_array( $this->mLoadedItems ) ) {
1226  $this->mLoadedItems[$item] = true;
1227  }
1228  }
1229 
1235  private function loadFromSession() {
1236  // MediaWiki\Session\Session already did the necessary authentication of the user
1237  // returned here, so just use it if applicable.
1238  $session = $this->getRequest()->getSession();
1239  $user = $session->getUser();
1240  if ( $user->isLoggedIn() ) {
1241  $this->loadFromUserObject( $user );
1242 
1243  // Other code expects these to be set in the session, so set them.
1244  $session->set( 'wsUserID', $this->getId() );
1245  $session->set( 'wsUserName', $this->getName() );
1246  $session->set( 'wsToken', $this->getToken() );
1247 
1248  return true;
1249  }
1250 
1251  return false;
1252  }
1253 
1261  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1262  // Paranoia
1263  $this->mId = intval( $this->mId );
1264 
1265  if ( !$this->mId ) {
1266  // Anonymous users are not in the database
1267  $this->loadDefaults();
1268  return false;
1269  }
1270 
1271  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1272  $db = wfGetDB( $index );
1273 
1274  $userQuery = self::getQueryInfo();
1275  $s = $db->selectRow(
1276  $userQuery['tables'],
1277  $userQuery['fields'],
1278  [ 'user_id' => $this->mId ],
1279  __METHOD__,
1280  $options,
1281  $userQuery['joins']
1282  );
1283 
1284  $this->queryFlagsUsed = $flags;
1285  $this->getHookRunner()->onUserLoadFromDatabase( $this, $s );
1286 
1287  if ( $s !== false ) {
1288  // Initialise user table data
1289  $this->loadFromRow( $s );
1290  $this->getEditCount(); // revalidation for nulls
1291  return true;
1292  }
1293 
1294  // Invalid user_id
1295  $this->mId = 0;
1296  $this->loadDefaults( 'Unknown user' );
1297 
1298  return false;
1299  }
1300 
1313  protected function loadFromRow( $row, $data = null ) {
1314  if ( !is_object( $row ) ) {
1315  throw new InvalidArgumentException( '$row must be an object' );
1316  }
1317 
1318  $all = true;
1319 
1320  if ( isset( $row->actor_id ) ) {
1321  $this->mActorId = (int)$row->actor_id;
1322  if ( $this->mActorId !== 0 ) {
1323  $this->mFrom = 'actor';
1324  }
1325  $this->setItemLoaded( 'actor' );
1326  } else {
1327  $all = false;
1328  }
1329 
1330  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1331  $this->mName = $row->user_name;
1332  $this->mFrom = 'name';
1333  $this->setItemLoaded( 'name' );
1334  } else {
1335  $all = false;
1336  }
1337 
1338  if ( isset( $row->user_real_name ) ) {
1339  $this->mRealName = $row->user_real_name;
1340  $this->setItemLoaded( 'realname' );
1341  } else {
1342  $all = false;
1343  }
1344 
1345  if ( isset( $row->user_id ) ) {
1346  $this->mId = intval( $row->user_id );
1347  if ( $this->mId !== 0 ) {
1348  $this->mFrom = 'id';
1349  }
1350  $this->setItemLoaded( 'id' );
1351  } else {
1352  $all = false;
1353  }
1354 
1355  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1356  self::$idCacheByName[$row->user_name] = $row->user_id;
1357  }
1358 
1359  if ( isset( $row->user_editcount ) ) {
1360  $this->mEditCount = $row->user_editcount;
1361  } else {
1362  $all = false;
1363  }
1364 
1365  if ( isset( $row->user_touched ) ) {
1366  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1367  } else {
1368  $all = false;
1369  }
1370 
1371  if ( isset( $row->user_token ) ) {
1372  // The definition for the column is binary(32), so trim the NULs
1373  // that appends. The previous definition was char(32), so trim
1374  // spaces too.
1375  $this->mToken = rtrim( $row->user_token, " \0" );
1376  if ( $this->mToken === '' ) {
1377  $this->mToken = null;
1378  }
1379  } else {
1380  $all = false;
1381  }
1382 
1383  if ( isset( $row->user_email ) ) {
1384  $this->mEmail = $row->user_email;
1385  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1386  $this->mEmailToken = $row->user_email_token;
1387  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1388  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1389  } else {
1390  $all = false;
1391  }
1392 
1393  if ( $all ) {
1394  $this->mLoadedItems = true;
1395  }
1396 
1397  if ( is_array( $data ) ) {
1398 
1399  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1400  MediaWikiServices::getInstance()
1401  ->getUserGroupManager()
1402  ->loadGroupMembershipsFromArray(
1403  $this,
1404  $data['user_groups'],
1405  $this->queryFlagsUsed
1406  );
1407  }
1408  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1409  MediaWikiServices::getInstance()
1410  ->getUserOptionsManager()
1411  ->loadUserOptions( $this, $this->queryFlagsUsed, $data['user_properties'] );
1412  }
1413  }
1414  }
1415 
1421  protected function loadFromUserObject( $user ) {
1422  $user->load();
1423  foreach ( self::$mCacheVars as $var ) {
1424  $this->$var = $user->$var;
1425  }
1426  }
1427 
1443  public function addAutopromoteOnceGroups( $event ) {
1444  return MediaWikiServices::getInstance()
1445  ->getUserGroupManager()
1446  ->addUserToAutopromoteOnceGroups( $this, $event );
1447  }
1448 
1458  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1459  if ( $this->mTouched ) {
1460  // CAS check: only update if the row wasn't changed sicne it was loaded.
1461  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1462  }
1463 
1464  return $conditions;
1465  }
1466 
1477  public function checkAndSetTouched() {
1478  $this->load();
1479 
1480  if ( !$this->mId ) {
1481  return false; // anon
1482  }
1483 
1484  // Get a new user_touched that is higher than the old one
1485  $newTouched = $this->newTouchedTimestamp();
1486 
1487  $dbw = wfGetDB( DB_MASTER );
1488  $dbw->update( 'user',
1489  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1490  $this->makeUpdateConditions( $dbw, [
1491  'user_id' => $this->mId,
1492  ] ),
1493  __METHOD__
1494  );
1495  $success = ( $dbw->affectedRows() > 0 );
1496 
1497  if ( $success ) {
1498  $this->mTouched = $newTouched;
1499  $this->clearSharedCache( 'changed' );
1500  } else {
1501  // Clears on failure too since that is desired if the cache is stale
1502  $this->clearSharedCache( 'refresh' );
1503  }
1504 
1505  return $success;
1506  }
1507 
1515  public function clearInstanceCache( $reloadFrom = false ) {
1516  global $wgFullyInitialised;
1517 
1518  $this->mDatePreference = null;
1519  $this->mBlockedby = -1; # Unset
1520  $this->mHash = false;
1521  $this->mEditCount = null;
1522 
1523  if ( $wgFullyInitialised && $this->mFrom ) {
1524  $services = MediaWikiServices::getInstance();
1525  $services->getPermissionManager()->invalidateUsersRightsCache( $this );
1526  $services->getUserOptionsManager()->clearUserOptionsCache( $this );
1527  $services->getTalkPageNotificationManager()->clearInstanceCache( $this );
1528  $services->getUserGroupManager()->clearCache( $this );
1529  $services->getUserEditTracker()->clearUserEditCache( $this );
1530  }
1531 
1532  if ( $reloadFrom ) {
1533  $this->mLoadedItems = [];
1534  $this->mFrom = $reloadFrom;
1535  }
1536  }
1537 
1545  public static function getDefaultOptions() {
1546  return MediaWikiServices::getInstance()
1547  ->getUserOptionsLookup()
1548  ->getDefaultOptions();
1549  }
1550 
1558  public static function getDefaultOption( $opt ) {
1559  return MediaWikiServices::getInstance()
1560  ->getUserOptionsLookup()
1561  ->getDefaultOption( $opt );
1562  }
1563 
1573  private function getBlockedStatus( $fromReplica = true ) {
1574  if ( $this->mBlockedby != -1 ) {
1575  return;
1576  }
1577 
1578  wfDebug( __METHOD__ . ": checking blocked status for " . $this->getName() );
1579 
1580  // Initialize data...
1581  // Otherwise something ends up stomping on $this->mBlockedby when
1582  // things get lazy-loaded later, causing false positive block hits
1583  // due to -1 !== 0. Probably session-related... Nothing should be
1584  // overwriting mBlockedby, surely?
1585  $this->load();
1586 
1587  // TODO: Block checking shouldn't really be done from the User object. Block
1588  // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1589  // which need more knowledge of the request context than the User should have.
1590  // Since we do currently check blocks from the User, we have to do the following
1591  // here:
1592  // - Check if this is the user associated with the main request
1593  // - If so, pass the relevant request information to the block manager
1594  $request = null;
1595 
1596  // The session user is set up towards the end of Setup.php. Until then,
1597  // assume it's a logged-out user.
1598  $sessionUser = RequestContext::getMain()->getUser();
1599  $globalUserName = $sessionUser->isSafeToLoad()
1600  ? $sessionUser->getName()
1601  : IPUtils::sanitizeIP( $sessionUser->getRequest()->getIP() );
1602 
1603  if ( $this->getName() === $globalUserName ) {
1604  // This is the global user, so we need to pass the request
1605  $request = $this->getRequest();
1606  }
1607 
1608  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1609  $this,
1610  $request,
1611  $fromReplica
1612  );
1613 
1614  if ( $block ) {
1615  $this->mBlock = $block;
1616  $this->mBlockedby = $block->getByName();
1617  $this->mBlockreason = $block->getReason();
1618  $this->mHideName = $block->getHideName();
1619  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1620  } else {
1621  $this->mBlock = null;
1622  $this->mBlockedby = '';
1623  $this->mBlockreason = '';
1624  $this->mHideName = 0;
1625  $this->mAllowUsertalk = false;
1626  }
1627  }
1628 
1634  public function isPingLimitable() {
1635  global $wgRateLimitsExcludedIPs;
1636  if ( IPUtils::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1637  // No other good way currently to disable rate limits
1638  // for specific IPs. :P
1639  // But this is a crappy hack and should die.
1640  return false;
1641  }
1642  return !$this->isAllowed( 'noratelimit' );
1643  }
1644 
1661  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1662  $logger = \MediaWiki\Logger\LoggerFactory::getInstance( 'ratelimit' );
1663 
1664  // Call the 'PingLimiter' hook
1665  $result = false;
1666  if ( !$this->getHookRunner()->onPingLimiter( $this, $action, $result, $incrBy ) ) {
1667  return $result;
1668  }
1669 
1670  global $wgRateLimits;
1671  if ( !isset( $wgRateLimits[$action] ) ) {
1672  return false;
1673  }
1674 
1675  $limits = array_merge(
1676  [ '&can-bypass' => true ],
1677  $wgRateLimits[$action]
1678  );
1679 
1680  // Some groups shouldn't trigger the ping limiter, ever
1681  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1682  return false;
1683  }
1684 
1685  $logger->debug( __METHOD__ . ": limiting $action rate for {$this->getName()}" );
1686 
1687  $keys = [];
1688  $id = $this->getId();
1689  $isNewbie = $this->isNewbie();
1691 
1692  if ( $id == 0 ) {
1693  // "shared anon" limit, for all anons combined
1694  if ( isset( $limits['anon'] ) ) {
1695  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1696  }
1697  } else {
1698  // "global per name" limit, across sites
1699  if ( isset( $limits['user-global'] ) ) {
1701 
1702  $centralId = $lookup
1703  ? $lookup->centralIdFromLocalUser( $this, CentralIdLookup::AUDIENCE_RAW )
1704  : 0;
1705 
1706  if ( $centralId ) {
1707  // We don't have proper realms, use provider ID.
1708  $realm = $lookup->getProviderId();
1709 
1710  $globalKey = $cache->makeGlobalKey( 'limiter', $action, 'user-global',
1711  $realm, $centralId );
1712  } else {
1713  // Fall back to a local key for a local ID
1714  $globalKey = $cache->makeKey( 'limiter', $action, 'user-global',
1715  'local', $id );
1716  }
1717  $keys[$globalKey] = $limits['user-global'];
1718  }
1719  }
1720 
1721  if ( $isNewbie ) {
1722  // "per ip" limit for anons and newbie users
1723  if ( isset( $limits['ip'] ) ) {
1724  $ip = $this->getRequest()->getIP();
1725  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
1726  }
1727  // "per subnet" limit for anons and newbie users
1728  if ( isset( $limits['subnet'] ) ) {
1729  $ip = $this->getRequest()->getIP();
1730  $subnet = IPUtils::getSubnet( $ip );
1731  if ( $subnet !== false ) {
1732  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
1733  }
1734  }
1735  }
1736 
1737  // determine the "per user account" limit
1738  $userLimit = false;
1739  if ( $id !== 0 && isset( $limits['user'] ) ) {
1740  // default limit for logged-in users
1741  $userLimit = $limits['user'];
1742  }
1743  // limits for newbie logged-in users (overrides all the normal user limits)
1744  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1745  $userLimit = $limits['newbie'];
1746  } else {
1747  // Check for group-specific limits
1748  // If more than one group applies, use the highest allowance (if higher than the default)
1749  foreach ( $this->getGroups() as $group ) {
1750  if ( isset( $limits[$group] ) ) {
1751  if ( $userLimit === false
1752  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1753  ) {
1754  $userLimit = $limits[$group];
1755  }
1756  }
1757  }
1758  }
1759 
1760  // Set the user limit key
1761  if ( $userLimit !== false ) {
1762  // phan is confused because &can-bypass's value is a bool, so it assumes
1763  // that $userLimit is also a bool here.
1764  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1765  list( $max, $period ) = $userLimit;
1766  $logger->debug( __METHOD__ . ": effective user limit: $max in {$period}s" );
1767  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
1768  }
1769 
1770  // ip-based limits for all ping-limitable users
1771  if ( isset( $limits['ip-all'] ) ) {
1772  $ip = $this->getRequest()->getIP();
1773  // ignore if user limit is more permissive
1774  if ( $isNewbie || $userLimit === false
1775  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1776  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip-all', $ip )] = $limits['ip-all'];
1777  }
1778  }
1779 
1780  // subnet-based limits for all ping-limitable users
1781  if ( isset( $limits['subnet-all'] ) ) {
1782  $ip = $this->getRequest()->getIP();
1783  $subnet = IPUtils::getSubnet( $ip );
1784  if ( $subnet !== false ) {
1785  // ignore if user limit is more permissive
1786  if ( $isNewbie || $userLimit === false
1787  || $limits['ip-all'][0] / $limits['ip-all'][1]
1788  > $userLimit[0] / $userLimit[1] ) {
1789  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet-all', $subnet )] = $limits['subnet-all'];
1790  }
1791  }
1792  }
1793 
1794  // XXX: We may want to use $cache->getCurrentTime() here, but that would make it
1795  // harder to test for T246991. Also $cache->getCurrentTime() is documented
1796  // as being for testing only, so it apparently should not be called here.
1797  $now = MWTimestamp::time();
1798  $clockFudge = 3; // avoid log spam when a clock is slightly off
1799 
1800  $triggered = false;
1801  foreach ( $keys as $key => $limit ) {
1802 
1803  // Do the update in a merge callback, for atomicity.
1804  // To use merge(), we need to explicitly track the desired expiry timestamp.
1805  // This tracking was introduced to investigate T246991. Once it is no longer needed,
1806  // we could go back to incrWithInit(), though that has more potential for race
1807  // conditions between the get() and incrWithInit() calls.
1808  $cache->merge(
1809  $key,
1810  function ( $cache, $key, $data, &$expiry )
1811  use ( $action, $logger, &$triggered, $now, $clockFudge, $limit, $incrBy )
1812  {
1813  // phan is confused because &can-bypass's value is a bool, so it assumes
1814  // that $userLimit is also a bool here.
1815  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1816  list( $max, $period ) = $limit;
1817 
1818  $expiry = $now + (int)$period;
1819  $count = 0;
1820 
1821  // Already pinged?
1822  if ( $data ) {
1823  // NOTE: in order to investigate T246991, we write the expiry time
1824  // into the payload, along with the count.
1825  $fields = explode( '|', $data );
1826  $storedCount = (int)( $fields[0] ?? 0 );
1827  $storedExpiry = (int)( $fields[1] ?? PHP_INT_MAX );
1828 
1829  // Found a stale entry. This should not happen!
1830  if ( $storedExpiry < ( $now + $clockFudge ) ) {
1831  $logger->info(
1832  'User::pingLimiter: '
1833  . 'Stale rate limit entry, cache key failed to expire (T246991)',
1834  [
1835  'action' => $action,
1836  'user' => $this->getName(),
1837  'limit' => $max,
1838  'period' => $period,
1839  'count' => $storedCount,
1840  'key' => $key,
1841  'expiry' => MWTimestamp::convert( TS_DB, $storedExpiry ),
1842  ]
1843  );
1844  } else {
1845  // NOTE: We'll keep the original expiry when bumping counters,
1846  // resulting in a kind of fixed-window throttle.
1847  $expiry = min( $storedExpiry, $now + (int)$period );
1848  $count = $storedCount;
1849  }
1850  }
1851 
1852  // Limit exceeded!
1853  if ( $count >= $max ) {
1854  if ( !$triggered ) {
1855  $logger->info(
1856  'User::pingLimiter: User tripped rate limit',
1857  [
1858  'action' => $action,
1859  'user' => $this->getName(),
1860  'ip' => $this->getRequest()->getIP(),
1861  'limit' => $max,
1862  'period' => $period,
1863  'count' => $count,
1864  'key' => $key
1865  ]
1866  );
1867  }
1868 
1869  $triggered = true;
1870  }
1871 
1872  $count += $incrBy;
1873  $data = "$count|$expiry";
1874  return $data;
1875  }
1876  );
1877  }
1878 
1879  return $triggered;
1880  }
1881 
1893  public function isBlocked( $fromReplica = true ) {
1894  return $this->getBlock( $fromReplica ) instanceof AbstractBlock;
1895  }
1896 
1903  public function getBlock( $fromReplica = true ) {
1904  $this->getBlockedStatus( $fromReplica );
1905  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
1906  }
1907 
1919  public function isBlockedFrom( $title, $fromReplica = false ) {
1920  return MediaWikiServices::getInstance()->getPermissionManager()
1921  ->isBlockedFrom( $this, $title, $fromReplica );
1922  }
1923 
1928  public function blockedBy() {
1929  $this->getBlockedStatus();
1930  return $this->mBlockedby;
1931  }
1932 
1939  public function blockedFor() {
1940  $this->getBlockedStatus();
1941  return $this->mBlockreason;
1942  }
1943 
1948  public function getBlockId() {
1949  $this->getBlockedStatus();
1950  return ( $this->mBlock ? $this->mBlock->getId() : false );
1951  }
1952 
1961  public function isBlockedGlobally( $ip = '' ) {
1962  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
1963  }
1964 
1975  public function getGlobalBlock( $ip = '' ) {
1976  if ( $this->mGlobalBlock !== null ) {
1977  return $this->mGlobalBlock ?: null;
1978  }
1979  // User is already an IP?
1980  if ( IPUtils::isIPAddress( $this->getName() ) ) {
1981  $ip = $this->getName();
1982  } elseif ( !$ip ) {
1983  $ip = $this->getRequest()->getIP();
1984  }
1985  $blocked = false;
1986  $block = null;
1987  $this->getHookRunner()->onUserIsBlockedGlobally( $this, $ip, $blocked, $block );
1988 
1989  if ( $blocked && $block === null ) {
1990  // back-compat: UserIsBlockedGlobally didn't have $block param first
1991  $block = new SystemBlock( [
1992  'address' => $ip,
1993  'systemBlock' => 'global-block'
1994  ] );
1995  }
1996 
1997  $this->mGlobalBlock = $blocked ? $block : false;
1998  return $this->mGlobalBlock ?: null;
1999  }
2000 
2006  public function isLocked() {
2007  if ( $this->mLocked !== null ) {
2008  return $this->mLocked;
2009  }
2010  // Reset for hook
2011  $this->mLocked = false;
2012  $this->getHookRunner()->onUserIsLocked( $this, $this->mLocked );
2013  return $this->mLocked;
2014  }
2015 
2021  public function isHidden() {
2022  if ( $this->mHideName !== null ) {
2023  return (bool)$this->mHideName;
2024  }
2025  $this->getBlockedStatus();
2026  return (bool)$this->mHideName;
2027  }
2028 
2033  public function getId() {
2034  if ( $this->mId === null && $this->mName !== null &&
2035  ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
2036  ) {
2037  // Special case, we know the user is anonymous
2038  return 0;
2039  }
2040 
2041  if ( !$this->isItemLoaded( 'id' ) ) {
2042  // Don't load if this was initialized from an ID
2043  $this->load();
2044  }
2045 
2046  return (int)$this->mId;
2047  }
2048 
2053  public function setId( $v ) {
2054  $this->mId = $v;
2055  $this->clearInstanceCache( 'id' );
2056  }
2057 
2062  public function getName() {
2063  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2064  // Special case optimisation
2065  return $this->mName;
2066  }
2067 
2068  $this->load();
2069  if ( $this->mName === false ) {
2070  // Clean up IPs
2071  $this->mName = IPUtils::sanitizeIP( $this->getRequest()->getIP() );
2072  }
2073 
2074  return $this->mName;
2075  }
2076 
2090  public function setName( $str ) {
2091  $this->load();
2092  $this->mName = $str;
2093  }
2094 
2101  public function getActorId( IDatabase $dbw = null ) {
2102  if ( !$this->isItemLoaded( 'actor' ) ) {
2103  $this->load();
2104  }
2105 
2106  if ( !$this->mActorId && $dbw ) {
2107  $migration = MediaWikiServices::getInstance()->getActorMigration();
2108  $this->mActorId = $migration->getNewActorId( $dbw, $this );
2109 
2110  $this->invalidateCache();
2111  $this->setItemLoaded( 'actor' );
2112  }
2113 
2114  return (int)$this->mActorId;
2115  }
2116 
2121  public function getTitleKey() {
2122  return str_replace( ' ', '_', $this->getName() );
2123  }
2124 
2130  public function getNewtalk() {
2131  wfDeprecated( __METHOD__, '1.35' );
2132  return MediaWikiServices::getInstance()
2133  ->getTalkPageNotificationManager()
2134  ->userHasNewMessages( $this );
2135  }
2136 
2153  public function getNewMessageLinks() {
2154  wfDeprecated( __METHOD__, '1.35' );
2155  $talks = [];
2156  if ( !$this->getHookRunner()->onUserRetrieveNewTalks( $this, $talks ) ) {
2157  return $talks;
2158  }
2159 
2160  $services = MediaWikiServices::getInstance();
2161  $userHasNewMessages = $services->getTalkPageNotificationManager()
2162  ->userHasNewMessages( $this );
2163  if ( !$userHasNewMessages ) {
2164  return [];
2165  }
2166  $utp = $this->getTalkPage();
2167  $timestamp = $services->getTalkPageNotificationManager()
2168  ->getLatestSeenMessageTimestamp( $this );
2169  $rev = null;
2170  if ( $timestamp ) {
2171  $revRecord = $services->getRevisionLookup()
2172  ->getRevisionByTimestamp( $utp, $timestamp );
2173  if ( $revRecord ) {
2174  $rev = new Revision( $revRecord );
2175  }
2176  }
2177  return [
2178  [
2179  'wiki' => WikiMap::getCurrentWikiId(),
2180  'link' => $utp->getLocalURL(),
2181  'rev' => $rev
2182  ]
2183  ];
2184  }
2185 
2192  public function getNewMessageRevisionId() {
2193  wfDeprecated( __METHOD__, '1.35' );
2194  $newMessageRevisionId = null;
2195  $newMessageLinks = $this->getNewMessageLinks();
2196 
2197  // Note: getNewMessageLinks() never returns more than a single link
2198  // and it is always for the same wiki, but we double-check here in
2199  // case that changes some time in the future.
2200  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2201  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2202  && $newMessageLinks[0]['rev']
2203  ) {
2205  $newMessageRevision = $newMessageLinks[0]['rev'];
2206  $newMessageRevisionId = $newMessageRevision->getId();
2207  }
2208 
2209  return $newMessageRevisionId;
2210  }
2211 
2220  public function setNewtalk( $val, $curRev = null ) {
2221  wfDeprecated( __METHOD__, '1.35' );
2222  if ( $curRev && $curRev instanceof Revision ) {
2223  $curRev = $curRev->getRevisionRecord();
2224  }
2225  if ( $val ) {
2226  MediaWikiServices::getInstance()
2227  ->getTalkPageNotificationManager()
2228  ->setUserHasNewMessages( $this, $curRev );
2229  } else {
2230  MediaWikiServices::getInstance()
2231  ->getTalkPageNotificationManager()
2232  ->removeUserHasNewMessages( $this );
2233  }
2234  }
2235 
2242  private function newTouchedTimestamp() {
2243  $time = time();
2244  if ( $this->mTouched ) {
2245  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2246  }
2247 
2248  return wfTimestamp( TS_MW, $time );
2249  }
2250 
2261  public function clearSharedCache( $mode = 'refresh' ) {
2262  if ( !$this->getId() ) {
2263  return;
2264  }
2265 
2266  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2267  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2268  $key = $this->getCacheKey( $cache );
2269 
2270  if ( $mode === 'refresh' ) {
2271  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2272  } else {
2273  $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2274  function () use ( $cache, $key ) {
2275  $cache->delete( $key );
2276  },
2277  __METHOD__
2278  );
2279  }
2280  }
2281 
2287  public function invalidateCache() {
2288  $this->touch();
2289  $this->clearSharedCache( 'changed' );
2290  }
2291 
2304  public function touch() {
2305  $id = $this->getId();
2306  if ( $id ) {
2307  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2308  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2309  $cache->touchCheckKey( $key );
2310  $this->mQuickTouched = null;
2311  }
2312  }
2313 
2319  public function validateCache( $timestamp ) {
2320  return ( $timestamp >= $this->getTouched() );
2321  }
2322 
2331  public function getTouched() {
2332  $this->load();
2333 
2334  if ( $this->mId ) {
2335  if ( $this->mQuickTouched === null ) {
2336  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2337  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2338 
2339  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2340  }
2341 
2342  return max( $this->mTouched, $this->mQuickTouched );
2343  }
2344 
2345  return $this->mTouched;
2346  }
2347 
2353  public function getDBTouched() {
2354  $this->load();
2355 
2356  return $this->mTouched;
2357  }
2358 
2371  public function changeAuthenticationData( array $data ) {
2372  $manager = MediaWikiServices::getInstance()->getAuthManager();
2373  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2374  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2375 
2376  $status = Status::newGood( 'ignored' );
2377  foreach ( $reqs as $req ) {
2378  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2379  }
2380  if ( $status->getValue() === 'ignored' ) {
2381  $status->warning( 'authenticationdatachange-ignored' );
2382  }
2383 
2384  if ( $status->isGood() ) {
2385  foreach ( $reqs as $req ) {
2386  $manager->changeAuthenticationData( $req );
2387  }
2388  }
2389  return $status;
2390  }
2391 
2398  public function getToken( $forceCreation = true ) {
2400 
2401  $this->load();
2402  if ( !$this->mToken && $forceCreation ) {
2403  $this->setToken();
2404  }
2405 
2406  if ( !$this->mToken ) {
2407  // The user doesn't have a token, return null to indicate that.
2408  return null;
2409  }
2410 
2411  if ( $this->mToken === self::INVALID_TOKEN ) {
2412  // We return a random value here so existing token checks are very
2413  // likely to fail.
2414  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2415  }
2416 
2417  if ( $wgAuthenticationTokenVersion === null ) {
2418  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2419  return $this->mToken;
2420  }
2421 
2422  // $wgAuthenticationTokenVersion in use, so hmac it.
2423  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2424 
2425  // The raw hash can be overly long. Shorten it up.
2426  $len = max( 32, self::TOKEN_LENGTH );
2427  if ( strlen( $ret ) < $len ) {
2428  // Should never happen, even md5 is 128 bits
2429  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2430  }
2431 
2432  return substr( $ret, -$len );
2433  }
2434 
2441  public function setToken( $token = false ) {
2442  $this->load();
2443  if ( $this->mToken === self::INVALID_TOKEN ) {
2445  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2446  } elseif ( !$token ) {
2447  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2448  } else {
2449  $this->mToken = $token;
2450  }
2451  }
2452 
2457  public function getEmail() {
2458  $this->load();
2459  $this->getHookRunner()->onUserGetEmail( $this, $this->mEmail );
2460  return $this->mEmail;
2461  }
2462 
2468  $this->load();
2469  $this->getHookRunner()->onUserGetEmailAuthenticationTimestamp(
2470  $this, $this->mEmailAuthenticated );
2472  }
2473 
2478  public function setEmail( $str ) {
2479  $this->load();
2480  if ( $str == $this->mEmail ) {
2481  return;
2482  }
2483  $this->invalidateEmail();
2484  $this->mEmail = $str;
2485  $this->getHookRunner()->onUserSetEmail( $this, $this->mEmail );
2486  }
2487 
2495  public function setEmailWithConfirmation( $str ) {
2497 
2498  if ( !$wgEnableEmail ) {
2499  return Status::newFatal( 'emaildisabled' );
2500  }
2501 
2502  $oldaddr = $this->getEmail();
2503  if ( $str === $oldaddr ) {
2504  return Status::newGood( true );
2505  }
2506 
2507  $type = $oldaddr != '' ? 'changed' : 'set';
2508  $notificationResult = null;
2509 
2510  if ( $wgEmailAuthentication && $type === 'changed' ) {
2511  // Send the user an email notifying the user of the change in registered
2512  // email address on their previous email address
2513  $change = $str != '' ? 'changed' : 'removed';
2514  $notificationResult = $this->sendMail(
2515  wfMessage( 'notificationemail_subject_' . $change )->text(),
2516  wfMessage( 'notificationemail_body_' . $change,
2517  $this->getRequest()->getIP(),
2518  $this->getName(),
2519  $str )->text()
2520  );
2521  }
2522 
2523  $this->setEmail( $str );
2524 
2525  if ( $str !== '' && $wgEmailAuthentication ) {
2526  // Send a confirmation request to the new address if needed
2527  $result = $this->sendConfirmationMail( $type );
2528 
2529  if ( $notificationResult !== null ) {
2530  $result->merge( $notificationResult );
2531  }
2532 
2533  if ( $result->isGood() ) {
2534  // Say to the caller that a confirmation and notification mail has been sent
2535  $result->value = 'eauth';
2536  }
2537  } else {
2538  $result = Status::newGood( true );
2539  }
2540 
2541  return $result;
2542  }
2543 
2548  public function getRealName() {
2549  if ( !$this->isItemLoaded( 'realname' ) ) {
2550  $this->load();
2551  }
2552 
2553  return $this->mRealName;
2554  }
2555 
2560  public function setRealName( $str ) {
2561  $this->load();
2562  $this->mRealName = $str;
2563  }
2564 
2577  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2578  if ( $oname === null ) {
2579  return null; // b/c
2580  }
2581  return MediaWikiServices::getInstance()
2582  ->getUserOptionsLookup()
2583  ->getOption( $this, $oname, $defaultOverride, $ignoreHidden );
2584  }
2585 
2595  public function getOptions( $flags = 0 ) {
2596  return MediaWikiServices::getInstance()
2597  ->getUserOptionsLookup()
2598  ->getOptions( $this, $flags );
2599  }
2600 
2609  public function getBoolOption( $oname ) {
2610  return MediaWikiServices::getInstance()
2611  ->getUserOptionsLookup()
2612  ->getBoolOption( $this, $oname );
2613  }
2614 
2624  public function getIntOption( $oname, $defaultOverride = 0 ) {
2625  if ( $oname === null ) {
2626  return null; // b/c
2627  }
2628  return MediaWikiServices::getInstance()
2629  ->getUserOptionsLookup()
2630  ->getIntOption( $this, $oname, $defaultOverride );
2631  }
2632 
2642  public function setOption( $oname, $val ) {
2643  MediaWikiServices::getInstance()
2644  ->getUserOptionsManager()
2645  ->setOption( $this, $oname, $val );
2646  }
2647 
2658  public function getTokenFromOption( $oname ) {
2659  global $wgHiddenPrefs;
2660 
2661  $id = $this->getId();
2662  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2663  return false;
2664  }
2665 
2666  $token = $this->getOption( $oname );
2667  if ( !$token ) {
2668  // Default to a value based on the user token to avoid space
2669  // wasted on storing tokens for all users. When this option
2670  // is set manually by the user, only then is it stored.
2671  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2672  }
2673 
2674  return $token;
2675  }
2676 
2686  public function resetTokenFromOption( $oname ) {
2687  global $wgHiddenPrefs;
2688  if ( in_array( $oname, $wgHiddenPrefs ) ) {
2689  return false;
2690  }
2691 
2692  $token = MWCryptRand::generateHex( 40 );
2693  $this->setOption( $oname, $token );
2694  return $token;
2695  }
2696 
2721  public static function listOptionKinds() {
2722  return MediaWikiServices::getInstance()
2723  ->getUserOptionsManager()
2724  ->listOptionKinds();
2725  }
2726 
2740  public function getOptionKinds( IContextSource $context, $options = null ) {
2741  return MediaWikiServices::getInstance()
2742  ->getUserOptionsManager()
2743  ->getOptionKinds( $this, $context, $options );
2744  }
2745 
2761  public function resetOptions(
2762  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
2763  IContextSource $context = null
2764  ) {
2765  MediaWikiServices::getInstance()
2766  ->getUserOptionsManager()
2767  ->resetOptions(
2768  $this,
2769  $context ?? RequestContext::getMain(),
2770  $resetKinds
2771  );
2772  }
2773 
2778  public function getDatePreference() {
2779  // Important migration for old data rows
2780  if ( $this->mDatePreference === null ) {
2781  global $wgLang;
2782  $value = $this->getOption( 'date' );
2783  $map = $wgLang->getDatePreferenceMigrationMap();
2784  if ( isset( $map[$value] ) ) {
2785  $value = $map[$value];
2786  }
2787  $this->mDatePreference = $value;
2788  }
2789  return $this->mDatePreference;
2790  }
2791 
2798  public function requiresHTTPS() {
2799  global $wgForceHTTPS, $wgSecureLogin;
2800  if ( $wgForceHTTPS ) {
2801  return true;
2802  }
2803  if ( !$wgSecureLogin ) {
2804  return false;
2805  }
2806  $https = $this->getBoolOption( 'prefershttps' );
2807  $this->getHookRunner()->onUserRequiresHTTPS( $this, $https );
2808  if ( $https ) {
2809  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
2810  }
2811 
2812  return $https;
2813  }
2814 
2820  public function getStubThreshold() {
2821  global $wgMaxArticleSize; # Maximum article size, in Kb
2822  $threshold = $this->getIntOption( 'stubthreshold' );
2823  if ( $threshold > $wgMaxArticleSize * 1024 ) {
2824  // If they have set an impossible value, disable the preference
2825  // so we can use the parser cache again.
2826  $threshold = 0;
2827  }
2828  return $threshold;
2829  }
2830 
2839  public function getRights() {
2840  return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
2841  }
2842 
2851  public function getGroups() {
2852  return MediaWikiServices::getInstance()
2853  ->getUserGroupManager()
2854  ->getUserGroups( $this, $this->queryFlagsUsed );
2855  }
2856 
2866  public function getGroupMemberships() {
2867  return MediaWikiServices::getInstance()
2868  ->getUserGroupManager()
2869  ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
2870  }
2871 
2882  public function getEffectiveGroups( $recache = false ) {
2883  return MediaWikiServices::getInstance()
2884  ->getUserGroupManager()
2885  ->getUserEffectiveGroups( $this, $this->queryFlagsUsed, $recache );
2886  }
2887 
2898  public function getAutomaticGroups( $recache = false ) {
2899  return MediaWikiServices::getInstance()
2900  ->getUserGroupManager()
2901  ->getUserImplicitGroups( $this, $this->queryFlagsUsed, $recache );
2902  }
2903 
2915  public function getFormerGroups() {
2916  return MediaWikiServices::getInstance()
2917  ->getUserGroupManager()
2918  ->getUserFormerGroups( $this, $this->queryFlagsUsed );
2919  }
2920 
2925  public function getEditCount() {
2926  if ( !$this->getId() ) {
2927  return null;
2928  }
2929 
2930  if ( $this->mEditCount === null ) {
2931  $this->mEditCount = MediaWikiServices::getInstance()
2932  ->getUserEditTracker()
2933  ->getUserEditCount( $this );
2934  }
2935  return (int)$this->mEditCount;
2936  }
2937 
2951  public function addGroup( $group, $expiry = null ) {
2952  return MediaWikiServices::getInstance()
2953  ->getUserGroupManager()
2954  ->addUserToGroup( $this, $group, $expiry, true );
2955  }
2956 
2966  public function removeGroup( $group ) {
2967  return MediaWikiServices::getInstance()
2968  ->getUserGroupManager()
2969  ->removeUserFromGroup( $this, $group );
2970  }
2971 
2981  public function isRegistered() {
2982  return $this->getId() != 0;
2983  }
2984 
2989  public function isLoggedIn() {
2990  return $this->isRegistered();
2991  }
2992 
2997  public function isAnon() {
2998  return !$this->isRegistered();
2999  }
3000 
3005  public function isBot() {
3006  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3007  return true;
3008  }
3009 
3010  $isBot = false;
3011  $this->getHookRunner()->onUserIsBot( $this, $isBot );
3012 
3013  return $isBot;
3014  }
3015 
3025  public function isSystemUser() {
3026  $this->load();
3027  if ( $this->mEmail || $this->mToken !== self::INVALID_TOKEN ||
3028  MediaWikiServices::getInstance()->getAuthManager()->userCanAuthenticate( $this->mName )
3029  ) {
3030  return false;
3031  }
3032  return true;
3033  }
3034 
3044  public function isAllowedAny( ...$permissions ) {
3045  return MediaWikiServices::getInstance()
3046  ->getPermissionManager()
3047  ->userHasAnyRight( $this, ...$permissions );
3048  }
3049 
3056  public function isAllowedAll( ...$permissions ) {
3057  return MediaWikiServices::getInstance()
3058  ->getPermissionManager()
3059  ->userHasAllRights( $this, ...$permissions );
3060  }
3061 
3072  public function isAllowed( $action = '' ) {
3073  return MediaWikiServices::getInstance()->getPermissionManager()
3074  ->userHasRight( $this, $action );
3075  }
3076 
3081  public function useRCPatrol() {
3082  global $wgUseRCPatrol;
3083  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3084  }
3085 
3090  public function useNPPatrol() {
3092  return (
3094  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3095  );
3096  }
3097 
3102  public function useFilePatrol() {
3104  return (
3106  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3107  );
3108  }
3109 
3115  public function getRequest() {
3116  if ( $this->mRequest ) {
3117  return $this->mRequest;
3118  }
3119 
3120  global $wgRequest;
3121  return $wgRequest;
3122  }
3123 
3132  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3133  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3134  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3135  }
3136  return false;
3137  }
3138 
3149  public function isTempWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ): bool {
3150  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3151  return MediaWikiServices::getInstance()->getWatchedItemStore()
3152  ->isTempWatched( $this, $title );
3153  }
3154  return false;
3155  }
3156 
3168  public function addWatch(
3169  $title,
3170  $checkRights = self::CHECK_USER_RIGHTS,
3171  ?string $expiry = null
3172  ) {
3173  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3174  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3175  $store->addWatch( $this, $title->getSubjectPage(), $expiry );
3176  $store->addWatch( $this, $title->getTalkPage(), $expiry );
3177  }
3178  $this->invalidateCache();
3179  }
3180 
3190  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3191  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3192  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3193  $store->removeWatch( $this, $title->getSubjectPage() );
3194  $store->removeWatch( $this, $title->getTalkPage() );
3195  }
3196  $this->invalidateCache();
3197  }
3198 
3210  public function clearNotification( &$title, $oldid = 0 ) {
3211  MediaWikiServices::getInstance()
3212  ->getWatchlistNotificationManager()
3213  ->clearTitleUserNotifications( $this, $title, $oldid );
3214  }
3215 
3225  public function clearAllNotifications() {
3226  wfDeprecated( __METHOD__, '1.35' );
3227  MediaWikiServices::getInstance()
3228  ->getWatchlistNotificationManager()
3229  ->clearAllUserNotifications( $this );
3230  }
3231 
3237  public function getExperienceLevel() {
3238  global $wgLearnerEdits,
3242 
3243  if ( $this->isAnon() ) {
3244  return false;
3245  }
3246 
3247  $editCount = $this->getEditCount();
3248  $registration = $this->getRegistration();
3249  $now = time();
3250  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3251  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3252 
3253  if ( $editCount < $wgLearnerEdits ||
3254  $registration > $learnerRegistration ) {
3255  return 'newcomer';
3256  }
3257 
3258  if ( $editCount > $wgExperiencedUserEdits &&
3259  $registration <= $experiencedRegistration
3260  ) {
3261  return 'experienced';
3262  }
3263 
3264  return 'learner';
3265  }
3266 
3275  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3276  $this->load();
3277  if ( $this->mId == 0 ) {
3278  return;
3279  }
3280 
3281  $session = $this->getRequest()->getSession();
3282  if ( $request && $session->getRequest() !== $request ) {
3283  $session = $session->sessionWithRequest( $request );
3284  }
3285  $delay = $session->delaySave();
3286 
3287  if ( !$session->getUser()->equals( $this ) ) {
3288  if ( !$session->canSetUser() ) {
3290  ->warning( __METHOD__ .
3291  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3292  );
3293  return;
3294  }
3295  $session->setUser( $this );
3296  }
3297 
3298  $session->setRememberUser( $rememberMe );
3299  if ( $secure !== null ) {
3300  $session->setForceHTTPS( $secure );
3301  }
3302 
3303  $session->persist();
3304 
3305  ScopedCallback::consume( $delay );
3306  }
3307 
3311  public function logout() {
3312  // Avoid PHP 7.1 warning of passing $this by reference
3313  $user = $this;
3314  if ( $this->getHookRunner()->onUserLogout( $user ) ) {
3315  $this->doLogout();
3316  }
3317  }
3318 
3323  public function doLogout() {
3324  $session = $this->getRequest()->getSession();
3325  if ( !$session->canSetUser() ) {
3327  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3328  $error = 'immutable';
3329  } elseif ( !$session->getUser()->equals( $this ) ) {
3331  ->warning( __METHOD__ .
3332  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3333  );
3334  // But we still may as well make this user object anon
3335  $this->clearInstanceCache( 'defaults' );
3336  $error = 'wronguser';
3337  } else {
3338  $this->clearInstanceCache( 'defaults' );
3339  $delay = $session->delaySave();
3340  $session->unpersist(); // Clear cookies (T127436)
3341  $session->setLoggedOutTimestamp( time() );
3342  $session->setUser( new User );
3343  $session->set( 'wsUserID', 0 ); // Other code expects this
3344  $session->resetAllTokens();
3345  ScopedCallback::consume( $delay );
3346  $error = false;
3347  }
3348  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3349  'event' => 'logout',
3350  'successful' => $error === false,
3351  'status' => $error ?: 'success',
3352  ] );
3353  }
3354 
3359  public function saveSettings() {
3360  if ( wfReadOnly() ) {
3361  // @TODO: caller should deal with this instead!
3362  // This should really just be an exception.
3364  null,
3365  "Could not update user with ID '{$this->mId}'; DB is read-only."
3366  ) );
3367  return;
3368  }
3369 
3370  $this->load();
3371  if ( $this->mId == 0 ) {
3372  return; // anon
3373  }
3374 
3375  // Get a new user_touched that is higher than the old one.
3376  // This will be used for a CAS check as a last-resort safety
3377  // check against race conditions and replica DB lag.
3378  $newTouched = $this->newTouchedTimestamp();
3379 
3380  $dbw = wfGetDB( DB_MASTER );
3381  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
3382  $dbw->update( 'user',
3383  [ /* SET */
3384  'user_name' => $this->mName,
3385  'user_real_name' => $this->mRealName,
3386  'user_email' => $this->mEmail,
3387  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3388  'user_touched' => $dbw->timestamp( $newTouched ),
3389  'user_token' => strval( $this->mToken ),
3390  'user_email_token' => $this->mEmailToken,
3391  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3392  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
3393  'user_id' => $this->mId,
3394  ] ), $fname
3395  );
3396 
3397  if ( !$dbw->affectedRows() ) {
3398  // Maybe the problem was a missed cache update; clear it to be safe
3399  $this->clearSharedCache( 'refresh' );
3400  // User was changed in the meantime or loaded with stale data
3401  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
3402  LoggerFactory::getInstance( 'preferences' )->warning(
3403  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
3404  [ 'user_id' => $this->mId, 'db_flag' => $from ]
3405  );
3406  throw new MWException( "CAS update failed on user_touched. " .
3407  "The version of the user to be saved is older than the current version."
3408  );
3409  }
3410 
3411  $dbw->update(
3412  'actor',
3413  [ 'actor_name' => $this->mName ],
3414  [ 'actor_user' => $this->mId ],
3415  $fname
3416  );
3417  } );
3418 
3419  $this->mTouched = $newTouched;
3420  MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3421 
3422  $this->getHookRunner()->onUserSaveSettings( $this );
3423  $this->clearSharedCache( 'changed' );
3424  $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3425  $hcu->purgeTitleUrls( $this->getUserPage(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
3426  }
3427 
3434  public function idForName( $flags = self::READ_NORMAL ) {
3435  $s = trim( $this->getName() );
3436  if ( $s === '' ) {
3437  return 0;
3438  }
3439 
3440  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
3441  $db = wfGetDB( $index );
3442 
3443  $id = $db->selectField( 'user',
3444  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
3445 
3446  return (int)$id;
3447  }
3448 
3464  public static function createNew( $name, $params = [] ) {
3465  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
3466  if ( isset( $params[$field] ) ) {
3467  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
3468  unset( $params[$field] );
3469  }
3470  }
3471 
3472  $user = new User;
3473  $user->load();
3474  $user->setToken(); // init token
3475  if ( isset( $params['options'] ) ) {
3476  MediaWikiServices::getInstance()
3477  ->getUserOptionsManager()
3478  ->loadUserOptions( $user, $user->queryFlagsUsed, $params['options'] );
3479  unset( $params['options'] );
3480  }
3481  $dbw = wfGetDB( DB_MASTER );
3482 
3483  $noPass = PasswordFactory::newInvalidPassword()->toString();
3484 
3485  $fields = [
3486  'user_name' => $name,
3487  'user_password' => $noPass,
3488  'user_newpassword' => $noPass,
3489  'user_email' => $user->mEmail,
3490  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3491  'user_real_name' => $user->mRealName,
3492  'user_token' => strval( $user->mToken ),
3493  'user_registration' => $dbw->timestamp( $user->mRegistration ),
3494  'user_editcount' => 0,
3495  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3496  ];
3497  foreach ( $params as $name => $value ) {
3498  $fields["user_$name"] = $value;
3499  }
3500 
3501  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
3502  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
3503  if ( $dbw->affectedRows() ) {
3504  $newUser = self::newFromId( $dbw->insertId() );
3505  $newUser->mName = $fields['user_name'];
3506  $newUser->updateActorId( $dbw );
3507  // Load the user from master to avoid replica lag
3508  $newUser->load( self::READ_LATEST );
3509  } else {
3510  $newUser = null;
3511  }
3512  return $newUser;
3513  } );
3514  }
3515 
3542  public function addToDatabase() {
3543  $this->load();
3544  if ( !$this->mToken ) {
3545  $this->setToken(); // init token
3546  }
3547 
3548  if ( !is_string( $this->mName ) ) {
3549  throw new RuntimeException( "User name field is not set." );
3550  }
3551 
3552  $this->mTouched = $this->newTouchedTimestamp();
3553 
3554  $dbw = wfGetDB( DB_MASTER );
3555  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
3556  $noPass = PasswordFactory::newInvalidPassword()->toString();
3557  $dbw->insert( 'user',
3558  [
3559  'user_name' => $this->mName,
3560  'user_password' => $noPass,
3561  'user_newpassword' => $noPass,
3562  'user_email' => $this->mEmail,
3563  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3564  'user_real_name' => $this->mRealName,
3565  'user_token' => strval( $this->mToken ),
3566  'user_registration' => $dbw->timestamp( $this->mRegistration ),
3567  'user_editcount' => 0,
3568  'user_touched' => $dbw->timestamp( $this->mTouched ),
3569  ], $fname,
3570  [ 'IGNORE' ]
3571  );
3572  if ( !$dbw->affectedRows() ) {
3573  // Use locking reads to bypass any REPEATABLE-READ snapshot.
3574  $this->mId = $dbw->selectField(
3575  'user',
3576  'user_id',
3577  [ 'user_name' => $this->mName ],
3578  $fname,
3579  [ 'LOCK IN SHARE MODE' ]
3580  );
3581  $loaded = false;
3582  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
3583  $loaded = true;
3584  }
3585  if ( !$loaded ) {
3586  throw new MWException( $fname . ": hit a key conflict attempting " .
3587  "to insert user '{$this->mName}' row, but it was not present in select!" );
3588  }
3589  return Status::newFatal( 'userexists' );
3590  }
3591  $this->mId = $dbw->insertId();
3592  self::$idCacheByName[$this->mName] = $this->mId;
3593  $this->updateActorId( $dbw );
3594 
3595  return Status::newGood();
3596  } );
3597  if ( !$status->isGood() ) {
3598  return $status;
3599  }
3600 
3601  // Clear instance cache other than user table data and actor, which is already accurate
3602  $this->clearInstanceCache();
3603 
3604  MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3605  return Status::newGood();
3606  }
3607 
3612  private function updateActorId( IDatabase $dbw ) {
3613  $dbw->insert(
3614  'actor',
3615  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
3616  __METHOD__
3617  );
3618  $this->mActorId = (int)$dbw->insertId();
3619  }
3620 
3626  public function spreadAnyEditBlock() {
3627  if ( $this->isLoggedIn() && $this->getBlock() ) {
3628  return $this->spreadBlock();
3629  }
3630 
3631  return false;
3632  }
3633 
3639  protected function spreadBlock() {
3640  wfDebug( __METHOD__ . "()" );
3641  $this->load();
3642  if ( $this->mId == 0 ) {
3643  return false;
3644  }
3645 
3646  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
3647  if ( !$userblock ) {
3648  return false;
3649  }
3650 
3651  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
3652  }
3653 
3658  public function isBlockedFromCreateAccount() {
3659  $this->getBlockedStatus();
3660  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
3661  return $this->mBlock;
3662  }
3663 
3664  # T15611: if the IP address the user is trying to create an account from is
3665  # blocked with createaccount disabled, prevent new account creation there even
3666  # when the user is logged in
3667  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
3668  $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
3669  null, $this->getRequest()->getIP()
3670  );
3671  }
3672  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
3673  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
3674  ? $this->mBlockedFromCreateAccount
3675  : false;
3676  }
3677 
3682  public function isBlockedFromEmailuser() {
3683  $this->getBlockedStatus();
3684  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
3685  }
3686 
3693  public function isBlockedFromUpload() {
3694  $this->getBlockedStatus();
3695  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
3696  }
3697 
3702  public function isAllowedToCreateAccount() {
3703  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
3704  }
3705 
3711  public function getUserPage() {
3712  return Title::makeTitle( NS_USER, $this->getName() );
3713  }
3714 
3720  public function getTalkPage() {
3721  $title = $this->getUserPage();
3722  return $title->getTalkPage();
3723  }
3724 
3730  public function isNewbie() {
3731  return !$this->isAllowed( 'autoconfirmed' );
3732  }
3733 
3745  public function getEditTokenObject( $salt = '', $request = null ) {
3746  if ( $this->isAnon() ) {
3747  return new LoggedOutEditToken();
3748  }
3749 
3750  if ( !$request ) {
3751  $request = $this->getRequest();
3752  }
3753  return $request->getSession()->getToken( $salt );
3754  }
3755 
3769  public function getEditToken( $salt = '', $request = null ) {
3770  return $this->getEditTokenObject( $salt, $request )->toString();
3771  }
3772 
3785  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
3786  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
3787  }
3788 
3799  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
3800  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
3801  return $this->matchEditToken( $val, $salt, $request, $maxage );
3802  }
3803 
3811  public function sendConfirmationMail( $type = 'created' ) {
3812  global $wgLang;
3813  $expiration = null; // gets passed-by-ref and defined in next line.
3814  $token = $this->confirmationToken( $expiration );
3815  $url = $this->confirmationTokenUrl( $token );
3816  $invalidateURL = $this->invalidationTokenUrl( $token );
3817  $this->saveSettings();
3818 
3819  if ( $type == 'created' || $type === false ) {
3820  $message = 'confirmemail_body';
3821  $type = 'created';
3822  } elseif ( $type === true ) {
3823  $message = 'confirmemail_body_changed';
3824  $type = 'changed';
3825  } else {
3826  // Messages: confirmemail_body_changed, confirmemail_body_set
3827  $message = 'confirmemail_body_' . $type;
3828  }
3829 
3830  $mail = [
3831  'subject' => wfMessage( 'confirmemail_subject' )->text(),
3832  'body' => wfMessage( $message,
3833  $this->getRequest()->getIP(),
3834  $this->getName(),
3835  $url,
3836  $wgLang->userTimeAndDate( $expiration, $this ),
3837  $invalidateURL,
3838  $wgLang->userDate( $expiration, $this ),
3839  $wgLang->userTime( $expiration, $this ) )->text(),
3840  'from' => null,
3841  'replyTo' => null,
3842  ];
3843  $info = [
3844  'type' => $type,
3845  'ip' => $this->getRequest()->getIP(),
3846  'confirmURL' => $url,
3847  'invalidateURL' => $invalidateURL,
3848  'expiration' => $expiration
3849  ];
3850 
3851  $this->getHookRunner()->onUserSendConfirmationMail( $this, $mail, $info );
3852  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
3853  }
3854 
3866  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
3867  global $wgPasswordSender;
3868 
3869  if ( $from instanceof User ) {
3870  $sender = MailAddress::newFromUser( $from );
3871  } else {
3872  $sender = new MailAddress( $wgPasswordSender,
3873  wfMessage( 'emailsender' )->inContentLanguage()->text() );
3874  }
3875  $to = MailAddress::newFromUser( $this );
3876 
3877  return UserMailer::send( $to, $sender, $subject, $body, [
3878  'replyTo' => $replyto,
3879  ] );
3880  }
3881 
3892  protected function confirmationToken( &$expiration ) {
3894  $now = time();
3895  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
3896  $expiration = wfTimestamp( TS_MW, $expires );
3897  $this->load();
3898  $token = MWCryptRand::generateHex( 32 );
3899  $hash = md5( $token );
3900  $this->mEmailToken = $hash;
3901  $this->mEmailTokenExpires = $expiration;
3902  return $token;
3903  }
3904 
3910  protected function confirmationTokenUrl( $token ) {
3911  return $this->getTokenUrl( 'ConfirmEmail', $token );
3912  }
3913 
3919  protected function invalidationTokenUrl( $token ) {
3920  return $this->getTokenUrl( 'InvalidateEmail', $token );
3921  }
3922 
3937  protected function getTokenUrl( $page, $token ) {
3938  // Hack to bypass localization of 'Special:'
3939  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
3940  return $title->getCanonicalURL();
3941  }
3942 
3950  public function confirmEmail() {
3951  // Check if it's already confirmed, so we don't touch the database
3952  // and fire the ConfirmEmailComplete hook on redundant confirmations.
3953  if ( !$this->isEmailConfirmed() ) {
3955  $this->getHookRunner()->onConfirmEmailComplete( $this );
3956  }
3957  return true;
3958  }
3959 
3967  public function invalidateEmail() {
3968  $this->load();
3969  $this->mEmailToken = null;
3970  $this->mEmailTokenExpires = null;
3971  $this->setEmailAuthenticationTimestamp( null );
3972  $this->mEmail = '';
3973  $this->getHookRunner()->onInvalidateEmailComplete( $this );
3974  return true;
3975  }
3976 
3981  public function setEmailAuthenticationTimestamp( $timestamp ) {
3982  $this->load();
3983  $this->mEmailAuthenticated = $timestamp;
3984  $this->getHookRunner()->onUserSetEmailAuthenticationTimestamp(
3985  $this, $this->mEmailAuthenticated );
3986  }
3987 
3993  public function canSendEmail() {
3995  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
3996  return false;
3997  }
3998  $canSend = $this->isEmailConfirmed();
3999  $this->getHookRunner()->onUserCanSendEmail( $this, $canSend );
4000  return $canSend;
4001  }
4002 
4008  public function canReceiveEmail() {
4009  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4010  }
4011 
4022  public function isEmailConfirmed() {
4023  global $wgEmailAuthentication;
4024  $this->load();
4025  // Avoid PHP 7.1 warning of passing $this by reference
4026  $user = $this;
4027  $confirmed = true;
4028  if ( $this->getHookRunner()->onEmailConfirmed( $user, $confirmed ) ) {
4029  if ( $this->isAnon() ) {
4030  return false;
4031  }
4032  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4033  return false;
4034  }
4036  return false;
4037  }
4038  return true;
4039  }
4040 
4041  return $confirmed;
4042  }
4043 
4048  public function isEmailConfirmationPending() {
4049  global $wgEmailAuthentication;
4050  return $wgEmailAuthentication &&
4051  !$this->isEmailConfirmed() &&
4052  $this->mEmailToken &&
4053  $this->mEmailTokenExpires > wfTimestamp();
4054  }
4055 
4063  public function getRegistration() {
4064  if ( $this->isAnon() ) {
4065  return false;
4066  }
4067  $this->load();
4068  return $this->mRegistration;
4069  }
4070 
4077  public function getFirstEditTimestamp() {
4078  return MediaWikiServices::getInstance()
4079  ->getUserEditTracker()
4080  ->getFirstEditTimestamp( $this );
4081  }
4082 
4090  public function getLatestEditTimestamp() {
4091  return MediaWikiServices::getInstance()
4092  ->getUserEditTracker()
4093  ->getLatestEditTimestamp( $this );
4094  }
4095 
4105  public static function getGroupPermissions( $groups ) {
4106  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4107  }
4108 
4118  public static function getGroupsWithPermission( $role ) {
4119  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4120  }
4121 
4137  public static function groupHasPermission( $group, $role ) {
4138  return MediaWikiServices::getInstance()->getPermissionManager()
4139  ->groupHasPermission( $group, $role );
4140  }
4141 
4159  public static function isEveryoneAllowed( $right ) {
4160  wfDeprecated( __METHOD__, '1.34' );
4161  return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4162  }
4163 
4171  public static function getAllGroups() {
4172  return MediaWikiServices::getInstance()
4173  ->getUserGroupManager()
4174  ->listAllGroups();
4175  }
4176 
4184  public static function getAllRights() {
4185  wfDeprecated( __METHOD__, '1.34' );
4186  return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4187  }
4188 
4194  public static function getImplicitGroups() {
4195  return MediaWikiServices::getInstance()
4196  ->getUserGroupManager()
4197  ->listAllImplicitGroups();
4198  }
4199 
4210  public static function changeableByGroup( $group ) {
4212 
4213  $groups = [
4214  'add' => [],
4215  'remove' => [],
4216  'add-self' => [],
4217  'remove-self' => []
4218  ];
4219 
4220  if ( empty( $wgAddGroups[$group] ) ) {
4221  // Don't add anything to $groups
4222  } elseif ( $wgAddGroups[$group] === true ) {
4223  // You get everything
4224  $groups['add'] = self::getAllGroups();
4225  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4226  $groups['add'] = $wgAddGroups[$group];
4227  }
4228 
4229  // Same thing for remove
4230  if ( empty( $wgRemoveGroups[$group] ) ) {
4231  // Do nothing
4232  } elseif ( $wgRemoveGroups[$group] === true ) {
4233  $groups['remove'] = self::getAllGroups();
4234  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4235  $groups['remove'] = $wgRemoveGroups[$group];
4236  }
4237 
4238  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4239  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4240  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4241  if ( is_int( $key ) ) {
4242  $wgGroupsAddToSelf['user'][] = $value;
4243  }
4244  }
4245  }
4246 
4247  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4248  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4249  if ( is_int( $key ) ) {
4250  $wgGroupsRemoveFromSelf['user'][] = $value;
4251  }
4252  }
4253  }
4254 
4255  // Now figure out what groups the user can add to him/herself
4256  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4257  // Do nothing
4258  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4259  // No idea WHY this would be used, but it's there
4260  $groups['add-self'] = self::getAllGroups();
4261  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4262  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4263  }
4264 
4265  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4266  // Do nothing
4267  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4268  $groups['remove-self'] = self::getAllGroups();
4269  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4270  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4271  }
4272 
4273  return $groups;
4274  }
4275 
4283  public function changeableGroups() {
4284  if ( $this->isAllowed( 'userrights' ) ) {
4285  // This group gives the right to modify everything (reverse-
4286  // compatibility with old "userrights lets you change
4287  // everything")
4288  // Using array_merge to make the groups reindexed
4289  $all = array_merge( self::getAllGroups() );
4290  return [
4291  'add' => $all,
4292  'remove' => $all,
4293  'add-self' => [],
4294  'remove-self' => []
4295  ];
4296  }
4297 
4298  // Okay, it's not so simple, we will have to go through the arrays
4299  $groups = [
4300  'add' => [],
4301  'remove' => [],
4302  'add-self' => [],
4303  'remove-self' => []
4304  ];
4305  $addergroups = $this->getEffectiveGroups();
4306 
4307  foreach ( $addergroups as $addergroup ) {
4308  $groups = array_merge_recursive(
4309  $groups, $this->changeableByGroup( $addergroup )
4310  );
4311  $groups['add'] = array_unique( $groups['add'] );
4312  $groups['remove'] = array_unique( $groups['remove'] );
4313  $groups['add-self'] = array_unique( $groups['add-self'] );
4314  $groups['remove-self'] = array_unique( $groups['remove-self'] );
4315  }
4316  return $groups;
4317  }
4318 
4322  public function incEditCount() {
4323  if ( $this->isAnon() ) {
4324  return; // sanity
4325  }
4326 
4328  new UserEditCountUpdate( $this, 1 ),
4330  );
4331  }
4332 
4338  public function setEditCountInternal( $count ) {
4339  $this->mEditCount = $count;
4340  }
4341 
4349  public function initEditCountInternal( IDatabase $dbr ) {
4350  return MediaWikiServices::getInstance()
4351  ->getUserEditTracker()
4352  ->initializeUserEditCount( $this );
4353  }
4354 
4362  public static function getRightDescription( $right ) {
4363  $key = "right-$right";
4364  $msg = wfMessage( $key );
4365  return $msg->isDisabled() ? $right : $msg->text();
4366  }
4367 
4375  public static function getGrantName( $grant ) {
4376  $key = "grant-$grant";
4377  $msg = wfMessage( $key );
4378  return $msg->isDisabled() ? $grant : $msg->text();
4379  }
4380 
4390  public static function getQueryInfo() {
4391  $ret = [
4392  'tables' => [ 'user', 'user_actor' => 'actor' ],
4393  'fields' => [
4394  'user_id',
4395  'user_name',
4396  'user_real_name',
4397  'user_email',
4398  'user_touched',
4399  'user_token',
4400  'user_email_authenticated',
4401  'user_email_token',
4402  'user_email_token_expires',
4403  'user_registration',
4404  'user_editcount',
4405  'user_actor.actor_id',
4406  ],
4407  'joins' => [
4408  'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
4409  ],
4410  ];
4411 
4412  return $ret;
4413  }
4414 
4422  public static function newFatalPermissionDeniedStatus( $permission ) {
4423  global $wgLang;
4424 
4425  $groups = [];
4426  foreach ( MediaWikiServices::getInstance()
4428  ->getGroupsWithPermission( $permission ) as $group ) {
4429  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
4430  }
4431 
4432  if ( $groups ) {
4433  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
4434  }
4435 
4436  return Status::newFatal( 'badaccess-group0' );
4437  }
4438 
4448  public function getInstanceForUpdate() {
4449  if ( !$this->getId() ) {
4450  return null; // anon
4451  }
4452 
4453  $user = self::newFromId( $this->getId() );
4454  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
4455  return null;
4456  }
4457 
4458  return $user;
4459  }
4460 
4468  public function equals( UserIdentity $user ) {
4469  // XXX it's not clear whether central ID providers are supposed to obey this
4470  return $this->getName() === $user->getName();
4471  }
4472 
4478  public function isAllowUsertalk() {
4479  return $this->mAllowUsertalk;
4480  }
4481 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1558
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=self::READ_NORMAL)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:684
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:5321
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:407
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:322
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2130
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:579
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:3910
User\__set
__set( $name, $value)
Definition: User.php:270
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
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:2780
User\$mToken
string $mToken
Definition: User.php:137
User\$mBlockedby
string int $mBlockedby
Definition: User.php:180
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:1074
User\getId
getId()
Get the user's ID.
Definition: User.php:2033
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3102
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2398
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:2997
User\$mBlockedFromCreateAccount
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:215
User\$mBlockreason
string $mBlockreason
TODO: This should be removed when User::BlockedFor and AbstractBlock::getReason are hard deprecated.
Definition: User.php:188
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:3937
User\isRegistered
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:2981
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2101
User\$mCacheVars
static string[] $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:100
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:2686
User\isBlockedFrom
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:1919
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1421
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:160
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:4422
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:3745
User\isBot
isBot()
Definition: User.php:3005
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:2242
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9393
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:2925
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:3639
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:85
User\incEditCount
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition: User.php:4322
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:709
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:2740
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4048
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:1948
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2624
true
return true
Definition: router.php:90
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:2595
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:133
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:238
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1815
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2398
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:3626
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2192
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:212
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1901
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred update queue for execution at the appropriate time.
Definition: DeferredUpdates.php:106
User\setEmailWithConfirmation
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:2495
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1789
User\$mHideName
bool $mHideName
Definition: User.php:199
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:2820
Sanitizer\validateEmail
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1711
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1126
User\$mLocked
bool $mLocked
Definition: User.php:192
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:542
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1313
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1220
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:3981
User\$mEmail
string $mEmail
Definition: User.php:131
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:3711
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:2851
$s
$s
Definition: mergeMessageFileList.php:184
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:611
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1087
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3090
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:2778
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:305
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:2478
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:993
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:3811
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4137
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2467
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:1661
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:3081
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:3967
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:731
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:7332
User\$mHash
string $mHash
Definition: User.php:182
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:7348
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
User\isIPRange
isIPRange()
Is the user an IP range?
Definition: User.php:977
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:2839
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:3464
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3115
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
User\getAutomaticGroups
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:2898
User\INVALID_TOKEN
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:68
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1545
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:4448
$dbr
$dbr
Definition: testCompression.php:54
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Specify the difference engine to use.
Definition: DefaultSettings.php:9392
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:772
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
NS_MAIN
const NS_MAIN
Definition: Defines.php:69
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:2951
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:668
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:3785
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2457
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:52
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:3720
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:3542
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:2287
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:3919
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2006
User\$mDatePreference
string $mDatePreference
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:173
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:5359
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1027
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:1100
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:3950
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1224
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:279
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:1939
getPermissionManager
getPermissionManager()
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2220
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2475
User\isTempWatched
isTempWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check if the article is temporarily watched.
Definition: User.php:3149
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:3702
User\logout
logout()
Log this user out.
Definition: User.php:3311
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:3892
User\initEditCountInternal
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition: User.php:4349
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:448
$wgLang
$wgLang
Definition: Setup.php:776
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1831
User\getImplicitGroups
static getImplicitGroups()
Get a list of implicit groups.
Definition: User.php:4194
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2021
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:3682
User\$mBlock
AbstractBlock null $mBlock
Definition: User.php:209
User\loadDefaults
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition: User.php:1176
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:965
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2319
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:2882
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1634
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:85
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:2966
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4008
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:3730
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2304
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:1515
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1783
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:86
User\TOKEN_LENGTH
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:63
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:591
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1844
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:135
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:630
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1903
User\setName
setName( $str)
Set the user name.
Definition: User.php:2090
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:128
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:2761
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:42
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:7359
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:910
User\__get
& __get( $name)
Definition: User.php:249
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5935
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3210
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:3359
User\isAllowedAll
isAllowedAll(... $permissions)
Definition: User.php:3056
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:827
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4077
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:81
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:2560
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2153
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:617
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:2915
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:881
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\isAllowedAny
isAllowedAny(... $permissions)
Check if user is allowed to access a feature / make an action.
Definition: User.php:3044
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1235
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:6100
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:1961
$wgForceHTTPS
bool $wgForceHTTPS
If this is true, when an insecure HTTP request is received, always redirect to HTTPS.
Definition: DefaultSettings.php:168
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2577
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:3434
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:2331
User\$mId
int $mId
Cache variables.
Definition: User.php:122
User\isSystemUser
isSystemUser()
Get whether the user is a system user.
Definition: User.php:3025
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:1975
User\__toString
__toString()
Definition: User.php:245
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:52
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1477
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:125
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:2548
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:3612
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:3275
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4105
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4283
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3225
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition: User.php:3168
User\VERSION
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:74
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:3799
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5930
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4171
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3190
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4184
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:3693
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:454
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:4390
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:1928
User\$mLoadedItems
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:154
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:9390
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2353
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:88
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:42
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4210
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2053
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3237
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1833
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4063
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:242
User\isEveryoneAllowed
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4159
MediaWiki\Block\AbstractBlock\appliesToRight
appliesToRight( $right)
Determine whether the block prevents a given right.
Definition: AbstractBlock.php:270
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:4362
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2371
$cache
$cache
Definition: mcc.php:33
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:6182
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:2989
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:34
User\$mActorId
int null $mActorId
Definition: User.php:126
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2121
User\purge
static purge( $dbDomain, $userId)
Definition: User.php:437
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2441
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:901
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:943
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:143
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1023
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:1144
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4022
User\$mGlobalBlock
AbstractBlock $mGlobalBlock
Definition: User.php:190
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:4478
User\getBlockedStatus
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1573
User\$idCacheByName
static int[] $idCacheByName
Definition: User.php:221
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:4375
UserCache\singleton
static singleton()
Definition: UserCache.php:34
User\clearSharedCache
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2261
User\isBlocked
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:1893
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:594
$keys
$keys
Definition: testCompression.php:72
NS_USER
const NS_USER
Definition: Defines.php:71
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:471
$wgLearnerMemberSince
$wgLearnerMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9391
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1443
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:3866
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:2658
User\$mEmailToken
string $mEmailToken
Definition: User.php:141
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:5714
User\equals
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:4468
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user data from the database.
Definition: User.php:1261
User\$mName
string $mName
Definition: User.php:124
User\$mRegistration
string $mRegistration
Definition: User.php:145
MediaWiki\Block\AbstractBlock
Definition: AbstractBlock.php:38
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for the wiki.
Definition: DefaultSettings.php:4867
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:91
User\$mRequest
WebRequest $mRequest
Definition: User.php:202
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:644
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:3132
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:2609
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:5709
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:1009
User\makeUpdateConditions
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1458
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1769
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:3658
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:3769
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:2798
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1212
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:2642
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:218
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:891
ExternalUserNames\isExternal
static isExternal( $username)
Tells whether the username is external or not.
Definition: ExternalUserNames.php:137
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2062
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:5347
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3323
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:2866
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:3993
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1064
Wikimedia\Rdbms\IDatabase\delete
delete( $table, $conds, $fname=__METHOD__)
Delete all rows in a table that match a condition.
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:459
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:4090
User\setEditCountInternal
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:4338
User\$mEditCount
int $mEditCount
Definition: User.php:147
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:139
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4118
User\$mFrom
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:167
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3072
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:2721
$type
$type
Definition: testCompression.php:52