MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
28 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
37 use Wikimedia\IPSet;
38 use Wikimedia\IPUtils;
42 use Wikimedia\ScopedCallback;
43 
54 class User implements IDBAccessObject, UserIdentity {
55  use ProtectedHookAccessorTrait;
56 
60  public const TOKEN_LENGTH = 32;
61 
65  public const INVALID_TOKEN = '*** INVALID ***';
66 
71  private const VERSION = 16;
72 
78  public const GETOPTIONS_EXCLUDE_DEFAULTS = UserOptionsLookup::EXCLUDE_DEFAULTS;
79 
83  public const CHECK_USER_RIGHTS = true;
84 
88  public const IGNORE_USER_RIGHTS = false;
89 
97  protected static $mCacheVars = [
98  // user table
99  'mId',
100  'mName',
101  'mRealName',
102  'mEmail',
103  'mTouched',
104  'mToken',
105  'mEmailAuthenticated',
106  'mEmailToken',
107  'mEmailTokenExpires',
108  'mRegistration',
109  'mEditCount',
110  // actor table
111  'mActorId',
112  ];
113 
115  // @{
117  public $mId;
119  public $mName;
121  protected $mActorId;
123  public $mRealName;
124 
126  public $mEmail;
128  public $mTouched;
130  protected $mQuickTouched;
132  protected $mToken;
136  protected $mEmailToken;
140  protected $mRegistration;
142  protected $mEditCount;
143  // @}
144 
145  // @{
149  protected $mLoadedItems = [];
150  // @}
151 
162  public $mFrom;
163 
168  protected $mDatePreference;
175  public $mBlockedby;
177  protected $mHash;
183  protected $mBlockreason;
185  protected $mGlobalBlock;
187  protected $mLocked;
194  public $mHideName;
195 
197  private $mRequest;
198 
204  public $mBlock;
205 
207  protected $mAllowUsertalk;
208 
210  private $mBlockedFromCreateAccount = false;
211 
213  protected $queryFlagsUsed = self::READ_NORMAL;
214 
216  public static $idCacheByName = [];
217 
229  public function __construct() {
230  $this->clearInstanceCache( 'defaults' );
231  }
232 
236  public function __toString() {
237  return (string)$this->getName();
238  }
239 
240  public function &__get( $name ) {
241  // A shortcut for $mRights deprecation phase
242  if ( $name === 'mRights' ) {
243  $copy = $this->getRights();
244  return $copy;
245  } elseif ( $name === 'mOptions' ) {
246  wfDeprecated( 'User::$mOptions', '1.35' );
247  $options = $this->getOptions();
248  return $options;
249  } elseif ( !property_exists( $this, $name ) ) {
250  // T227688 - do not break $u->foo['bar'] = 1
251  wfLogWarning( 'tried to get non-existent property' );
252  $this->$name = null;
253  return $this->$name;
254  } else {
255  wfLogWarning( 'tried to get non-visible property' );
256  $null = null;
257  return $null;
258  }
259  }
260 
261  public function __set( $name, $value ) {
262  // A shortcut for $mRights deprecation phase, only known legitimate use was for
263  // testing purposes, other uses seem bad in principle
264  if ( $name === 'mRights' ) {
265  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
266  $this,
267  $value === null ? [] : $value
268  );
269  } elseif ( $name === 'mOptions' ) {
270  wfDeprecated( 'User::$mOptions', '1.35' );
271  MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $this );
272  foreach ( $value as $key => $val ) {
273  $this->setOption( $key, $val );
274  }
275  } elseif ( !property_exists( $this, $name ) ) {
276  $this->$name = $value;
277  } else {
278  wfLogWarning( 'tried to set non-visible property' );
279  }
280  }
281 
296  public function isSafeToLoad() {
297  global $wgFullyInitialised;
298 
299  // The user is safe to load if:
300  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
301  // * mLoadedItems === true (already loaded)
302  // * mFrom !== 'session' (sessions not involved at all)
303 
304  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
305  $this->mLoadedItems === true || $this->mFrom !== 'session';
306  }
307 
313  public function load( $flags = self::READ_NORMAL ) {
314  global $wgFullyInitialised;
315 
316  if ( $this->mLoadedItems === true ) {
317  return;
318  }
319 
320  // Set it now to avoid infinite recursion in accessors
321  $oldLoadedItems = $this->mLoadedItems;
322  $this->mLoadedItems = true;
323  $this->queryFlagsUsed = $flags;
324 
325  // If this is called too early, things are likely to break.
326  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
328  ->warning( 'User::loadFromSession called before the end of Setup.php', [
329  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
330  ] );
331  $this->loadDefaults();
332  $this->mLoadedItems = $oldLoadedItems;
333  return;
334  }
335 
336  switch ( $this->mFrom ) {
337  case 'defaults':
338  $this->loadDefaults();
339  break;
340  case 'id':
341  // Make sure this thread sees its own changes, if the ID isn't 0
342  if ( $this->mId != 0 ) {
343  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
344  if ( $lb->hasOrMadeRecentMasterChanges() ) {
345  $flags |= self::READ_LATEST;
346  $this->queryFlagsUsed = $flags;
347  }
348  }
349 
350  $this->loadFromId( $flags );
351  break;
352  case 'actor':
353  case 'name':
354  // Make sure this thread sees its own changes
355  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
356  if ( $lb->hasOrMadeRecentMasterChanges() ) {
357  $flags |= self::READ_LATEST;
358  $this->queryFlagsUsed = $flags;
359  }
360 
361  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
362  $row = wfGetDB( $index )->selectRow(
363  'actor',
364  [ 'actor_id', 'actor_user', 'actor_name' ],
365  $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
366  __METHOD__,
367  $options
368  );
369 
370  if ( !$row ) {
371  // Ugh.
372  $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
373  } elseif ( $row->actor_user ) {
374  $this->mId = $row->actor_user;
375  $this->loadFromId( $flags );
376  } else {
377  $this->loadDefaults( $row->actor_name, $row->actor_id );
378  }
379  break;
380  case 'session':
381  if ( !$this->loadFromSession() ) {
382  // Loading from session failed. Load defaults.
383  $this->loadDefaults();
384  }
385  $this->getHookRunner()->onUserLoadAfterLoadFromSession( $this );
386  break;
387  default:
388  throw new UnexpectedValueException(
389  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
390  }
391  }
392 
398  public function loadFromId( $flags = self::READ_NORMAL ) {
399  if ( $this->mId == 0 ) {
400  // Anonymous users are not in the database (don't need cache)
401  $this->loadDefaults();
402  return false;
403  }
404 
405  // Try cache (unless this needs data from the master DB).
406  // NOTE: if this thread called saveSettings(), the cache was cleared.
407  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
408  if ( $latest ) {
409  if ( !$this->loadFromDatabase( $flags ) ) {
410  // Can't load from ID
411  return false;
412  }
413  } else {
414  $this->loadFromCache();
415  }
416 
417  $this->mLoadedItems = true;
418  $this->queryFlagsUsed = $flags;
419 
420  return true;
421  }
422 
428  public static function purge( $dbDomain, $userId ) {
429  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
430  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
431  $cache->delete( $key );
432  }
433 
439  protected function getCacheKey( WANObjectCache $cache ) {
440  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
441 
442  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
443  }
444 
451  $id = $this->getId();
452 
453  return $id ? [ $this->getCacheKey( $cache ) ] : [];
454  }
455 
462  protected function loadFromCache() {
463  global $wgFullyInitialised;
464 
465  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
466  $data = $cache->getWithSetCallback(
467  $this->getCacheKey( $cache ),
468  $cache::TTL_HOUR,
469  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache, $wgFullyInitialised ) {
470  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
471  wfDebug( "User: cache miss for user {$this->mId}" );
472 
473  $this->loadFromDatabase( self::READ_NORMAL );
474 
475  $data = [];
476  foreach ( self::$mCacheVars as $name ) {
477  $data[$name] = $this->$name;
478  }
479 
480  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
481 
482  if ( $wgFullyInitialised ) {
483  $groupMemberships = MediaWikiServices::getInstance()
484  ->getUserGroupManager()
485  ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
486 
487  // if a user group membership is about to expire, the cache needs to
488  // expire at that time (T163691)
489  foreach ( $groupMemberships as $ugm ) {
490  if ( $ugm->getExpiry() ) {
491  $secondsUntilExpiry =
492  wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
493 
494  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
495  $ttl = $secondsUntilExpiry;
496  }
497  }
498  }
499  }
500 
501  return $data;
502  },
503  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
504  );
505 
506  // Restore from cache
507  foreach ( self::$mCacheVars as $name ) {
508  $this->$name = $data[$name];
509  }
510 
511  return true;
512  }
513 
515  // @{
516 
533  public static function newFromName( $name, $validate = 'valid' ) {
534  if ( $validate === true ) {
535  $validate = 'valid';
536  }
537  $name = self::getCanonicalName( $name, $validate );
538  if ( $name === false ) {
539  return false;
540  }
541 
542  // Create unloaded user object
543  $u = new User;
544  $u->mName = $name;
545  $u->mFrom = 'name';
546  $u->setItemLoaded( 'name' );
547 
548  return $u;
549  }
550 
557  public static function newFromId( $id ) {
558  $u = new User;
559  $u->mId = $id;
560  $u->mFrom = 'id';
561  $u->setItemLoaded( 'id' );
562  return $u;
563  }
564 
572  public static function newFromActorId( $id ) {
573  $u = new User;
574  $u->mActorId = $id;
575  $u->mFrom = 'actor';
576  $u->setItemLoaded( 'actor' );
577  return $u;
578  }
579 
589  public static function newFromIdentity( UserIdentity $identity ) {
590  return MediaWikiServices::getInstance()
591  ->getUserFactory()
592  ->newFromUserIdentity( $identity );
593  }
594 
608  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
609  // Stop-gap solution for the problem described in T222212.
610  // Force the User ID and Actor ID to zero for users loaded from the database
611  // of another wiki, to prevent subtle data corruption and confusing failure modes.
612  if ( $dbDomain !== false ) {
613  $userId = 0;
614  $actorId = 0;
615  }
616 
617  $user = new User;
618  $user->mFrom = 'defaults';
619 
620  if ( $actorId !== null ) {
621  $user->mActorId = (int)$actorId;
622  if ( $user->mActorId !== 0 ) {
623  $user->mFrom = 'actor';
624  }
625  $user->setItemLoaded( 'actor' );
626  }
627 
628  if ( $userName !== null && $userName !== '' ) {
629  $user->mName = $userName;
630  $user->mFrom = 'name';
631  $user->setItemLoaded( 'name' );
632  }
633 
634  if ( $userId !== null ) {
635  $user->mId = (int)$userId;
636  if ( $user->mId !== 0 ) {
637  $user->mFrom = 'id';
638  }
639  $user->setItemLoaded( 'id' );
640  }
641 
642  if ( $user->mFrom === 'defaults' ) {
643  throw new InvalidArgumentException(
644  'Cannot create a user with no name, no ID, and no actor ID'
645  );
646  }
647 
648  return $user;
649  }
650 
662  public static function newFromConfirmationCode( $code, $flags = 0 ) {
663  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
664  ? wfGetDB( DB_MASTER )
665  : wfGetDB( DB_REPLICA );
666 
667  $id = $db->selectField(
668  'user',
669  'user_id',
670  [
671  'user_email_token' => md5( $code ),
672  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
673  ],
674  __METHOD__
675  );
676 
677  return $id ? self::newFromId( $id ) : null;
678  }
679 
687  public static function newFromSession( WebRequest $request = null ) {
688  $user = new User;
689  $user->mFrom = 'session';
690  $user->mRequest = $request;
691  return $user;
692  }
693 
709  public static function newFromRow( $row, $data = null ) {
710  $user = new User;
711  $user->loadFromRow( $row, $data );
712  return $user;
713  }
714 
750  public static function newSystemUser( $name, $options = [] ) {
751  $options += [
752  'validate' => 'valid',
753  'create' => true,
754  'steal' => false,
755  ];
756 
757  $name = self::getCanonicalName( $name, $options['validate'] );
758  if ( $name === false ) {
759  return null;
760  }
761 
762  $dbr = wfGetDB( DB_REPLICA );
763  $userQuery = self::getQueryInfo();
764  $row = $dbr->selectRow(
765  $userQuery['tables'],
766  $userQuery['fields'],
767  [ 'user_name' => $name ],
768  __METHOD__,
769  [],
770  $userQuery['joins']
771  );
772  if ( !$row ) {
773  // Try the master database...
774  $dbw = wfGetDB( DB_MASTER );
775  $row = $dbw->selectRow(
776  $userQuery['tables'],
777  $userQuery['fields'],
778  [ 'user_name' => $name ],
779  __METHOD__,
780  [],
781  $userQuery['joins']
782  );
783  }
784 
785  if ( !$row ) {
786  // No user. Create it?
787  // @phan-suppress-next-line PhanImpossibleCondition
788  if ( !$options['create'] ) {
789  // No.
790  return null;
791  }
792 
793  // If it's a reserved user that had an anonymous actor created for it at
794  // some point, we need special handling.
795  if ( !self::isValidUserName( $name ) || self::isUsableName( $name ) ) {
796  // Not reserved, so just create it.
797  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
798  }
799 
800  // It is reserved. Check for an anonymous actor row.
801  $dbw = wfGetDB( DB_MASTER );
802  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $name ) {
803  $row = $dbw->selectRow(
804  'actor',
805  [ 'actor_id' ],
806  [ 'actor_name' => $name, 'actor_user' => null ],
807  $fname,
808  [ 'FOR UPDATE' ]
809  );
810  if ( !$row ) {
811  // No anonymous actor.
812  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
813  }
814 
815  // There is an anonymous actor. Delete the actor row so we can create the user,
816  // then restore the old actor_id so as to not break existing references.
817  // @todo If MediaWiki ever starts using foreign keys for `actor`, this will break things.
818  $dbw->delete( 'actor', [ 'actor_id' => $row->actor_id ], $fname );
819  $user = self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
820  $dbw->update(
821  'actor',
822  [ 'actor_id' => $row->actor_id ],
823  [ 'actor_id' => $user->getActorId() ],
824  $fname
825  );
826  $user->clearInstanceCache( 'id' );
827  $user->invalidateCache();
828  return $user;
829  } );
830  }
831 
832  $user = self::newFromRow( $row );
833 
834  if ( !$user->isSystemUser() ) {
835  // User exists. Steal it?
836  // @phan-suppress-next-line PhanRedundantCondition
837  if ( !$options['steal'] ) {
838  return null;
839  }
840 
841  MediaWikiServices::getInstance()->getAuthManager()->revokeAccessForUser( $name );
842 
843  $user->invalidateEmail();
844  $user->mToken = self::INVALID_TOKEN;
845  $user->saveSettings();
846  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
847  }
848 
849  return $user;
850  }
851 
852  // @}
853 
859  public static function whoIs( $id ) {
860  return UserCache::singleton()->getProp( $id, 'name' );
861  }
862 
869  public static function whoIsReal( $id ) {
870  return UserCache::singleton()->getProp( $id, 'real_name' );
871  }
872 
879  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
880  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
881  $name = (string)$name;
882  $nt = Title::makeTitleSafe( NS_USER, $name );
883  if ( $nt === null ) {
884  // Illegal name
885  return null;
886  }
887 
888  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
889  return self::$idCacheByName[$name] === null ? null : (int)self::$idCacheByName[$name];
890  }
891 
892  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
893  $db = wfGetDB( $index );
894 
895  $s = $db->selectRow(
896  'user',
897  [ 'user_id' ],
898  [ 'user_name' => $nt->getText() ],
899  __METHOD__,
900  $options
901  );
902 
903  if ( $s === false ) {
904  $result = null;
905  } else {
906  $result = (int)$s->user_id;
907  }
908 
909  if ( count( self::$idCacheByName ) >= 1000 ) {
910  self::$idCacheByName = [];
911  }
912 
913  self::$idCacheByName[$name] = $result;
914 
915  return $result;
916  }
917 
921  public static function resetIdByNameCache() {
922  self::$idCacheByName = [];
923  }
924 
943  public static function isIP( $name ) {
944  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
945  || IPUtils::isIPv6( $name );
946  }
947 
955  public function isIPRange() {
956  return IPUtils::isValidRange( $this->mName );
957  }
958 
971  public static function isValidUserName( $name ) {
972  return MediaWikiServices::getInstance()->getUserNameUtils()->isValid( $name );
973  }
974 
987  public static function isUsableName( $name ) {
988  return MediaWikiServices::getInstance()->getUserNameUtils()->isUsable( $name );
989  }
990 
1001  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1002  if ( $groups === [] ) {
1003  return UserArrayFromResult::newFromIDs( [] );
1004  }
1005 
1006  $groups = array_unique( (array)$groups );
1007  $limit = min( 5000, $limit );
1008 
1009  $conds = [ 'ug_group' => $groups ];
1010  if ( $after !== null ) {
1011  $conds[] = 'ug_user > ' . (int)$after;
1012  }
1013 
1014  $dbr = wfGetDB( DB_REPLICA );
1015  $ids = $dbr->selectFieldValues(
1016  'user_groups',
1017  'ug_user',
1018  $conds,
1019  __METHOD__,
1020  [
1021  'DISTINCT' => true,
1022  'ORDER BY' => 'ug_user',
1023  'LIMIT' => $limit,
1024  ]
1025  ) ?: [];
1026  return UserArray::newFromIDs( $ids );
1027  }
1028 
1042  public static function isCreatableName( $name ) {
1043  return MediaWikiServices::getInstance()->getUserNameUtils()->isCreatable( $name );
1044  }
1045 
1052  public function isValidPassword( $password ) {
1053  // simple boolean wrapper for checkPasswordValidity
1054  return $this->checkPasswordValidity( $password )->isGood();
1055  }
1056 
1078  public function checkPasswordValidity( $password ) {
1079  global $wgPasswordPolicy;
1080 
1081  $upp = new UserPasswordPolicy(
1082  $wgPasswordPolicy['policies'],
1083  $wgPasswordPolicy['checks']
1084  );
1085 
1086  $status = Status::newGood( [] );
1087  $result = false; // init $result to false for the internal checks
1088 
1089  if ( !$this->getHookRunner()->onIsValidPassword( $password, $result, $this ) ) {
1090  $status->error( $result );
1091  return $status;
1092  }
1093 
1094  if ( $result === false ) {
1095  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1096  return $status;
1097  }
1098 
1099  if ( $result === true ) {
1100  return $status;
1101  }
1102 
1103  $status->error( $result );
1104  return $status; // the isValidPassword hook set a string $result and returned true
1105  }
1106 
1122  public static function getCanonicalName( $name, $validate = 'valid' ) {
1123  // Backwards compatibility with strings / false
1124  $validationLevels = [
1125  'valid' => UserNameUtils::RIGOR_VALID,
1126  'usable' => UserNameUtils::RIGOR_USABLE,
1127  'creatable' => UserNameUtils::RIGOR_CREATABLE
1128  ];
1129 
1130  if ( $validate === false ) {
1131  $validation = UserNameUtils::RIGOR_NONE;
1132  } elseif ( array_key_exists( $validate, $validationLevels ) ) {
1133  $validation = $validationLevels[ $validate ];
1134  } else {
1135  // Not a recognized value, probably a test for unsupported validation
1136  // levels, regardless, just pass it along
1137  $validation = $validate;
1138  }
1139 
1140  return MediaWikiServices::getInstance()
1141  ->getUserNameUtils()
1142  ->getCanonical( (string)$name, $validation );
1143  }
1144 
1154  public function loadDefaults( $name = false, $actorId = null ) {
1155  $this->mId = 0;
1156  $this->mName = $name;
1157  $this->mActorId = $actorId;
1158  $this->mRealName = '';
1159  $this->mEmail = '';
1160 
1161  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1162  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1163  if ( $loggedOut !== 0 ) {
1164  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1165  } else {
1166  $this->mTouched = '1'; # Allow any pages to be cached
1167  }
1168 
1169  $this->mToken = null; // Don't run cryptographic functions till we need a token
1170  $this->mEmailAuthenticated = null;
1171  $this->mEmailToken = '';
1172  $this->mEmailTokenExpires = null;
1173  $this->mRegistration = wfTimestamp( TS_MW );
1174 
1175  $this->getHookRunner()->onUserLoadDefaults( $this, $name );
1176  }
1177 
1190  public function isItemLoaded( $item, $all = 'all' ) {
1191  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1192  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1193  }
1194 
1200  protected function setItemLoaded( $item ) {
1201  if ( is_array( $this->mLoadedItems ) ) {
1202  $this->mLoadedItems[$item] = true;
1203  }
1204  }
1205 
1211  private function loadFromSession() {
1212  // MediaWiki\Session\Session already did the necessary authentication of the user
1213  // returned here, so just use it if applicable.
1214  $session = $this->getRequest()->getSession();
1215  $user = $session->getUser();
1216  if ( $user->isLoggedIn() ) {
1217  $this->loadFromUserObject( $user );
1218 
1219  // Other code expects these to be set in the session, so set them.
1220  $session->set( 'wsUserID', $this->getId() );
1221  $session->set( 'wsUserName', $this->getName() );
1222  $session->set( 'wsToken', $this->getToken() );
1223 
1224  return true;
1225  }
1226 
1227  return false;
1228  }
1229 
1235  public function trackBlockWithCookie() {
1236  wfDeprecated( __METHOD__, '1.34' );
1237  // Obsolete.
1238  // MediaWiki::preOutputCommit() handles this whenever possible.
1239  }
1240 
1248  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1249  // Paranoia
1250  $this->mId = intval( $this->mId );
1251 
1252  if ( !$this->mId ) {
1253  // Anonymous users are not in the database
1254  $this->loadDefaults();
1255  return false;
1256  }
1257 
1258  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1259  $db = wfGetDB( $index );
1260 
1261  $userQuery = self::getQueryInfo();
1262  $s = $db->selectRow(
1263  $userQuery['tables'],
1264  $userQuery['fields'],
1265  [ 'user_id' => $this->mId ],
1266  __METHOD__,
1267  $options,
1268  $userQuery['joins']
1269  );
1270 
1271  $this->queryFlagsUsed = $flags;
1272  $this->getHookRunner()->onUserLoadFromDatabase( $this, $s );
1273 
1274  if ( $s !== false ) {
1275  // Initialise user table data
1276  $this->loadFromRow( $s );
1277  $this->getEditCount(); // revalidation for nulls
1278  return true;
1279  }
1280 
1281  // Invalid user_id
1282  $this->mId = 0;
1283  $this->loadDefaults();
1284 
1285  return false;
1286  }
1287 
1300  protected function loadFromRow( $row, $data = null ) {
1301  if ( !is_object( $row ) ) {
1302  throw new InvalidArgumentException( '$row must be an object' );
1303  }
1304 
1305  $all = true;
1306 
1307  if ( isset( $row->actor_id ) ) {
1308  $this->mActorId = (int)$row->actor_id;
1309  if ( $this->mActorId !== 0 ) {
1310  $this->mFrom = 'actor';
1311  }
1312  $this->setItemLoaded( 'actor' );
1313  } else {
1314  $all = false;
1315  }
1316 
1317  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1318  $this->mName = $row->user_name;
1319  $this->mFrom = 'name';
1320  $this->setItemLoaded( 'name' );
1321  } else {
1322  $all = false;
1323  }
1324 
1325  if ( isset( $row->user_real_name ) ) {
1326  $this->mRealName = $row->user_real_name;
1327  $this->setItemLoaded( 'realname' );
1328  } else {
1329  $all = false;
1330  }
1331 
1332  if ( isset( $row->user_id ) ) {
1333  $this->mId = intval( $row->user_id );
1334  if ( $this->mId !== 0 ) {
1335  $this->mFrom = 'id';
1336  }
1337  $this->setItemLoaded( 'id' );
1338  } else {
1339  $all = false;
1340  }
1341 
1342  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1343  self::$idCacheByName[$row->user_name] = $row->user_id;
1344  }
1345 
1346  if ( isset( $row->user_editcount ) ) {
1347  $this->mEditCount = $row->user_editcount;
1348  } else {
1349  $all = false;
1350  }
1351 
1352  if ( isset( $row->user_touched ) ) {
1353  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1354  } else {
1355  $all = false;
1356  }
1357 
1358  if ( isset( $row->user_token ) ) {
1359  // The definition for the column is binary(32), so trim the NULs
1360  // that appends. The previous definition was char(32), so trim
1361  // spaces too.
1362  $this->mToken = rtrim( $row->user_token, " \0" );
1363  if ( $this->mToken === '' ) {
1364  $this->mToken = null;
1365  }
1366  } else {
1367  $all = false;
1368  }
1369 
1370  if ( isset( $row->user_email ) ) {
1371  $this->mEmail = $row->user_email;
1372  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1373  $this->mEmailToken = $row->user_email_token;
1374  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1375  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1376  } else {
1377  $all = false;
1378  }
1379 
1380  if ( $all ) {
1381  $this->mLoadedItems = true;
1382  }
1383 
1384  if ( is_array( $data ) ) {
1385 
1386  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1387  MediaWikiServices::getInstance()
1388  ->getUserGroupManager()
1389  ->loadGroupMembershipsFromArray(
1390  $this,
1391  $data['user_groups'],
1392  $this->queryFlagsUsed
1393  );
1394  }
1395  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1396  MediaWikiServices::getInstance()
1397  ->getUserOptionsManager()
1398  ->loadUserOptions( $this, $this->queryFlagsUsed, $data['user_properties'] );
1399  }
1400  }
1401  }
1402 
1408  protected function loadFromUserObject( $user ) {
1409  $user->load();
1410  foreach ( self::$mCacheVars as $var ) {
1411  $this->$var = $user->$var;
1412  }
1413  }
1414 
1430  public function addAutopromoteOnceGroups( $event ) {
1431  return MediaWikiServices::getInstance()
1432  ->getUserGroupManager()
1433  ->addUserToAutopromoteOnceGroups( $this, $event );
1434  }
1435 
1445  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1446  if ( $this->mTouched ) {
1447  // CAS check: only update if the row wasn't changed sicne it was loaded.
1448  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1449  }
1450 
1451  return $conditions;
1452  }
1453 
1464  public function checkAndSetTouched() {
1465  $this->load();
1466 
1467  if ( !$this->mId ) {
1468  return false; // anon
1469  }
1470 
1471  // Get a new user_touched that is higher than the old one
1472  $newTouched = $this->newTouchedTimestamp();
1473 
1474  $dbw = wfGetDB( DB_MASTER );
1475  $dbw->update( 'user',
1476  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1477  $this->makeUpdateConditions( $dbw, [
1478  'user_id' => $this->mId,
1479  ] ),
1480  __METHOD__
1481  );
1482  $success = ( $dbw->affectedRows() > 0 );
1483 
1484  if ( $success ) {
1485  $this->mTouched = $newTouched;
1486  $this->clearSharedCache( 'changed' );
1487  } else {
1488  // Clears on failure too since that is desired if the cache is stale
1489  $this->clearSharedCache( 'refresh' );
1490  }
1491 
1492  return $success;
1493  }
1494 
1502  public function clearInstanceCache( $reloadFrom = false ) {
1503  global $wgFullyInitialised;
1504 
1505  $this->mDatePreference = null;
1506  $this->mBlockedby = -1; # Unset
1507  $this->mHash = false;
1508  $this->mEditCount = null;
1509 
1510  if ( $wgFullyInitialised && $this->mFrom ) {
1511  $services = MediaWikiServices::getInstance();
1512  $services->getPermissionManager()->invalidateUsersRightsCache( $this );
1513  $services->getUserOptionsManager()->clearUserOptionsCache( $this );
1514  $services->getTalkPageNotificationManager()->clearInstanceCache( $this );
1515  $services->getUserGroupManager()->clearCache( $this );
1516  $services->getUserEditTracker()->clearUserEditCache( $this );
1517  }
1518 
1519  if ( $reloadFrom ) {
1520  $this->mLoadedItems = [];
1521  $this->mFrom = $reloadFrom;
1522  }
1523  }
1524 
1532  public static function getDefaultOptions() {
1533  return MediaWikiServices::getInstance()
1534  ->getUserOptionsLookup()
1535  ->getDefaultOptions();
1536  }
1537 
1545  public static function getDefaultOption( $opt ) {
1546  return MediaWikiServices::getInstance()
1547  ->getUserOptionsLookup()
1548  ->getDefaultOption( $opt );
1549  }
1550 
1560  private function getBlockedStatus( $fromReplica = true ) {
1561  if ( $this->mBlockedby != -1 ) {
1562  return;
1563  }
1564 
1565  wfDebug( __METHOD__ . ": checking blocked status for " . $this->getName() );
1566 
1567  // Initialize data...
1568  // Otherwise something ends up stomping on $this->mBlockedby when
1569  // things get lazy-loaded later, causing false positive block hits
1570  // due to -1 !== 0. Probably session-related... Nothing should be
1571  // overwriting mBlockedby, surely?
1572  $this->load();
1573 
1574  // TODO: Block checking shouldn't really be done from the User object. Block
1575  // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1576  // which need more knowledge of the request context than the User should have.
1577  // Since we do currently check blocks from the User, we have to do the following
1578  // here:
1579  // - Check if this is the user associated with the main request
1580  // - If so, pass the relevant request information to the block manager
1581  $request = null;
1582 
1583  // The session user is set up towards the end of Setup.php. Until then,
1584  // assume it's a logged-out user.
1585  $sessionUser = RequestContext::getMain()->getUser();
1586  $globalUserName = $sessionUser->isSafeToLoad()
1587  ? $sessionUser->getName()
1588  : IPUtils::sanitizeIP( $sessionUser->getRequest()->getIP() );
1589 
1590  if ( $this->getName() === $globalUserName ) {
1591  // This is the global user, so we need to pass the request
1592  $request = $this->getRequest();
1593  }
1594 
1595  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1596  $this,
1597  $request,
1598  $fromReplica
1599  );
1600 
1601  if ( $block ) {
1602  $this->mBlock = $block;
1603  $this->mBlockedby = $block->getByName();
1604  $this->mBlockreason = $block->getReason();
1605  $this->mHideName = $block->getHideName();
1606  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1607  } else {
1608  $this->mBlock = null;
1609  $this->mBlockedby = '';
1610  $this->mBlockreason = '';
1611  $this->mHideName = 0;
1612  $this->mAllowUsertalk = false;
1613  }
1614  }
1615 
1624  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1625  wfDeprecated( __METHOD__, '1.34' );
1626  return MediaWikiServices::getInstance()->getBlockManager()
1627  ->isDnsBlacklisted( $ip, $checkWhitelist );
1628  }
1629 
1638  public function inDnsBlacklist( $ip, $bases ) {
1639  wfDeprecated( __METHOD__, '1.34' );
1640 
1641  $found = false;
1642  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1643  if ( IPUtils::isIPv4( $ip ) ) {
1644  // Reverse IP, T23255
1645  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1646 
1647  foreach ( (array)$bases as $base ) {
1648  // Make hostname
1649  // If we have an access key, use that too (ProjectHoneypot, etc.)
1650  $basename = $base;
1651  if ( is_array( $base ) ) {
1652  if ( count( $base ) >= 2 ) {
1653  // Access key is 1, base URL is 0
1654  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1655  } else {
1656  $host = "$ipReversed.{$base[0]}";
1657  }
1658  $basename = $base[0];
1659  } else {
1660  $host = "$ipReversed.$base";
1661  }
1662 
1663  // Send query
1664  $ipList = gethostbynamel( $host );
1665 
1666  if ( $ipList ) {
1667  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1668  $found = true;
1669  break;
1670  }
1671 
1672  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1673  }
1674  }
1675 
1676  return $found;
1677  }
1678 
1686  public static function isLocallyBlockedProxy( $ip ) {
1687  wfDeprecated( __METHOD__, '1.34' );
1688 
1689  global $wgProxyList;
1690 
1691  if ( !$wgProxyList ) {
1692  return false;
1693  }
1694 
1695  if ( !is_array( $wgProxyList ) ) {
1696  // Load values from the specified file
1697  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1698  }
1699 
1700  $resultProxyList = [];
1701  $deprecatedIPEntries = [];
1702 
1703  // backward compatibility: move all ip addresses in keys to values
1704  foreach ( $wgProxyList as $key => $value ) {
1705  $keyIsIP = IPUtils::isIPAddress( $key );
1706  $valueIsIP = IPUtils::isIPAddress( $value );
1707  if ( $keyIsIP && !$valueIsIP ) {
1708  $deprecatedIPEntries[] = $key;
1709  $resultProxyList[] = $key;
1710  } elseif ( $keyIsIP && $valueIsIP ) {
1711  $deprecatedIPEntries[] = $key;
1712  $resultProxyList[] = $key;
1713  $resultProxyList[] = $value;
1714  } else {
1715  $resultProxyList[] = $value;
1716  }
1717  }
1718 
1719  if ( $deprecatedIPEntries ) {
1721  'Use of IP addresses in the keys of $wgProxyList is deprecated since MediaWiki 1.30. ' .
1722  'Found the following IP addresses in keys: ' . implode( ', ', $deprecatedIPEntries ) .
1723  ', please move them to values.', '1.30', false, false );
1724  }
1725 
1726  $proxyListIPSet = new IPSet( $resultProxyList );
1727  return $proxyListIPSet->match( $ip );
1728  }
1729 
1735  public function isPingLimitable() {
1736  global $wgRateLimitsExcludedIPs;
1737  if ( IPUtils::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1738  // No other good way currently to disable rate limits
1739  // for specific IPs. :P
1740  // But this is a crappy hack and should die.
1741  return false;
1742  }
1743  return !$this->isAllowed( 'noratelimit' );
1744  }
1745 
1760  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1761  // Call the 'PingLimiter' hook
1762  $result = false;
1763  if ( !$this->getHookRunner()->onPingLimiter( $this, $action, $result, $incrBy ) ) {
1764  return $result;
1765  }
1766 
1767  global $wgRateLimits;
1768  if ( !isset( $wgRateLimits[$action] ) ) {
1769  return false;
1770  }
1771 
1772  $limits = array_merge(
1773  [ '&can-bypass' => true ],
1774  $wgRateLimits[$action]
1775  );
1776 
1777  // Some groups shouldn't trigger the ping limiter, ever
1778  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1779  return false;
1780  }
1781 
1782  $keys = [];
1783  $id = $this->getId();
1784  $userLimit = false;
1785  $isNewbie = $this->isNewbie();
1787 
1788  if ( $id == 0 ) {
1789  // "shared anon" limit, for all anons combined
1790  if ( isset( $limits['anon'] ) ) {
1791  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1792  }
1793  } elseif ( isset( $limits['user'] ) ) {
1794  // limits for logged-in users
1795  $userLimit = $limits['user'];
1796  }
1797 
1798  if ( $isNewbie ) {
1799  // "per ip" limit for anons and newbie users
1800  if ( isset( $limits['ip'] ) ) {
1801  $ip = $this->getRequest()->getIP();
1802  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
1803  }
1804  // "per subnet" limit for anons and newbie users
1805  if ( isset( $limits['subnet'] ) ) {
1806  $ip = $this->getRequest()->getIP();
1807  $subnet = IPUtils::getSubnet( $ip );
1808  if ( $subnet !== false ) {
1809  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
1810  }
1811  }
1812  }
1813 
1814  // Check for group-specific permissions
1815  // If more than one group applies, use the group with the highest limit ratio (max/period)
1816  foreach ( $this->getGroups() as $group ) {
1817  if ( isset( $limits[$group] ) ) {
1818  if ( $userLimit === false
1819  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1820  ) {
1821  $userLimit = $limits[$group];
1822  }
1823  }
1824  }
1825  // limits for newbie logged-in users (overrides all the normal user limits)
1826  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1827  $userLimit = $limits['newbie'];
1828  }
1829 
1830  // Set the user limit key
1831  if ( $userLimit !== false ) {
1832  // phan is confused because &can-bypass's value is a bool, so it assumes
1833  // that $userLimit is also a bool here.
1834  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1835  list( $max, $period ) = $userLimit;
1836  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s" );
1837  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
1838  }
1839 
1840  // ip-based limits for all ping-limitable users
1841  if ( isset( $limits['ip-all'] ) ) {
1842  $ip = $this->getRequest()->getIP();
1843  // ignore if user limit is more permissive
1844  if ( $isNewbie || $userLimit === false
1845  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1846  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip-all', $ip )] = $limits['ip-all'];
1847  }
1848  }
1849 
1850  // subnet-based limits for all ping-limitable users
1851  if ( isset( $limits['subnet-all'] ) ) {
1852  $ip = $this->getRequest()->getIP();
1853  $subnet = IPUtils::getSubnet( $ip );
1854  if ( $subnet !== false ) {
1855  // ignore if user limit is more permissive
1856  if ( $isNewbie || $userLimit === false
1857  || $limits['ip-all'][0] / $limits['ip-all'][1]
1858  > $userLimit[0] / $userLimit[1] ) {
1859  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet-all', $subnet )] = $limits['subnet-all'];
1860  }
1861  }
1862  }
1863 
1864  $triggered = false;
1865  foreach ( $keys as $key => $limit ) {
1866  // phan is confused because &can-bypass's value is a bool, so it assumes
1867  // that $userLimit is also a bool here.
1868  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1869  list( $max, $period ) = $limit;
1870  $summary = "(limit $max in {$period}s)";
1871  $count = $cache->get( $key );
1872  // Already pinged?
1873  if ( $count && $count >= $max ) {
1874  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
1875  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
1876  $triggered = true;
1877  } else {
1878  wfDebug( __METHOD__ . ": adding record for $key $summary" );
1879  if ( $incrBy > 0 ) {
1880  $cache->add( $key, 0, intval( $period ) ); // first ping
1881  }
1882  }
1883  if ( $incrBy > 0 ) {
1884  $cache->incrWithInit( $key, (int)$period, $incrBy, $incrBy );
1885  }
1886  }
1887 
1888  return $triggered;
1889  }
1890 
1902  public function isBlocked( $fromReplica = true ) {
1903  return $this->getBlock( $fromReplica ) instanceof AbstractBlock;
1904  }
1905 
1912  public function getBlock( $fromReplica = true ) {
1913  $this->getBlockedStatus( $fromReplica );
1914  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
1915  }
1916 
1928  public function isBlockedFrom( $title, $fromReplica = false ) {
1929  return MediaWikiServices::getInstance()->getPermissionManager()
1930  ->isBlockedFrom( $this, $title, $fromReplica );
1931  }
1932 
1937  public function blockedBy() {
1938  $this->getBlockedStatus();
1939  return $this->mBlockedby;
1940  }
1941 
1948  public function blockedFor() {
1949  $this->getBlockedStatus();
1950  return $this->mBlockreason;
1951  }
1952 
1957  public function getBlockId() {
1958  $this->getBlockedStatus();
1959  return ( $this->mBlock ? $this->mBlock->getId() : false );
1960  }
1961 
1970  public function isBlockedGlobally( $ip = '' ) {
1971  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
1972  }
1973 
1984  public function getGlobalBlock( $ip = '' ) {
1985  if ( $this->mGlobalBlock !== null ) {
1986  return $this->mGlobalBlock ?: null;
1987  }
1988  // User is already an IP?
1989  if ( IPUtils::isIPAddress( $this->getName() ) ) {
1990  $ip = $this->getName();
1991  } elseif ( !$ip ) {
1992  $ip = $this->getRequest()->getIP();
1993  }
1994  $blocked = false;
1995  $block = null;
1996  $this->getHookRunner()->onUserIsBlockedGlobally( $this, $ip, $blocked, $block );
1997 
1998  if ( $blocked && $block === null ) {
1999  // back-compat: UserIsBlockedGlobally didn't have $block param first
2000  $block = new SystemBlock( [
2001  'address' => $ip,
2002  'systemBlock' => 'global-block'
2003  ] );
2004  }
2005 
2006  $this->mGlobalBlock = $blocked ? $block : false;
2007  return $this->mGlobalBlock ?: null;
2008  }
2009 
2015  public function isLocked() {
2016  if ( $this->mLocked !== null ) {
2017  return $this->mLocked;
2018  }
2019  // Reset for hook
2020  $this->mLocked = false;
2021  $this->getHookRunner()->onUserIsLocked( $this, $this->mLocked );
2022  return $this->mLocked;
2023  }
2024 
2030  public function isHidden() {
2031  if ( $this->mHideName !== null ) {
2032  return (bool)$this->mHideName;
2033  }
2034  $this->getBlockedStatus();
2035  return (bool)$this->mHideName;
2036  }
2037 
2042  public function getId() {
2043  if ( $this->mId === null && $this->mName !== null &&
2044  ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
2045  ) {
2046  // Special case, we know the user is anonymous
2047  return 0;
2048  }
2049 
2050  if ( !$this->isItemLoaded( 'id' ) ) {
2051  // Don't load if this was initialized from an ID
2052  $this->load();
2053  }
2054 
2055  return (int)$this->mId;
2056  }
2057 
2062  public function setId( $v ) {
2063  $this->mId = $v;
2064  $this->clearInstanceCache( 'id' );
2065  }
2066 
2071  public function getName() {
2072  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2073  // Special case optimisation
2074  return $this->mName;
2075  }
2076 
2077  $this->load();
2078  if ( $this->mName === false ) {
2079  // Clean up IPs
2080  $this->mName = IPUtils::sanitizeIP( $this->getRequest()->getIP() );
2081  }
2082 
2083  return $this->mName;
2084  }
2085 
2099  public function setName( $str ) {
2100  $this->load();
2101  $this->mName = $str;
2102  }
2103 
2110  public function getActorId( IDatabase $dbw = null ) {
2111  if ( !$this->isItemLoaded( 'actor' ) ) {
2112  $this->load();
2113  }
2114 
2115  if ( !$this->mActorId && $dbw ) {
2116  $q = [
2117  'actor_user' => $this->getId() ?: null,
2118  'actor_name' => (string)$this->getName(),
2119  ];
2120  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2121  throw new CannotCreateActorException(
2122  'Cannot create an actor for a usable name that is not an existing user: ' .
2123  "user_id={$this->getId()} user_name=\"{$this->getName()}\""
2124  );
2125  }
2126  if ( $q['actor_name'] === '' ) {
2127  throw new CannotCreateActorException(
2128  'Cannot create an actor for a user with no name: ' .
2129  "user_id={$this->getId()} user_name=\"{$this->getName()}\""
2130  );
2131  }
2132  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2133  if ( $dbw->affectedRows() ) {
2134  $this->mActorId = (int)$dbw->insertId();
2135  } else {
2136  // Outdated cache?
2137  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2138  $this->mActorId = (int)$dbw->selectField(
2139  'actor',
2140  'actor_id',
2141  $q,
2142  __METHOD__,
2143  [ 'LOCK IN SHARE MODE' ]
2144  );
2145  if ( !$this->mActorId ) {
2146  throw new CannotCreateActorException(
2147  "Failed to create actor ID for " .
2148  "user_id={$this->getId()} user_name=\"{$this->getName()}\""
2149  );
2150  }
2151  }
2152  $this->invalidateCache();
2153  $this->setItemLoaded( 'actor' );
2154  }
2155 
2156  return (int)$this->mActorId;
2157  }
2158 
2163  public function getTitleKey() {
2164  return str_replace( ' ', '_', $this->getName() );
2165  }
2166 
2172  public function getNewtalk() {
2173  wfDeprecated( __METHOD__, '1.35' );
2174  return MediaWikiServices::getInstance()
2175  ->getTalkPageNotificationManager()
2176  ->userHasNewMessages( $this );
2177  }
2178 
2195  public function getNewMessageLinks() {
2196  wfDeprecated( __METHOD__, '1.35' );
2197  $talks = [];
2198  if ( !$this->getHookRunner()->onUserRetrieveNewTalks( $this, $talks ) ) {
2199  return $talks;
2200  }
2201 
2202  $services = MediaWikiServices::getInstance();
2203  $userHasNewMessages = $services->getTalkPageNotificationManager()
2204  ->userHasNewMessages( $this );
2205  if ( !$userHasNewMessages ) {
2206  return [];
2207  }
2208  $utp = $this->getTalkPage();
2209  $timestamp = $services->getTalkPageNotificationManager()
2210  ->getLatestSeenMessageTimestamp( $this );
2211  $rev = null;
2212  if ( $timestamp ) {
2213  $revRecord = $services->getRevisionLookup()
2214  ->getRevisionByTimestamp( $utp, $timestamp );
2215  if ( $revRecord ) {
2216  $rev = new Revision( $revRecord );
2217  }
2218  }
2219  return [
2220  [
2221  'wiki' => WikiMap::getCurrentWikiId(),
2222  'link' => $utp->getLocalURL(),
2223  'rev' => $rev
2224  ]
2225  ];
2226  }
2227 
2234  public function getNewMessageRevisionId() {
2235  wfDeprecated( __METHOD__, '1.35' );
2236  $newMessageRevisionId = null;
2237  $newMessageLinks = $this->getNewMessageLinks();
2238 
2239  // Note: getNewMessageLinks() never returns more than a single link
2240  // and it is always for the same wiki, but we double-check here in
2241  // case that changes some time in the future.
2242  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2243  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2244  && $newMessageLinks[0]['rev']
2245  ) {
2247  $newMessageRevision = $newMessageLinks[0]['rev'];
2248  $newMessageRevisionId = $newMessageRevision->getId();
2249  }
2250 
2251  return $newMessageRevisionId;
2252  }
2253 
2262  public function setNewtalk( $val, $curRev = null ) {
2263  wfDeprecated( __METHOD__, '1.35' );
2264  if ( $curRev && $curRev instanceof Revision ) {
2265  $curRev = $curRev->getRevisionRecord();
2266  }
2267  if ( $val ) {
2268  MediaWikiServices::getInstance()
2269  ->getTalkPageNotificationManager()
2270  ->setUserHasNewMessages( $this, $curRev );
2271  } else {
2272  MediaWikiServices::getInstance()
2273  ->getTalkPageNotificationManager()
2274  ->removeUserHasNewMessages( $this );
2275  }
2276  }
2277 
2284  private function newTouchedTimestamp() {
2285  $time = time();
2286  if ( $this->mTouched ) {
2287  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2288  }
2289 
2290  return wfTimestamp( TS_MW, $time );
2291  }
2292 
2303  public function clearSharedCache( $mode = 'refresh' ) {
2304  if ( !$this->getId() ) {
2305  return;
2306  }
2307 
2308  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2309  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2310  $key = $this->getCacheKey( $cache );
2311 
2312  if ( $mode === 'refresh' ) {
2313  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2314  } else {
2315  $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2316  function () use ( $cache, $key ) {
2317  $cache->delete( $key );
2318  },
2319  __METHOD__
2320  );
2321  }
2322  }
2323 
2329  public function invalidateCache() {
2330  $this->touch();
2331  $this->clearSharedCache( 'changed' );
2332  }
2333 
2346  public function touch() {
2347  $id = $this->getId();
2348  if ( $id ) {
2349  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2350  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2351  $cache->touchCheckKey( $key );
2352  $this->mQuickTouched = null;
2353  }
2354  }
2355 
2361  public function validateCache( $timestamp ) {
2362  return ( $timestamp >= $this->getTouched() );
2363  }
2364 
2373  public function getTouched() {
2374  $this->load();
2375 
2376  if ( $this->mId ) {
2377  if ( $this->mQuickTouched === null ) {
2378  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2379  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2380 
2381  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2382  }
2383 
2384  return max( $this->mTouched, $this->mQuickTouched );
2385  }
2386 
2387  return $this->mTouched;
2388  }
2389 
2395  public function getDBTouched() {
2396  $this->load();
2397 
2398  return $this->mTouched;
2399  }
2400 
2417  public function setPassword( $str ) {
2418  wfDeprecated( __METHOD__, '1.27' );
2419  return $this->setPasswordInternal( $str );
2420  }
2421 
2430  private function setPasswordInternal( $str ) {
2431  $manager = MediaWikiServices::getInstance()->getAuthManager();
2432 
2433  // If the user doesn't exist yet, fail
2434  if ( !$manager->userExists( $this->getName() ) ) {
2435  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2436  }
2437 
2438  $status = $this->changeAuthenticationData( [
2439  'username' => $this->getName(),
2440  'password' => $str,
2441  'retype' => $str,
2442  ] );
2443  if ( !$status->isGood() ) {
2445  ->info( __METHOD__ . ': Password change rejected: '
2446  . $status->getWikiText( null, null, 'en' ) );
2447  return false;
2448  }
2449 
2450  $this->setOption( 'watchlisttoken', false );
2451  SessionManager::singleton()->invalidateSessionsForUser( $this );
2452 
2453  return true;
2454  }
2455 
2468  public function changeAuthenticationData( array $data ) {
2469  $manager = MediaWikiServices::getInstance()->getAuthManager();
2470  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2471  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2472 
2473  $status = Status::newGood( 'ignored' );
2474  foreach ( $reqs as $req ) {
2475  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2476  }
2477  if ( $status->getValue() === 'ignored' ) {
2478  $status->warning( 'authenticationdatachange-ignored' );
2479  }
2480 
2481  if ( $status->isGood() ) {
2482  foreach ( $reqs as $req ) {
2483  $manager->changeAuthenticationData( $req );
2484  }
2485  }
2486  return $status;
2487  }
2488 
2495  public function getToken( $forceCreation = true ) {
2497 
2498  $this->load();
2499  if ( !$this->mToken && $forceCreation ) {
2500  $this->setToken();
2501  }
2502 
2503  if ( !$this->mToken ) {
2504  // The user doesn't have a token, return null to indicate that.
2505  return null;
2506  }
2507 
2508  if ( $this->mToken === self::INVALID_TOKEN ) {
2509  // We return a random value here so existing token checks are very
2510  // likely to fail.
2511  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2512  }
2513 
2514  if ( $wgAuthenticationTokenVersion === null ) {
2515  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2516  return $this->mToken;
2517  }
2518 
2519  // $wgAuthenticationTokenVersion in use, so hmac it.
2520  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2521 
2522  // The raw hash can be overly long. Shorten it up.
2523  $len = max( 32, self::TOKEN_LENGTH );
2524  if ( strlen( $ret ) < $len ) {
2525  // Should never happen, even md5 is 128 bits
2526  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2527  }
2528 
2529  return substr( $ret, -$len );
2530  }
2531 
2538  public function setToken( $token = false ) {
2539  $this->load();
2540  if ( $this->mToken === self::INVALID_TOKEN ) {
2542  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2543  } elseif ( !$token ) {
2544  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2545  } else {
2546  $this->mToken = $token;
2547  }
2548  }
2549 
2554  public function getEmail() {
2555  $this->load();
2556  $this->getHookRunner()->onUserGetEmail( $this, $this->mEmail );
2557  return $this->mEmail;
2558  }
2559 
2565  $this->load();
2566  $this->getHookRunner()->onUserGetEmailAuthenticationTimestamp(
2567  $this, $this->mEmailAuthenticated );
2569  }
2570 
2575  public function setEmail( $str ) {
2576  $this->load();
2577  if ( $str == $this->mEmail ) {
2578  return;
2579  }
2580  $this->invalidateEmail();
2581  $this->mEmail = $str;
2582  $this->getHookRunner()->onUserSetEmail( $this, $this->mEmail );
2583  }
2584 
2592  public function setEmailWithConfirmation( $str ) {
2594 
2595  if ( !$wgEnableEmail ) {
2596  return Status::newFatal( 'emaildisabled' );
2597  }
2598 
2599  $oldaddr = $this->getEmail();
2600  if ( $str === $oldaddr ) {
2601  return Status::newGood( true );
2602  }
2603 
2604  $type = $oldaddr != '' ? 'changed' : 'set';
2605  $notificationResult = null;
2606 
2607  if ( $wgEmailAuthentication && $type === 'changed' ) {
2608  // Send the user an email notifying the user of the change in registered
2609  // email address on their previous email address
2610  $change = $str != '' ? 'changed' : 'removed';
2611  $notificationResult = $this->sendMail(
2612  wfMessage( 'notificationemail_subject_' . $change )->text(),
2613  wfMessage( 'notificationemail_body_' . $change,
2614  $this->getRequest()->getIP(),
2615  $this->getName(),
2616  $str )->text()
2617  );
2618  }
2619 
2620  $this->setEmail( $str );
2621 
2622  if ( $str !== '' && $wgEmailAuthentication ) {
2623  // Send a confirmation request to the new address if needed
2624  $result = $this->sendConfirmationMail( $type );
2625 
2626  if ( $notificationResult !== null ) {
2627  $result->merge( $notificationResult );
2628  }
2629 
2630  if ( $result->isGood() ) {
2631  // Say to the caller that a confirmation and notification mail has been sent
2632  $result->value = 'eauth';
2633  }
2634  } else {
2635  $result = Status::newGood( true );
2636  }
2637 
2638  return $result;
2639  }
2640 
2645  public function getRealName() {
2646  if ( !$this->isItemLoaded( 'realname' ) ) {
2647  $this->load();
2648  }
2649 
2650  return $this->mRealName;
2651  }
2652 
2657  public function setRealName( $str ) {
2658  $this->load();
2659  $this->mRealName = $str;
2660  }
2661 
2674  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2675  if ( $oname === null ) {
2676  return null; // b/c
2677  }
2678  return MediaWikiServices::getInstance()
2679  ->getUserOptionsLookup()
2680  ->getOption( $this, $oname, $defaultOverride, $ignoreHidden );
2681  }
2682 
2692  public function getOptions( $flags = 0 ) {
2693  return MediaWikiServices::getInstance()
2694  ->getUserOptionsLookup()
2695  ->getOptions( $this, $flags );
2696  }
2697 
2706  public function getBoolOption( $oname ) {
2707  return MediaWikiServices::getInstance()
2708  ->getUserOptionsLookup()
2709  ->getBoolOption( $this, $oname );
2710  }
2711 
2721  public function getIntOption( $oname, $defaultOverride = 0 ) {
2722  if ( $oname === null ) {
2723  return null; // b/c
2724  }
2725  return MediaWikiServices::getInstance()
2726  ->getUserOptionsLookup()
2727  ->getIntOption( $this, $oname, $defaultOverride );
2728  }
2729 
2739  public function setOption( $oname, $val ) {
2740  MediaWikiServices::getInstance()
2741  ->getUserOptionsManager()
2742  ->setOption( $this, $oname, $val );
2743  }
2744 
2755  public function getTokenFromOption( $oname ) {
2756  global $wgHiddenPrefs;
2757 
2758  $id = $this->getId();
2759  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2760  return false;
2761  }
2762 
2763  $token = $this->getOption( $oname );
2764  if ( !$token ) {
2765  // Default to a value based on the user token to avoid space
2766  // wasted on storing tokens for all users. When this option
2767  // is set manually by the user, only then is it stored.
2768  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2769  }
2770 
2771  return $token;
2772  }
2773 
2783  public function resetTokenFromOption( $oname ) {
2784  global $wgHiddenPrefs;
2785  if ( in_array( $oname, $wgHiddenPrefs ) ) {
2786  return false;
2787  }
2788 
2789  $token = MWCryptRand::generateHex( 40 );
2790  $this->setOption( $oname, $token );
2791  return $token;
2792  }
2793 
2818  public static function listOptionKinds() {
2819  return MediaWikiServices::getInstance()
2820  ->getUserOptionsManager()
2821  ->listOptionKinds();
2822  }
2823 
2837  public function getOptionKinds( IContextSource $context, $options = null ) {
2838  return MediaWikiServices::getInstance()
2839  ->getUserOptionsManager()
2840  ->getOptionKinds( $this, $context, $options );
2841  }
2842 
2858  public function resetOptions(
2859  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
2860  IContextSource $context = null
2861  ) {
2862  MediaWikiServices::getInstance()
2863  ->getUserOptionsManager()
2864  ->resetOptions(
2865  $this,
2866  $context ?? RequestContext::getMain(),
2867  $resetKinds
2868  );
2869  }
2870 
2875  public function getDatePreference() {
2876  // Important migration for old data rows
2877  if ( $this->mDatePreference === null ) {
2878  global $wgLang;
2879  $value = $this->getOption( 'date' );
2880  $map = $wgLang->getDatePreferenceMigrationMap();
2881  if ( isset( $map[$value] ) ) {
2882  $value = $map[$value];
2883  }
2884  $this->mDatePreference = $value;
2885  }
2886  return $this->mDatePreference;
2887  }
2888 
2895  public function requiresHTTPS() {
2896  global $wgForceHTTPS, $wgSecureLogin;
2897  if ( $wgForceHTTPS ) {
2898  return true;
2899  }
2900  if ( !$wgSecureLogin ) {
2901  return false;
2902  }
2903  $https = $this->getBoolOption( 'prefershttps' );
2904  $this->getHookRunner()->onUserRequiresHTTPS( $this, $https );
2905  if ( $https ) {
2906  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
2907  }
2908 
2909  return $https;
2910  }
2911 
2917  public function getStubThreshold() {
2918  global $wgMaxArticleSize; # Maximum article size, in Kb
2919  $threshold = $this->getIntOption( 'stubthreshold' );
2920  if ( $threshold > $wgMaxArticleSize * 1024 ) {
2921  // If they have set an impossible value, disable the preference
2922  // so we can use the parser cache again.
2923  $threshold = 0;
2924  }
2925  return $threshold;
2926  }
2927 
2936  public function getRights() {
2937  return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
2938  }
2939 
2948  public function getGroups() {
2949  return MediaWikiServices::getInstance()
2950  ->getUserGroupManager()
2951  ->getUserGroups( $this, $this->queryFlagsUsed );
2952  }
2953 
2963  public function getGroupMemberships() {
2964  return MediaWikiServices::getInstance()
2965  ->getUserGroupManager()
2966  ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
2967  }
2968 
2979  public function getEffectiveGroups( $recache = false ) {
2980  return MediaWikiServices::getInstance()
2981  ->getUserGroupManager()
2982  ->getUserEffectiveGroups( $this, $this->queryFlagsUsed, $recache );
2983  }
2984 
2995  public function getAutomaticGroups( $recache = false ) {
2996  return MediaWikiServices::getInstance()
2997  ->getUserGroupManager()
2998  ->getUserImplicitGroups( $this, $this->queryFlagsUsed, $recache );
2999  }
3000 
3012  public function getFormerGroups() {
3013  return MediaWikiServices::getInstance()
3014  ->getUserGroupManager()
3015  ->getUserFormerGroups( $this, $this->queryFlagsUsed );
3016  }
3017 
3022  public function getEditCount() {
3023  if ( !$this->getId() ) {
3024  return null;
3025  }
3026 
3027  if ( $this->mEditCount === null ) {
3028  $this->mEditCount = MediaWikiServices::getInstance()
3029  ->getUserEditTracker()
3030  ->getUserEditCount( $this );
3031  }
3032  return (int)$this->mEditCount;
3033  }
3034 
3048  public function addGroup( $group, $expiry = null ) {
3049  return MediaWikiServices::getInstance()
3050  ->getUserGroupManager()
3051  ->addUserToGroup( $this, $group, $expiry, true );
3052  }
3053 
3063  public function removeGroup( $group ) {
3064  return MediaWikiServices::getInstance()
3065  ->getUserGroupManager()
3066  ->removeUserFromGroup( $this, $group );
3067  }
3068 
3078  public function isRegistered() {
3079  return $this->getId() != 0;
3080  }
3081 
3086  public function isLoggedIn() {
3087  return $this->isRegistered();
3088  }
3089 
3094  public function isAnon() {
3095  return !$this->isRegistered();
3096  }
3097 
3102  public function isBot() {
3103  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3104  return true;
3105  }
3106 
3107  $isBot = false;
3108  $this->getHookRunner()->onUserIsBot( $this, $isBot );
3109 
3110  return $isBot;
3111  }
3112 
3122  public function isSystemUser() {
3123  $this->load();
3124  if ( $this->mEmail || $this->mToken !== self::INVALID_TOKEN ||
3125  MediaWikiServices::getInstance()->getAuthManager()->userCanAuthenticate( $this->mName )
3126  ) {
3127  return false;
3128  }
3129  return true;
3130  }
3131 
3141  public function isAllowedAny( ...$permissions ) {
3142  return MediaWikiServices::getInstance()
3143  ->getPermissionManager()
3144  ->userHasAnyRight( $this, ...$permissions );
3145  }
3146 
3153  public function isAllowedAll( ...$permissions ) {
3154  return MediaWikiServices::getInstance()
3155  ->getPermissionManager()
3156  ->userHasAllRights( $this, ...$permissions );
3157  }
3158 
3169  public function isAllowed( $action = '' ) {
3170  return MediaWikiServices::getInstance()->getPermissionManager()
3171  ->userHasRight( $this, $action );
3172  }
3173 
3178  public function useRCPatrol() {
3179  global $wgUseRCPatrol;
3180  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3181  }
3182 
3187  public function useNPPatrol() {
3189  return (
3191  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3192  );
3193  }
3194 
3199  public function useFilePatrol() {
3201  return (
3203  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3204  );
3205  }
3206 
3212  public function getRequest() {
3213  if ( $this->mRequest ) {
3214  return $this->mRequest;
3215  }
3216 
3217  global $wgRequest;
3218  return $wgRequest;
3219  }
3220 
3229  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3230  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3231  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3232  }
3233  return false;
3234  }
3235 
3246  public function isTempWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ): bool {
3247  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3248  return MediaWikiServices::getInstance()->getWatchedItemStore()
3249  ->isTempWatched( $this, $title );
3250  }
3251  return false;
3252  }
3253 
3263  public function addWatch(
3264  $title,
3265  $checkRights = self::CHECK_USER_RIGHTS,
3266  ?string $expiry = null
3267  ) {
3268  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3269  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3270  $this,
3271  [ $title->getSubjectPage(), $title->getTalkPage() ],
3272  $expiry
3273  );
3274  }
3275  $this->invalidateCache();
3276  }
3277 
3285  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3286  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3287  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3288  $store->removeWatch( $this, $title->getSubjectPage() );
3289  $store->removeWatch( $this, $title->getTalkPage() );
3290  }
3291  $this->invalidateCache();
3292  }
3293 
3305  public function clearNotification( &$title, $oldid = 0 ) {
3306  MediaWikiServices::getInstance()
3307  ->getWatchlistNotificationManager()
3308  ->clearTitleUserNotifications( $this, $title, $oldid );
3309  }
3310 
3320  public function clearAllNotifications() {
3321  wfDeprecated( __METHOD__, '1.35' );
3322  MediaWikiServices::getInstance()
3323  ->getWatchlistNotificationManager()
3324  ->clearAllUserNotifications( $this );
3325  }
3326 
3332  public function getExperienceLevel() {
3333  global $wgLearnerEdits,
3337 
3338  if ( $this->isAnon() ) {
3339  return false;
3340  }
3341 
3342  $editCount = $this->getEditCount();
3343  $registration = $this->getRegistration();
3344  $now = time();
3345  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3346  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3347 
3348  if ( $editCount < $wgLearnerEdits ||
3349  $registration > $learnerRegistration ) {
3350  return 'newcomer';
3351  }
3352 
3353  if ( $editCount > $wgExperiencedUserEdits &&
3354  $registration <= $experiencedRegistration
3355  ) {
3356  return 'experienced';
3357  }
3358 
3359  return 'learner';
3360  }
3361 
3370  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3371  $this->load();
3372  if ( $this->mId == 0 ) {
3373  return;
3374  }
3375 
3376  $session = $this->getRequest()->getSession();
3377  if ( $request && $session->getRequest() !== $request ) {
3378  $session = $session->sessionWithRequest( $request );
3379  }
3380  $delay = $session->delaySave();
3381 
3382  if ( !$session->getUser()->equals( $this ) ) {
3383  if ( !$session->canSetUser() ) {
3385  ->warning( __METHOD__ .
3386  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3387  );
3388  return;
3389  }
3390  $session->setUser( $this );
3391  }
3392 
3393  $session->setRememberUser( $rememberMe );
3394  if ( $secure !== null ) {
3395  $session->setForceHTTPS( $secure );
3396  }
3397 
3398  $session->persist();
3399 
3400  ScopedCallback::consume( $delay );
3401  }
3402 
3406  public function logout() {
3407  // Avoid PHP 7.1 warning of passing $this by reference
3408  $user = $this;
3409  if ( $this->getHookRunner()->onUserLogout( $user ) ) {
3410  $this->doLogout();
3411  }
3412  }
3413 
3418  public function doLogout() {
3419  $session = $this->getRequest()->getSession();
3420  if ( !$session->canSetUser() ) {
3422  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3423  $error = 'immutable';
3424  } elseif ( !$session->getUser()->equals( $this ) ) {
3426  ->warning( __METHOD__ .
3427  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3428  );
3429  // But we still may as well make this user object anon
3430  $this->clearInstanceCache( 'defaults' );
3431  $error = 'wronguser';
3432  } else {
3433  $this->clearInstanceCache( 'defaults' );
3434  $delay = $session->delaySave();
3435  $session->unpersist(); // Clear cookies (T127436)
3436  $session->setLoggedOutTimestamp( time() );
3437  $session->setUser( new User );
3438  $session->set( 'wsUserID', 0 ); // Other code expects this
3439  $session->resetAllTokens();
3440  ScopedCallback::consume( $delay );
3441  $error = false;
3442  }
3443  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3444  'event' => 'logout',
3445  'successful' => $error === false,
3446  'status' => $error ?: 'success',
3447  ] );
3448  }
3449 
3454  public function saveSettings() {
3455  if ( wfReadOnly() ) {
3456  // @TODO: caller should deal with this instead!
3457  // This should really just be an exception.
3459  null,
3460  "Could not update user with ID '{$this->mId}'; DB is read-only."
3461  ) );
3462  return;
3463  }
3464 
3465  $this->load();
3466  if ( $this->mId == 0 ) {
3467  return; // anon
3468  }
3469 
3470  // Get a new user_touched that is higher than the old one.
3471  // This will be used for a CAS check as a last-resort safety
3472  // check against race conditions and replica DB lag.
3473  $newTouched = $this->newTouchedTimestamp();
3474 
3475  $dbw = wfGetDB( DB_MASTER );
3476  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
3477  $dbw->update( 'user',
3478  [ /* SET */
3479  'user_name' => $this->mName,
3480  'user_real_name' => $this->mRealName,
3481  'user_email' => $this->mEmail,
3482  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3483  'user_touched' => $dbw->timestamp( $newTouched ),
3484  'user_token' => strval( $this->mToken ),
3485  'user_email_token' => $this->mEmailToken,
3486  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3487  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
3488  'user_id' => $this->mId,
3489  ] ), $fname
3490  );
3491 
3492  if ( !$dbw->affectedRows() ) {
3493  // Maybe the problem was a missed cache update; clear it to be safe
3494  $this->clearSharedCache( 'refresh' );
3495  // User was changed in the meantime or loaded with stale data
3496  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
3497  LoggerFactory::getInstance( 'preferences' )->warning(
3498  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
3499  [ 'user_id' => $this->mId, 'db_flag' => $from ]
3500  );
3501  throw new MWException( "CAS update failed on user_touched. " .
3502  "The version of the user to be saved is older than the current version."
3503  );
3504  }
3505 
3506  $dbw->update(
3507  'actor',
3508  [ 'actor_name' => $this->mName ],
3509  [ 'actor_user' => $this->mId ],
3510  $fname
3511  );
3512  } );
3513 
3514  $this->mTouched = $newTouched;
3515  MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3516 
3517  $this->getHookRunner()->onUserSaveSettings( $this );
3518  $this->clearSharedCache( 'changed' );
3519  $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3520  $hcu->purgeTitleUrls( $this->getUserPage(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
3521  }
3522 
3529  public function idForName( $flags = 0 ) {
3530  $s = trim( $this->getName() );
3531  if ( $s === '' ) {
3532  return 0;
3533  }
3534 
3535  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
3536  ? wfGetDB( DB_MASTER )
3537  : wfGetDB( DB_REPLICA );
3538 
3539  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
3540  ? [ 'LOCK IN SHARE MODE' ]
3541  : [];
3542 
3543  $id = $db->selectField( 'user',
3544  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
3545 
3546  return (int)$id;
3547  }
3548 
3564  public static function createNew( $name, $params = [] ) {
3565  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
3566  if ( isset( $params[$field] ) ) {
3567  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
3568  unset( $params[$field] );
3569  }
3570  }
3571 
3572  $user = new User;
3573  $user->load();
3574  $user->setToken(); // init token
3575  if ( isset( $params['options'] ) ) {
3576  MediaWikiServices::getInstance()
3577  ->getUserOptionsManager()
3578  ->loadUserOptions( $user, $user->queryFlagsUsed, $params['options'] );
3579  unset( $params['options'] );
3580  }
3581  $dbw = wfGetDB( DB_MASTER );
3582 
3583  $noPass = PasswordFactory::newInvalidPassword()->toString();
3584 
3585  $fields = [
3586  'user_name' => $name,
3587  'user_password' => $noPass,
3588  'user_newpassword' => $noPass,
3589  'user_email' => $user->mEmail,
3590  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3591  'user_real_name' => $user->mRealName,
3592  'user_token' => strval( $user->mToken ),
3593  'user_registration' => $dbw->timestamp( $user->mRegistration ),
3594  'user_editcount' => 0,
3595  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3596  ];
3597  foreach ( $params as $name => $value ) {
3598  $fields["user_$name"] = $value;
3599  }
3600 
3601  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
3602  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
3603  if ( $dbw->affectedRows() ) {
3604  $newUser = self::newFromId( $dbw->insertId() );
3605  $newUser->mName = $fields['user_name'];
3606  $newUser->updateActorId( $dbw );
3607  // Load the user from master to avoid replica lag
3608  $newUser->load( self::READ_LATEST );
3609  } else {
3610  $newUser = null;
3611  }
3612  return $newUser;
3613  } );
3614  }
3615 
3642  public function addToDatabase() {
3643  $this->load();
3644  if ( !$this->mToken ) {
3645  $this->setToken(); // init token
3646  }
3647 
3648  if ( !is_string( $this->mName ) ) {
3649  throw new RuntimeException( "User name field is not set." );
3650  }
3651 
3652  $this->mTouched = $this->newTouchedTimestamp();
3653 
3654  $dbw = wfGetDB( DB_MASTER );
3655  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
3656  $noPass = PasswordFactory::newInvalidPassword()->toString();
3657  $dbw->insert( 'user',
3658  [
3659  'user_name' => $this->mName,
3660  'user_password' => $noPass,
3661  'user_newpassword' => $noPass,
3662  'user_email' => $this->mEmail,
3663  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3664  'user_real_name' => $this->mRealName,
3665  'user_token' => strval( $this->mToken ),
3666  'user_registration' => $dbw->timestamp( $this->mRegistration ),
3667  'user_editcount' => 0,
3668  'user_touched' => $dbw->timestamp( $this->mTouched ),
3669  ], $fname,
3670  [ 'IGNORE' ]
3671  );
3672  if ( !$dbw->affectedRows() ) {
3673  // Use locking reads to bypass any REPEATABLE-READ snapshot.
3674  $this->mId = $dbw->selectField(
3675  'user',
3676  'user_id',
3677  [ 'user_name' => $this->mName ],
3678  $fname,
3679  [ 'LOCK IN SHARE MODE' ]
3680  );
3681  $loaded = false;
3682  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
3683  $loaded = true;
3684  }
3685  if ( !$loaded ) {
3686  throw new MWException( $fname . ": hit a key conflict attempting " .
3687  "to insert user '{$this->mName}' row, but it was not present in select!" );
3688  }
3689  return Status::newFatal( 'userexists' );
3690  }
3691  $this->mId = $dbw->insertId();
3692  self::$idCacheByName[$this->mName] = $this->mId;
3693  $this->updateActorId( $dbw );
3694 
3695  return Status::newGood();
3696  } );
3697  if ( !$status->isGood() ) {
3698  return $status;
3699  }
3700 
3701  // Clear instance cache other than user table data and actor, which is already accurate
3702  $this->clearInstanceCache();
3703 
3704  MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3705  return Status::newGood();
3706  }
3707 
3712  private function updateActorId( IDatabase $dbw ) {
3713  $dbw->insert(
3714  'actor',
3715  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
3716  __METHOD__
3717  );
3718  $this->mActorId = (int)$dbw->insertId();
3719  }
3720 
3726  public function spreadAnyEditBlock() {
3727  if ( $this->isLoggedIn() && $this->getBlock() ) {
3728  return $this->spreadBlock();
3729  }
3730 
3731  return false;
3732  }
3733 
3739  protected function spreadBlock() {
3740  wfDebug( __METHOD__ . "()" );
3741  $this->load();
3742  if ( $this->mId == 0 ) {
3743  return false;
3744  }
3745 
3746  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
3747  if ( !$userblock ) {
3748  return false;
3749  }
3750 
3751  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
3752  }
3753 
3758  public function isBlockedFromCreateAccount() {
3759  $this->getBlockedStatus();
3760  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
3761  return $this->mBlock;
3762  }
3763 
3764  # T15611: if the IP address the user is trying to create an account from is
3765  # blocked with createaccount disabled, prevent new account creation there even
3766  # when the user is logged in
3767  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
3768  $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
3769  null, $this->getRequest()->getIP()
3770  );
3771  }
3772  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
3773  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
3774  ? $this->mBlockedFromCreateAccount
3775  : false;
3776  }
3777 
3782  public function isBlockedFromEmailuser() {
3783  $this->getBlockedStatus();
3784  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
3785  }
3786 
3793  public function isBlockedFromUpload() {
3794  $this->getBlockedStatus();
3795  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
3796  }
3797 
3802  public function isAllowedToCreateAccount() {
3803  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
3804  }
3805 
3811  public function getUserPage() {
3812  return Title::makeTitle( NS_USER, $this->getName() );
3813  }
3814 
3820  public function getTalkPage() {
3821  $title = $this->getUserPage();
3822  return $title->getTalkPage();
3823  }
3824 
3830  public function isNewbie() {
3831  return !$this->isAllowed( 'autoconfirmed' );
3832  }
3833 
3845  public function getEditTokenObject( $salt = '', $request = null ) {
3846  if ( $this->isAnon() ) {
3847  return new LoggedOutEditToken();
3848  }
3849 
3850  if ( !$request ) {
3851  $request = $this->getRequest();
3852  }
3853  return $request->getSession()->getToken( $salt );
3854  }
3855 
3869  public function getEditToken( $salt = '', $request = null ) {
3870  return $this->getEditTokenObject( $salt, $request )->toString();
3871  }
3872 
3885  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
3886  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
3887  }
3888 
3899  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
3900  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
3901  return $this->matchEditToken( $val, $salt, $request, $maxage );
3902  }
3903 
3911  public function sendConfirmationMail( $type = 'created' ) {
3912  global $wgLang;
3913  $expiration = null; // gets passed-by-ref and defined in next line.
3914  $token = $this->confirmationToken( $expiration );
3915  $url = $this->confirmationTokenUrl( $token );
3916  $invalidateURL = $this->invalidationTokenUrl( $token );
3917  $this->saveSettings();
3918 
3919  if ( $type == 'created' || $type === false ) {
3920  $message = 'confirmemail_body';
3921  $type = 'created';
3922  } elseif ( $type === true ) {
3923  $message = 'confirmemail_body_changed';
3924  $type = 'changed';
3925  } else {
3926  // Messages: confirmemail_body_changed, confirmemail_body_set
3927  $message = 'confirmemail_body_' . $type;
3928  }
3929 
3930  $mail = [
3931  'subject' => wfMessage( 'confirmemail_subject' )->text(),
3932  'body' => wfMessage( $message,
3933  $this->getRequest()->getIP(),
3934  $this->getName(),
3935  $url,
3936  $wgLang->userTimeAndDate( $expiration, $this ),
3937  $invalidateURL,
3938  $wgLang->userDate( $expiration, $this ),
3939  $wgLang->userTime( $expiration, $this ) )->text(),
3940  'from' => null,
3941  'replyTo' => null,
3942  ];
3943  $info = [
3944  'type' => $type,
3945  'ip' => $this->getRequest()->getIP(),
3946  'confirmURL' => $url,
3947  'invalidateURL' => $invalidateURL,
3948  'expiration' => $expiration
3949  ];
3950 
3951  $this->getHookRunner()->onUserSendConfirmationMail( $this, $mail, $info );
3952  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
3953  }
3954 
3966  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
3967  global $wgPasswordSender;
3968 
3969  if ( $from instanceof User ) {
3970  $sender = MailAddress::newFromUser( $from );
3971  } else {
3972  $sender = new MailAddress( $wgPasswordSender,
3973  wfMessage( 'emailsender' )->inContentLanguage()->text() );
3974  }
3975  $to = MailAddress::newFromUser( $this );
3976 
3977  return UserMailer::send( $to, $sender, $subject, $body, [
3978  'replyTo' => $replyto,
3979  ] );
3980  }
3981 
3992  protected function confirmationToken( &$expiration ) {
3994  $now = time();
3995  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
3996  $expiration = wfTimestamp( TS_MW, $expires );
3997  $this->load();
3998  $token = MWCryptRand::generateHex( 32 );
3999  $hash = md5( $token );
4000  $this->mEmailToken = $hash;
4001  $this->mEmailTokenExpires = $expiration;
4002  return $token;
4003  }
4004 
4010  protected function confirmationTokenUrl( $token ) {
4011  return $this->getTokenUrl( 'ConfirmEmail', $token );
4012  }
4013 
4019  protected function invalidationTokenUrl( $token ) {
4020  return $this->getTokenUrl( 'InvalidateEmail', $token );
4021  }
4022 
4037  protected function getTokenUrl( $page, $token ) {
4038  // Hack to bypass localization of 'Special:'
4039  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4040  return $title->getCanonicalURL();
4041  }
4042 
4050  public function confirmEmail() {
4051  // Check if it's already confirmed, so we don't touch the database
4052  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4053  if ( !$this->isEmailConfirmed() ) {
4055  $this->getHookRunner()->onConfirmEmailComplete( $this );
4056  }
4057  return true;
4058  }
4059 
4067  public function invalidateEmail() {
4068  $this->load();
4069  $this->mEmailToken = null;
4070  $this->mEmailTokenExpires = null;
4071  $this->setEmailAuthenticationTimestamp( null );
4072  $this->mEmail = '';
4073  $this->getHookRunner()->onInvalidateEmailComplete( $this );
4074  return true;
4075  }
4076 
4081  public function setEmailAuthenticationTimestamp( $timestamp ) {
4082  $this->load();
4083  $this->mEmailAuthenticated = $timestamp;
4084  $this->getHookRunner()->onUserSetEmailAuthenticationTimestamp(
4085  $this, $this->mEmailAuthenticated );
4086  }
4087 
4093  public function canSendEmail() {
4095  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4096  return false;
4097  }
4098  $canSend = $this->isEmailConfirmed();
4099  $this->getHookRunner()->onUserCanSendEmail( $this, $canSend );
4100  return $canSend;
4101  }
4102 
4108  public function canReceiveEmail() {
4109  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4110  }
4111 
4122  public function isEmailConfirmed() {
4123  global $wgEmailAuthentication;
4124  $this->load();
4125  // Avoid PHP 7.1 warning of passing $this by reference
4126  $user = $this;
4127  $confirmed = true;
4128  if ( $this->getHookRunner()->onEmailConfirmed( $user, $confirmed ) ) {
4129  if ( $this->isAnon() ) {
4130  return false;
4131  }
4132  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4133  return false;
4134  }
4136  return false;
4137  }
4138  return true;
4139  }
4140 
4141  return $confirmed;
4142  }
4143 
4148  public function isEmailConfirmationPending() {
4149  global $wgEmailAuthentication;
4150  return $wgEmailAuthentication &&
4151  !$this->isEmailConfirmed() &&
4152  $this->mEmailToken &&
4153  $this->mEmailTokenExpires > wfTimestamp();
4154  }
4155 
4163  public function getRegistration() {
4164  if ( $this->isAnon() ) {
4165  return false;
4166  }
4167  $this->load();
4168  return $this->mRegistration;
4169  }
4170 
4177  public function getFirstEditTimestamp() {
4178  return MediaWikiServices::getInstance()
4179  ->getUserEditTracker()
4180  ->getFirstEditTimestamp( $this );
4181  }
4182 
4190  public function getLatestEditTimestamp() {
4191  return MediaWikiServices::getInstance()
4192  ->getUserEditTracker()
4193  ->getLatestEditTimestamp( $this );
4194  }
4195 
4205  public static function getGroupPermissions( $groups ) {
4206  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4207  }
4208 
4218  public static function getGroupsWithPermission( $role ) {
4219  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4220  }
4221 
4237  public static function groupHasPermission( $group, $role ) {
4238  return MediaWikiServices::getInstance()->getPermissionManager()
4239  ->groupHasPermission( $group, $role );
4240  }
4241 
4259  public static function isEveryoneAllowed( $right ) {
4260  wfDeprecated( __METHOD__, '1.34' );
4261  return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4262  }
4263 
4271  public static function getAllGroups() {
4272  return MediaWikiServices::getInstance()
4273  ->getUserGroupManager()
4274  ->listAllGroups();
4275  }
4276 
4284  public static function getAllRights() {
4285  wfDeprecated( __METHOD__, '1.34' );
4286  return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4287  }
4288 
4294  public static function getImplicitGroups() {
4295  return MediaWikiServices::getInstance()
4296  ->getUserGroupManager()
4297  ->listAllImplicitGroups();
4298  }
4299 
4310  public static function changeableByGroup( $group ) {
4312 
4313  $groups = [
4314  'add' => [],
4315  'remove' => [],
4316  'add-self' => [],
4317  'remove-self' => []
4318  ];
4319 
4320  if ( empty( $wgAddGroups[$group] ) ) {
4321  // Don't add anything to $groups
4322  } elseif ( $wgAddGroups[$group] === true ) {
4323  // You get everything
4324  $groups['add'] = self::getAllGroups();
4325  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4326  $groups['add'] = $wgAddGroups[$group];
4327  }
4328 
4329  // Same thing for remove
4330  if ( empty( $wgRemoveGroups[$group] ) ) {
4331  // Do nothing
4332  } elseif ( $wgRemoveGroups[$group] === true ) {
4333  $groups['remove'] = self::getAllGroups();
4334  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4335  $groups['remove'] = $wgRemoveGroups[$group];
4336  }
4337 
4338  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4339  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4340  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4341  if ( is_int( $key ) ) {
4342  $wgGroupsAddToSelf['user'][] = $value;
4343  }
4344  }
4345  }
4346 
4347  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4348  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4349  if ( is_int( $key ) ) {
4350  $wgGroupsRemoveFromSelf['user'][] = $value;
4351  }
4352  }
4353  }
4354 
4355  // Now figure out what groups the user can add to him/herself
4356  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4357  // Do nothing
4358  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4359  // No idea WHY this would be used, but it's there
4360  $groups['add-self'] = self::getAllGroups();
4361  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4362  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4363  }
4364 
4365  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4366  // Do nothing
4367  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4368  $groups['remove-self'] = self::getAllGroups();
4369  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4370  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4371  }
4372 
4373  return $groups;
4374  }
4375 
4383  public function changeableGroups() {
4384  if ( $this->isAllowed( 'userrights' ) ) {
4385  // This group gives the right to modify everything (reverse-
4386  // compatibility with old "userrights lets you change
4387  // everything")
4388  // Using array_merge to make the groups reindexed
4389  $all = array_merge( self::getAllGroups() );
4390  return [
4391  'add' => $all,
4392  'remove' => $all,
4393  'add-self' => [],
4394  'remove-self' => []
4395  ];
4396  }
4397 
4398  // Okay, it's not so simple, we will have to go through the arrays
4399  $groups = [
4400  'add' => [],
4401  'remove' => [],
4402  'add-self' => [],
4403  'remove-self' => []
4404  ];
4405  $addergroups = $this->getEffectiveGroups();
4406 
4407  foreach ( $addergroups as $addergroup ) {
4408  $groups = array_merge_recursive(
4409  $groups, $this->changeableByGroup( $addergroup )
4410  );
4411  $groups['add'] = array_unique( $groups['add'] );
4412  $groups['remove'] = array_unique( $groups['remove'] );
4413  $groups['add-self'] = array_unique( $groups['add-self'] );
4414  $groups['remove-self'] = array_unique( $groups['remove-self'] );
4415  }
4416  return $groups;
4417  }
4418 
4422  public function incEditCount() {
4423  if ( $this->isAnon() ) {
4424  return; // sanity
4425  }
4426 
4428  new UserEditCountUpdate( $this, 1 ),
4430  );
4431  }
4432 
4438  public function setEditCountInternal( $count ) {
4439  $this->mEditCount = $count;
4440  }
4441 
4449  public function initEditCountInternal( IDatabase $dbr ) {
4450  return MediaWikiServices::getInstance()
4451  ->getUserEditTracker()
4452  ->initializeUserEditCount( $this );
4453  }
4454 
4462  public static function getRightDescription( $right ) {
4463  $key = "right-$right";
4464  $msg = wfMessage( $key );
4465  return $msg->isDisabled() ? $right : $msg->text();
4466  }
4467 
4475  public static function getGrantName( $grant ) {
4476  $key = "grant-$grant";
4477  $msg = wfMessage( $key );
4478  return $msg->isDisabled() ? $grant : $msg->text();
4479  }
4480 
4501  public function addNewUserLogEntry( $action = false, $reason = '' ) {
4502  return true; // disabled
4503  }
4504 
4511  public static function selectFields() {
4512  wfDeprecated( __METHOD__, '1.31' );
4513  return [
4514  'user_id',
4515  'user_name',
4516  'user_real_name',
4517  'user_email',
4518  'user_touched',
4519  'user_token',
4520  'user_email_authenticated',
4521  'user_email_token',
4522  'user_email_token_expires',
4523  'user_registration',
4524  'user_editcount',
4525  ];
4526  }
4527 
4537  public static function getQueryInfo() {
4538  $ret = [
4539  'tables' => [ 'user', 'user_actor' => 'actor' ],
4540  'fields' => [
4541  'user_id',
4542  'user_name',
4543  'user_real_name',
4544  'user_email',
4545  'user_touched',
4546  'user_token',
4547  'user_email_authenticated',
4548  'user_email_token',
4549  'user_email_token_expires',
4550  'user_registration',
4551  'user_editcount',
4552  'user_actor.actor_id',
4553  ],
4554  'joins' => [
4555  'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
4556  ],
4557  ];
4558 
4559  return $ret;
4560  }
4561 
4569  public static function newFatalPermissionDeniedStatus( $permission ) {
4570  global $wgLang;
4571 
4572  $groups = [];
4573  foreach ( MediaWikiServices::getInstance()
4575  ->getGroupsWithPermission( $permission ) as $group ) {
4576  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
4577  }
4578 
4579  if ( $groups ) {
4580  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
4581  }
4582 
4583  return Status::newFatal( 'badaccess-group0' );
4584  }
4585 
4595  public function getInstanceForUpdate() {
4596  if ( !$this->getId() ) {
4597  return null; // anon
4598  }
4599 
4600  $user = self::newFromId( $this->getId() );
4601  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
4602  return null;
4603  }
4604 
4605  return $user;
4606  }
4607 
4615  public function equals( UserIdentity $user ) {
4616  // XXX it's not clear whether central ID providers are supposed to obey this
4617  return $this->getName() === $user->getName();
4618  }
4619 
4625  public function isAllowUsertalk() {
4626  return $this->mAllowUsertalk;
4627  }
4628 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1545
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:5326
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:398
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:313
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2172
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:50
User\inDnsBlacklist
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1638
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:557
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:4010
User\__set
__set( $name, $value)
Definition: User.php:261
$wgProxyList
$wgProxyList
Big list of banned IP addresses.
Definition: DefaultSettings.php:6417
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:2819
User\$mToken
string $mToken
Definition: User.php:132
User\$mBlockedby
string int $mBlockedby
Definition: User.php:175
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:272
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1052
User\getId
getId()
Get the user's ID.
Definition: User.php:2042
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3199
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2403
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:3094
User\$mBlockedFromCreateAccount
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:210
User\$mBlockreason
string $mBlockreason
TODO: This should be removed when User::BlockedFor and AbstractBlock::getReason are hard deprecated.
Definition: User.php:183
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4037
User\isRegistered
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3078
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2110
User\isLocallyBlockedProxy
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1686
User\$mCacheVars
static string[] $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:97
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:2783
User\isBlockedFrom
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:1928
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1408
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:152
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:4569
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:3845
User\isBot
isBot()
Definition: User.php:3102
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:2284
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9381
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3022
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:3739
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:4422
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:687
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:2837
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4148
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:1957
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2721
true
return true
Definition: router.php:90
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:2692
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:128
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:229
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1811
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2495
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:3726
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2234
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:207
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1908
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:2592
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1796
User\$mHideName
bool $mHideName
Definition: User.php:194
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:2917
Sanitizer\validateEmail
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1984
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1128
User\$mLocked
bool $mLocked
Definition: User.php:187
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:533
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1300
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1222
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4081
User\$mEmail
string $mEmail
Definition: User.php:126
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:3811
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:2948
$s
$s
Definition: mergeMessageFileList.php:185
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:589
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1089
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3187
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:2875
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:296
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:2575
User\idForName
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:3529
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:971
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:3911
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4237
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2564
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:1760
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:55
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3178
$base
$base
Definition: generateLocalAutoload.php:11
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:4067
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:709
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:7329
User\$mHash
string $mHash
Definition: User.php:177
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:7345
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:991
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:955
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
User\getRights
getRights()
Get the permissions this user has.
Definition: User.php:2936
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:3564
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3212
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:2995
User\INVALID_TOKEN
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:65
User\setPassword
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2417
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1532
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:4595
$dbr
$dbr
Definition: testCompression.php:54
IDBAccessObject\READ_LOCKING
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:64
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Specify the difference engine to use.
Definition: DefaultSettings.php:9380
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:750
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:39
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:3048
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:32
MWExceptionHandler\logException
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).
Definition: MWExceptionHandler.php:656
LoggedOutEditToken
Value object representing a logged-out user's edit token.
Definition: LoggedOutEditToken.php:35
User\matchEditToken
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:3885
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2554
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:54
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:3820
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:3642
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:2329
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1061
MWException
MediaWiki exception.
Definition: MWException.php:26
User\invalidationTokenUrl
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4019
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2015
User\$mDatePreference
string $mDatePreference
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:168
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:5364
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1029
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:1078
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4050
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1200
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:1948
getPermissionManager
getPermissionManager()
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2262
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2464
User\isTempWatched
isTempWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check if the article is temporarily watched.
Definition: User.php:3246
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:3802
User\logout
logout()
Log this user out.
Definition: User.php:3406
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:3992
User\initEditCountInternal
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition: User.php:4449
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:439
$wgLang
$wgLang
Definition: Setup.php:782
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1827
User\getImplicitGroups
static getImplicitGroups()
Get a list of implicit groups.
Definition: User.php:4294
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2030
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:3782
User\$mBlock
AbstractBlock null $mBlock
Definition: User.php:204
User\loadDefaults
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition: User.php:1154
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:943
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2361
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:2979
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1735
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:85
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3063
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4108
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:3830
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2346
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:1502
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1790
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:83
User\TOKEN_LENGTH
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:60
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:592
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1840
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:130
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:608
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1912
User\setName
setName( $str)
Set the user name.
Definition: User.php:2099
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:123
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:2858
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:7356
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:912
User\__get
& __get( $name)
Definition: User.php:240
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5940
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3305
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:3454
User\addNewUserLogEntry
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:4501
User\isAllowedAll
isAllowedAll(... $permissions)
Definition: User.php:3153
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:833
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4177
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:78
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:2657
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2195
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:618
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3012
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:859
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:3141
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1211
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:6102
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:1970
$wgForceHTTPS
bool $wgForceHTTPS
If this is true, when an insecure HTTP request is received, always redirect to HTTPS.
Definition: DefaultSettings.php:166
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2674
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
User\isDnsBlacklisted
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1624
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:2373
User\$mId
int $mId
Cache variables.
Definition: User.php:117
User\isSystemUser
isSystemUser()
Get whether the user is a system user.
Definition: User.php:3122
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:1984
User\__toString
__toString()
Definition: User.php:236
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
CannotCreateActorException
Exception thrown when an actor can't be created.
Definition: CannotCreateActorException.php:28
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1464
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:125
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:2645
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:3712
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:3370
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4205
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4383
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3320
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition: User.php:3263
User\VERSION
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:71
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:3899
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5935
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4271
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3285
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4284
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:3793
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:451
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:4537
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:1937
User\$mLoadedItems
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:149
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:9378
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2395
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:53
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:43
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4310
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2062
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3332
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1840
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4163
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:4259
MediaWiki\Block\AbstractBlock\appliesToRight
appliesToRight( $right)
Determine whether the block prevents a given right.
Definition: AbstractBlock.php:268
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:4462
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2468
$cache
$cache
Definition: mcc.php:33
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:6184
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3086
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:32
User\$mActorId
int null $mActorId
Definition: User.php:121
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2163
User\purge
static purge( $dbDomain, $userId)
Definition: User.php:428
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2538
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:879
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:921
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:138
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1001
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:1122
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4122
User\$mGlobalBlock
AbstractBlock $mGlobalBlock
Definition: User.php:185
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:4625
User\getBlockedStatus
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1560
User\$idCacheByName
static int[] $idCacheByName
Definition: User.php:216
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:4475
UserCache\singleton
static singleton()
Definition: UserCache.php:34
User\clearSharedCache
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2303
User\isBlocked
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:1902
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:572
$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:462
$wgLearnerMemberSince
$wgLearnerMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9379
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1430
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:3966
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:2755
User\$mEmailToken
string $mEmailToken
Definition: User.php:136
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:5719
User\equals
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:4615
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user data from the database.
Definition: User.php:1248
User\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:4511
User\$mName
string $mName
Definition: User.php:119
User\$mRegistration
string $mRegistration
Definition: User.php:140
MediaWiki\Block\AbstractBlock
Definition: AbstractBlock.php:37
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for the wiki.
Definition: DefaultSettings.php:4873
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:662
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:88
User\$mRequest
WebRequest $mRequest
Definition: User.php:197
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:644
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3229
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:2706
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:5714
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:987
User\makeUpdateConditions
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1445
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1776
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:3758
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:3869
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:2895
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1190
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:54
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:2739
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:213
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:869
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:2071
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:5352
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3418
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:2963
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4093
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1042
Wikimedia\Rdbms\IDatabase\delete
delete( $table, $conds, $fname=__METHOD__)
Delete all rows in a table that match a condition.
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:450
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
User\getLatestEditTimestamp
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4190
User\setEditCountInternal
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:4438
User\setPasswordInternal
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2430
User\$mEditCount
int $mEditCount
Definition: User.php:142
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:134
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4218
User\$mFrom
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:162
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3169
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:2818
$type
$type
Definition: testCompression.php:52
User\trackBlockWithCookie
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition: User.php:1235