MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
30 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
43 use Wikimedia\Assert\Assert;
44 use Wikimedia\Assert\PreconditionException;
45 use Wikimedia\IPUtils;
49 use Wikimedia\ScopedCallback;
50 use Wikimedia\Timestamp\ConvertibleTimestamp;
51 
68  use ProtectedHookAccessorTrait;
70 
75  public const READ_EXCLUSIVE = IDBAccessObject::READ_EXCLUSIVE;
76 
81  public const READ_LOCKING = IDBAccessObject::READ_LOCKING;
82 
86  public const TOKEN_LENGTH = 32;
87 
91  public const INVALID_TOKEN = '*** INVALID ***';
92 
97  private const VERSION = 17;
98 
102  public const CHECK_USER_RIGHTS = true;
103 
107  public const IGNORE_USER_RIGHTS = false;
108 
113  public const MAINTENANCE_SCRIPT_USER = 'Maintenance script';
114 
122  protected static $mCacheVars = [
123  // user table
124  'mId',
125  'mName',
126  'mRealName',
127  'mEmail',
128  'mTouched',
129  'mToken',
130  'mEmailAuthenticated',
131  'mEmailToken',
132  'mEmailTokenExpires',
133  'mRegistration',
134  // actor table
135  'mActorId',
136  ];
137 
139  // Some of these are public, including for use by the UserFactory, but they generally
140  // should not be set manually
141  // @{
143  public $mId;
145  public $mName;
151  public $mActorId;
153  public $mRealName;
154 
156  public $mEmail;
158  public $mTouched;
160  protected $mQuickTouched;
162  protected $mToken;
166  protected $mEmailToken;
170  protected $mRegistration;
171  // @}
172 
173  // @{
177  protected $mLoadedItems = [];
178  // @}
179 
190  public $mFrom;
191 
196  protected $mDatePreference;
203  public $mBlockedby;
205  protected $mHash;
211  protected $mBlockreason;
213  protected $mGlobalBlock;
215  protected $mLocked;
222  public $mHideName;
223 
225  private $mRequest;
226 
232  public $mBlock;
233 
235  private $mBlockedFromCreateAccount = false;
236 
238  protected $queryFlagsUsed = self::READ_NORMAL;
239 
242 
258  public function __construct() {
259  $this->clearInstanceCache( 'defaults' );
260  }
261 
268  public function getWikiId() {
269  return self::LOCAL;
270  }
271 
275  public function __toString() {
276  return $this->getName();
277  }
278 
279  public function &__get( $name ) {
280  // A shortcut for $mRights deprecation phase
281  if ( $name === 'mRights' ) {
282  $copy = MediaWikiServices::getInstance()
283  ->getPermissionManager()
284  ->getUserPermissions( $this );
285  return $copy;
286  } elseif ( $name === 'mOptions' ) {
287  wfDeprecated( 'User::$mOptions', '1.35' );
288  $options = MediaWikiServices::getInstance()->getUserOptionsLookup()->getOptions( $this );
289  return $options;
290  } elseif ( !property_exists( $this, $name ) ) {
291  // T227688 - do not break $u->foo['bar'] = 1
292  wfLogWarning( 'tried to get non-existent property' );
293  $this->$name = null;
294  return $this->$name;
295  } else {
296  wfLogWarning( 'tried to get non-visible property' );
297  $null = null;
298  return $null;
299  }
300  }
301 
302  public function __set( $name, $value ) {
303  // A shortcut for $mRights deprecation phase, only known legitimate use was for
304  // testing purposes, other uses seem bad in principle
305  if ( $name === 'mRights' ) {
306  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
307  $this,
308  $value ?? []
309  );
310  } elseif ( $name === 'mOptions' ) {
311  wfDeprecated( 'User::$mOptions', '1.35' );
312  $userOptionsManager = MediaWikiServices::getInstance()->getUserOptionsManager();
313  $userOptionsManager->clearUserOptionsCache( $this );
314  foreach ( $value as $key => $val ) {
315  $userOptionsManager->setOption( $this, $key, $val );
316  }
317  } elseif ( !property_exists( $this, $name ) ) {
318  $this->$name = $value;
319  } else {
320  wfLogWarning( 'tried to set non-visible property' );
321  }
322  }
323 
324  public function __sleep(): array {
325  return array_diff(
326  array_keys( get_object_vars( $this ) ),
327  [
328  'mThisAsAuthority' // memoization, will be recreated on demand.
329  ]
330  );
331  }
332 
347  public function isSafeToLoad() {
348  global $wgFullyInitialised;
349 
350  // The user is safe to load if:
351  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
352  // * mLoadedItems === true (already loaded)
353  // * mFrom !== 'session' (sessions not involved at all)
354 
355  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
356  $this->mLoadedItems === true || $this->mFrom !== 'session';
357  }
358 
364  public function load( $flags = self::READ_NORMAL ) {
365  global $wgFullyInitialised;
366 
367  if ( $this->mLoadedItems === true ) {
368  return;
369  }
370 
371  // Set it now to avoid infinite recursion in accessors
372  $oldLoadedItems = $this->mLoadedItems;
373  $this->mLoadedItems = true;
374  $this->queryFlagsUsed = $flags;
375 
376  // If this is called too early, things are likely to break.
377  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
378  LoggerFactory::getInstance( 'session' )
379  ->warning( 'User::loadFromSession called before the end of Setup.php', [
380  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
381  ] );
382  $this->loadDefaults();
383  $this->mLoadedItems = $oldLoadedItems;
384  return;
385  }
386 
387  switch ( $this->mFrom ) {
388  case 'defaults':
389  $this->loadDefaults();
390  break;
391  case 'id':
392  // Make sure this thread sees its own changes, if the ID isn't 0
393  if ( $this->mId != 0 ) {
394  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
395  if ( $lb->hasOrMadeRecentPrimaryChanges() ) {
396  $flags |= self::READ_LATEST;
397  $this->queryFlagsUsed = $flags;
398  }
399  }
400 
401  $this->loadFromId( $flags );
402  break;
403  case 'actor':
404  case 'name':
405  // Make sure this thread sees its own changes
406  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
407  if ( $lb->hasOrMadeRecentPrimaryChanges() ) {
408  $flags |= self::READ_LATEST;
409  $this->queryFlagsUsed = $flags;
410  }
411 
412  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
413  $row = wfGetDB( $index )->selectRow(
414  'actor',
415  [ 'actor_id', 'actor_user', 'actor_name' ],
416  $this->mFrom === 'name'
417  // make sure to use normalized form of IP for anonymous users
418  ? [ 'actor_name' => IPUtils::sanitizeIP( $this->mName ) ]
419  : [ 'actor_id' => $this->mActorId ],
420  __METHOD__,
421  $options
422  );
423 
424  if ( !$row ) {
425  // Ugh.
426  $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
427  } elseif ( $row->actor_user ) {
428  $this->mId = $row->actor_user;
429  $this->loadFromId( $flags );
430  } else {
431  $this->loadDefaults( $row->actor_name, $row->actor_id );
432  }
433  break;
434  case 'session':
435  if ( !$this->loadFromSession() ) {
436  // Loading from session failed. Load defaults.
437  $this->loadDefaults();
438  }
439  $this->getHookRunner()->onUserLoadAfterLoadFromSession( $this );
440  break;
441  default:
442  throw new UnexpectedValueException(
443  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
444  }
445  }
446 
452  public function loadFromId( $flags = self::READ_NORMAL ) {
453  if ( $this->mId == 0 ) {
454  // Anonymous users are not in the database (don't need cache)
455  $this->loadDefaults();
456  return false;
457  }
458 
459  // Try cache (unless this needs data from the primary DB).
460  // NOTE: if this thread called saveSettings(), the cache was cleared.
461  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
462  if ( $latest ) {
463  if ( !$this->loadFromDatabase( $flags ) ) {
464  // Can't load from ID
465  return false;
466  }
467  } else {
468  $this->loadFromCache();
469  }
470 
471  $this->mLoadedItems = true;
472  $this->queryFlagsUsed = $flags;
473 
474  return true;
475  }
476 
482  public static function purge( $dbDomain, $userId ) {
483  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
484  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
485  $cache->delete( $key );
486  }
487 
493  protected function getCacheKey( WANObjectCache $cache ) {
494  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
495 
496  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
497  }
498 
505  $id = $this->getId();
506 
507  return $id ? [ $this->getCacheKey( $cache ) ] : [];
508  }
509 
516  protected function loadFromCache() {
517  global $wgFullyInitialised;
518 
519  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
520  $data = $cache->getWithSetCallback(
521  $this->getCacheKey( $cache ),
522  $cache::TTL_HOUR,
523  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache, $wgFullyInitialised ) {
524  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
525  wfDebug( "User: cache miss for user {$this->mId}" );
526 
527  $this->loadFromDatabase( self::READ_NORMAL );
528 
529  $data = [];
530  foreach ( self::$mCacheVars as $name ) {
531  $data[$name] = $this->$name;
532  }
533 
534  $ttl = $cache->adaptiveTTL( (int)wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
535 
536  if ( $wgFullyInitialised ) {
537  $groupMemberships = MediaWikiServices::getInstance()
538  ->getUserGroupManager()
539  ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
540 
541  // if a user group membership is about to expire, the cache needs to
542  // expire at that time (T163691)
543  foreach ( $groupMemberships as $ugm ) {
544  if ( $ugm->getExpiry() ) {
545  $secondsUntilExpiry =
546  (int)wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
547 
548  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
549  $ttl = $secondsUntilExpiry;
550  }
551  }
552  }
553  }
554 
555  return $data;
556  },
557  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
558  );
559 
560  // Restore from cache
561  foreach ( self::$mCacheVars as $name ) {
562  $this->$name = $data[$name];
563  }
564 
565  return true;
566  }
567 
568  /***************************************************************************/
569  // region newFrom*() static factory methods
595  public static function newFromName( $name, $validate = 'valid' ) {
596  // Backwards compatibility with strings / false
597  $validationLevels = [
598  'valid' => UserFactory::RIGOR_VALID,
599  'usable' => UserFactory::RIGOR_USABLE,
600  'creatable' => UserFactory::RIGOR_CREATABLE
601  ];
602  if ( $validate === true ) {
603  $validate = 'valid';
604  }
605  if ( $validate === false ) {
606  $validation = UserFactory::RIGOR_NONE;
607  } elseif ( array_key_exists( $validate, $validationLevels ) ) {
608  $validation = $validationLevels[ $validate ];
609  } else {
610  // Not a recognized value, probably a test for unsupported validation
611  // levels, regardless, just pass it along
612  $validation = $validate;
613  }
614 
615  $user = MediaWikiServices::getInstance()
616  ->getUserFactory()
617  ->newFromName( (string)$name, $validation );
618 
619  // UserFactory returns null instead of false
620  if ( $user === null ) {
621  $user = false;
622  }
623  return $user;
624  }
625 
636  public static function newFromId( $id ) {
637  return MediaWikiServices::getInstance()
638  ->getUserFactory()
639  ->newFromId( (int)$id );
640  }
641 
653  public static function newFromActorId( $id ) {
654  return MediaWikiServices::getInstance()
655  ->getUserFactory()
656  ->newFromActorId( (int)$id );
657  }
658 
672  public static function newFromIdentity( UserIdentity $identity ) {
673  // Don't use the service if we already have a User object,
674  // so that User::newFromIdentity calls don't break things in unit tests.
675  if ( $identity instanceof User ) {
676  return $identity;
677  }
678 
679  return MediaWikiServices::getInstance()
680  ->getUserFactory()
681  ->newFromUserIdentity( $identity );
682  }
683 
701  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
702  return MediaWikiServices::getInstance()
703  ->getUserFactory()
704  ->newFromAnyId( $userId, $userName, $actorId, $dbDomain );
705  }
706 
722  public static function newFromConfirmationCode( $code, $flags = self::READ_NORMAL ) {
723  return MediaWikiServices::getInstance()
724  ->getUserFactory()
725  ->newFromConfirmationCode( (string)$code, $flags );
726  }
727 
735  public static function newFromSession( WebRequest $request = null ) {
736  $user = new User;
737  $user->mFrom = 'session';
738  $user->mRequest = $request;
739  return $user;
740  }
741 
757  public static function newFromRow( $row, $data = null ) {
758  $user = new User;
759  $user->loadFromRow( $row, $data );
760  return $user;
761  }
762 
803  public static function newSystemUser( $name, $options = [] ) {
804  $options += [
805  'validate' => UserNameUtils::RIGOR_VALID,
806  'create' => true,
807  'steal' => false,
808  ];
809 
810  // Username validation
811  // Backwards compatibility with strings / false
812  $validationLevels = [
813  'valid' => UserNameUtils::RIGOR_VALID,
814  'usable' => UserNameUtils::RIGOR_USABLE,
815  'creatable' => UserNameUtils::RIGOR_CREATABLE
816  ];
817  $validate = $options['validate'];
818 
819  // @phan-suppress-next-line PhanSuspiciousValueComparison
820  if ( $validate === false ) {
821  $validation = UserNameUtils::RIGOR_NONE;
822  } elseif ( array_key_exists( $validate, $validationLevels ) ) {
823  $validation = $validationLevels[ $validate ];
824  } else {
825  // Not a recognized value, probably a test for unsupported validation
826  // levels, regardless, just pass it along
827  $validation = $validate;
828  }
829 
830  if ( $validation !== UserNameUtils::RIGOR_VALID ) {
832  __METHOD__ . ' options["validation"] parameter must be omitted or set to "valid".',
833  '1.36'
834  );
835  }
836  $services = MediaWikiServices::getInstance();
837  $userNameUtils = $services->getUserNameUtils();
838 
839  $name = $userNameUtils->getCanonical( (string)$name, $validation );
840  if ( $name === false ) {
841  return null;
842  }
843 
844  $loadBalancer = $services->getDBLoadBalancer();
845  $dbr = $loadBalancer->getConnectionRef( DB_REPLICA );
846 
847  $userQuery = self::getQueryInfo();
848  $row = $dbr->selectRow(
849  $userQuery['tables'],
850  $userQuery['fields'],
851  [ 'user_name' => $name ],
852  __METHOD__,
853  [],
854  $userQuery['joins']
855  );
856  if ( !$row ) {
857  // Try the primary database...
858  $dbw = $loadBalancer->getConnectionRef( DB_PRIMARY );
859  $row = $dbw->selectRow(
860  $userQuery['tables'],
861  $userQuery['fields'],
862  [ 'user_name' => $name ],
863  __METHOD__,
864  [],
865  $userQuery['joins']
866  );
867  }
868 
869  if ( !$row ) {
870  // No user. Create it?
871  if ( !$options['create'] ) {
872  // No.
873  return null;
874  }
875 
876  // If it's a reserved user that had an anonymous actor created for it at
877  // some point, we need special handling.
878  return self::insertNewUser( static function ( UserIdentity $actor, IDatabase $dbw ) {
879  return MediaWikiServices::getInstance()->getActorStore()->acquireSystemActorId( $actor, $dbw );
880  }, $name, [ 'token' => self::INVALID_TOKEN ] );
881  }
882 
883  $user = self::newFromRow( $row );
884 
885  if ( !$user->isSystemUser() ) {
886  // User exists. Steal it?
887  if ( !$options['steal'] ) {
888  return null;
889  }
890 
891  $services->getAuthManager()->revokeAccessForUser( $name );
892 
893  $user->invalidateEmail();
894  $user->mToken = self::INVALID_TOKEN;
895  $user->saveSettings();
896  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
897  }
898 
899  return $user;
900  }
901 
903  // endregion -- end of newFrom*() static factory methods
904 
910  public static function whoIs( $id ) {
911  return UserCache::singleton()->getProp( $id, 'name' );
912  }
913 
920  public static function whoIsReal( $id ) {
921  return UserCache::singleton()->getProp( $id, 'real_name' );
922  }
923 
931  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
932  $actor = MediaWikiServices::getInstance()
933  ->getUserIdentityLookup()
934  ->getUserIdentityByName( (string)$name, $flags );
935  if ( $actor && $actor->getId() ) {
936  return $actor->getId();
937  }
938  return null;
939  }
940 
951  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
952  if ( $groups === [] ) {
953  return UserArrayFromResult::newFromIDs( [] );
954  }
955 
956  $groups = array_unique( (array)$groups );
957  $limit = min( 5000, $limit );
958 
959  $conds = [ 'ug_group' => $groups ];
960  if ( $after !== null ) {
961  $conds[] = 'ug_user > ' . (int)$after;
962  }
963 
964  $dbr = wfGetDB( DB_REPLICA );
965  $ids = $dbr->selectFieldValues(
966  'user_groups',
967  'ug_user',
968  $conds,
969  __METHOD__,
970  [
971  'DISTINCT' => true,
972  'ORDER BY' => 'ug_user',
973  'LIMIT' => $limit,
974  ]
975  ) ?: [];
976  return UserArray::newFromIDs( $ids );
977  }
978 
985  public function isValidPassword( $password ) {
986  // simple boolean wrapper for checkPasswordValidity
987  return $this->checkPasswordValidity( $password )->isGood();
988  }
989 
1011  public function checkPasswordValidity( $password ) {
1012  $passwordPolicy = MediaWikiServices::getInstance()->getMainConfig()->get( 'PasswordPolicy' );
1013 
1014  $upp = new UserPasswordPolicy(
1015  $passwordPolicy['policies'],
1016  $passwordPolicy['checks']
1017  );
1018 
1019  $status = Status::newGood( [] );
1020  $result = false; // init $result to false for the internal checks
1021 
1022  if ( !$this->getHookRunner()->onIsValidPassword( $password, $result, $this ) ) {
1023  $status->error( $result );
1024  return $status;
1025  }
1026 
1027  if ( $result === false ) {
1028  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1029  return $status;
1030  }
1031 
1032  if ( $result === true ) {
1033  return $status;
1034  }
1035 
1036  $status->error( $result );
1037  return $status; // the isValidPassword hook set a string $result and returned true
1038  }
1039 
1049  public function loadDefaults( $name = false, $actorId = null ) {
1050  $this->mId = 0;
1051  $this->mName = $name;
1052  $this->mActorId = $actorId;
1053  $this->mRealName = '';
1054  $this->mEmail = '';
1055 
1056  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1057  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1058  if ( $loggedOut !== 0 ) {
1059  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1060  } else {
1061  $this->mTouched = '1'; # Allow any pages to be cached
1062  }
1063 
1064  $this->mToken = null; // Don't run cryptographic functions till we need a token
1065  $this->mEmailAuthenticated = null;
1066  $this->mEmailToken = '';
1067  $this->mEmailTokenExpires = null;
1068  $this->mRegistration = wfTimestamp( TS_MW );
1069 
1070  $this->getHookRunner()->onUserLoadDefaults( $this, $name );
1071  }
1072 
1085  public function isItemLoaded( $item, $all = 'all' ) {
1086  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1087  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1088  }
1089 
1097  public function setItemLoaded( $item ) {
1098  if ( is_array( $this->mLoadedItems ) ) {
1099  $this->mLoadedItems[$item] = true;
1100  }
1101  }
1102 
1108  private function loadFromSession() {
1109  // MediaWiki\Session\Session already did the necessary authentication of the user
1110  // returned here, so just use it if applicable.
1111  $session = $this->getRequest()->getSession();
1112  $user = $session->getUser();
1113  if ( $user->isRegistered() ) {
1114  $this->loadFromUserObject( $user );
1115 
1116  // Other code expects these to be set in the session, so set them.
1117  $session->set( 'wsUserID', $this->getId() );
1118  $session->set( 'wsUserName', $this->getName() );
1119  $session->set( 'wsToken', $this->getToken() );
1120 
1121  return true;
1122  }
1123 
1124  return false;
1125  }
1126 
1134  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1135  // Paranoia
1136  $this->mId = intval( $this->mId );
1137 
1138  if ( !$this->mId ) {
1139  // Anonymous users are not in the database
1140  $this->loadDefaults();
1141  return false;
1142  }
1143 
1144  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1145  $db = wfGetDB( $index );
1146 
1147  $userQuery = self::getQueryInfo();
1148  $s = $db->selectRow(
1149  $userQuery['tables'],
1150  $userQuery['fields'],
1151  [ 'user_id' => $this->mId ],
1152  __METHOD__,
1153  $options,
1154  $userQuery['joins']
1155  );
1156 
1157  $this->queryFlagsUsed = $flags;
1158 
1159  // hook is hard deprecated since 1.37
1160  $this->getHookRunner()->onUserLoadFromDatabase( $this, $s );
1161 
1162  if ( $s !== false ) {
1163  // Initialise user table data
1164  $this->loadFromRow( $s );
1165  return true;
1166  }
1167 
1168  // Invalid user_id
1169  $this->mId = 0;
1170  $this->loadDefaults( 'Unknown user' );
1171 
1172  return false;
1173  }
1174 
1186  protected function loadFromRow( $row, $data = null ) {
1187  if ( !is_object( $row ) ) {
1188  throw new InvalidArgumentException( '$row must be an object' );
1189  }
1190 
1191  $all = true;
1192 
1193  if ( isset( $row->actor_id ) ) {
1194  $this->mActorId = (int)$row->actor_id;
1195  if ( $this->mActorId !== 0 ) {
1196  $this->mFrom = 'actor';
1197  }
1198  $this->setItemLoaded( 'actor' );
1199  } else {
1200  $all = false;
1201  }
1202 
1203  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1204  $this->mName = $row->user_name;
1205  $this->mFrom = 'name';
1206  $this->setItemLoaded( 'name' );
1207  } else {
1208  $all = false;
1209  }
1210 
1211  if ( isset( $row->user_real_name ) ) {
1212  $this->mRealName = $row->user_real_name;
1213  $this->setItemLoaded( 'realname' );
1214  } else {
1215  $all = false;
1216  }
1217 
1218  if ( isset( $row->user_id ) ) {
1219  $this->mId = intval( $row->user_id );
1220  if ( $this->mId !== 0 ) {
1221  $this->mFrom = 'id';
1222  }
1223  $this->setItemLoaded( 'id' );
1224  } else {
1225  $all = false;
1226  }
1227 
1228  if ( isset( $row->user_editcount ) ) {
1229  // Don't try to set edit count for anonymous users
1230  // We check the id here and not in UserEditTracker because calling
1231  // User::getId() can trigger some other loading. This will result in
1232  // discarding the user_editcount field for rows if the id wasn't set.
1233  if ( $this->mId !== null && $this->mId !== 0 ) {
1234  MediaWikiServices::getInstance()
1235  ->getUserEditTracker()
1236  ->setCachedUserEditCount( $this, (int)$row->user_editcount );
1237  }
1238  } else {
1239  $all = false;
1240  }
1241 
1242  if ( isset( $row->user_touched ) ) {
1243  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1244  } else {
1245  $all = false;
1246  }
1247 
1248  if ( isset( $row->user_token ) ) {
1249  // The definition for the column is binary(32), so trim the NULs
1250  // that appends. The previous definition was char(32), so trim
1251  // spaces too.
1252  $this->mToken = rtrim( $row->user_token, " \0" );
1253  if ( $this->mToken === '' ) {
1254  $this->mToken = null;
1255  }
1256  } else {
1257  $all = false;
1258  }
1259 
1260  if ( isset( $row->user_email ) ) {
1261  $this->mEmail = $row->user_email;
1262  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1263  $this->mEmailToken = $row->user_email_token;
1264  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1265  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1266  } else {
1267  $all = false;
1268  }
1269 
1270  if ( $all ) {
1271  $this->mLoadedItems = true;
1272  }
1273 
1274  if ( is_array( $data ) ) {
1275 
1276  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1277  MediaWikiServices::getInstance()
1278  ->getUserGroupManager()
1279  ->loadGroupMembershipsFromArray(
1280  $this,
1281  $data['user_groups'],
1282  $this->queryFlagsUsed
1283  );
1284  }
1285  }
1286  }
1287 
1293  protected function loadFromUserObject( $user ) {
1294  $user->load();
1295  foreach ( self::$mCacheVars as $var ) {
1296  $this->$var = $user->$var;
1297  }
1298  }
1299 
1309  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1310  if ( $this->mTouched ) {
1311  // CAS check: only update if the row wasn't changed sicne it was loaded.
1312  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1313  }
1314 
1315  return $conditions;
1316  }
1317 
1328  public function checkAndSetTouched() {
1329  $this->load();
1330 
1331  if ( !$this->mId ) {
1332  return false; // anon
1333  }
1334 
1335  // Get a new user_touched that is higher than the old one
1336  $newTouched = $this->newTouchedTimestamp();
1337 
1338  $dbw = wfGetDB( DB_PRIMARY );
1339  $dbw->update( 'user',
1340  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1341  $this->makeUpdateConditions( $dbw, [
1342  'user_id' => $this->mId,
1343  ] ),
1344  __METHOD__
1345  );
1346  $success = ( $dbw->affectedRows() > 0 );
1347 
1348  if ( $success ) {
1349  $this->mTouched = $newTouched;
1350  $this->clearSharedCache( 'changed' );
1351  } else {
1352  // Clears on failure too since that is desired if the cache is stale
1353  $this->clearSharedCache( 'refresh' );
1354  }
1355 
1356  return $success;
1357  }
1358 
1366  public function clearInstanceCache( $reloadFrom = false ) {
1367  global $wgFullyInitialised;
1368 
1369  $this->mDatePreference = null;
1370  $this->mBlockedby = -1; # Unset
1371  $this->mHash = false;
1372  $this->mThisAsAuthority = null;
1373 
1374  if ( $wgFullyInitialised && $this->mFrom ) {
1375  $services = MediaWikiServices::getInstance();
1376  $services->getPermissionManager()->invalidateUsersRightsCache( $this );
1377  $services->getUserOptionsManager()->clearUserOptionsCache( $this );
1378  $services->getTalkPageNotificationManager()->clearInstanceCache( $this );
1379  $services->getUserGroupManager()->clearCache( $this );
1380  $services->getUserEditTracker()->clearUserEditCache( $this );
1381  }
1382 
1383  if ( $reloadFrom ) {
1384  $this->mLoadedItems = [];
1385  $this->mFrom = $reloadFrom;
1386  }
1387  }
1388 
1400  private function getBlockedStatus( $fromReplica = true, $disableIpBlockExemptChecking = false ) {
1401  if ( $this->mBlockedby != -1 ) {
1402  return;
1403  }
1404 
1405  wfDebug( __METHOD__ . ": checking blocked status for " . $this->getName() );
1406 
1407  // Initialize data...
1408  // Otherwise something ends up stomping on $this->mBlockedby when
1409  // things get lazy-loaded later, causing false positive block hits
1410  // due to -1 !== 0. Probably session-related... Nothing should be
1411  // overwriting mBlockedby, surely?
1412  $this->load();
1413 
1414  // TODO: Block checking shouldn't really be done from the User object. Block
1415  // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1416  // which need more knowledge of the request context than the User should have.
1417  // Since we do currently check blocks from the User, we have to do the following
1418  // here:
1419  // - Check if this is the user associated with the main request
1420  // - If so, pass the relevant request information to the block manager
1421  $request = null;
1422  if ( $this->isGlobalSessionUser() ) {
1423  // This is the global user, so we need to pass the request
1424  $request = $this->getRequest();
1425  }
1426 
1427  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1428  $this,
1429  $request,
1430  $fromReplica,
1431  $disableIpBlockExemptChecking
1432  );
1433 
1434  if ( $block ) {
1435  $this->mBlock = $block;
1436  $this->mBlockedby = $block->getByName();
1437  $this->mBlockreason = $block->getReason();
1438  $this->mHideName = $block->getHideName();
1439  } else {
1440  $this->mBlock = null;
1441  $this->mBlockedby = '';
1442  $this->mBlockreason = '';
1443  $this->mHideName = 0;
1444  }
1445  }
1446 
1452  public function isPingLimitable() {
1453  $rateLimitsExcludedIPs = MediaWikiServices::getInstance()->getMainConfig()->get( 'RateLimitsExcludedIPs' );
1454  if ( IPUtils::isInRanges( $this->getRequest()->getIP(), $rateLimitsExcludedIPs ) ) {
1455  // No other good way currently to disable rate limits
1456  // for specific IPs. :P
1457  // But this is a crappy hack and should die.
1458  return false;
1459  }
1460  return !$this->isAllowed( 'noratelimit' );
1461  }
1462 
1479  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1480  $logger = LoggerFactory::getInstance( 'ratelimit' );
1481 
1482  // Call the 'PingLimiter' hook
1483  $result = false;
1484  if ( !$this->getHookRunner()->onPingLimiter( $this, $action, $result, $incrBy ) ) {
1485  return $result;
1486  }
1487 
1488  $rateLimits = MediaWikiServices::getInstance()->getMainConfig()->get( 'RateLimits' );
1489  if ( !isset( $rateLimits[$action] ) ) {
1490  return false;
1491  }
1492 
1493  $limits = array_merge(
1494  [ '&can-bypass' => true ],
1495  $rateLimits[$action]
1496  );
1497 
1498  // Some groups shouldn't trigger the ping limiter, ever
1499  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1500  return false;
1501  }
1502 
1503  $logger->debug( __METHOD__ . ": limiting $action rate for {$this->getName()}" );
1504 
1505  $keys = [];
1506  $id = $this->getId();
1507  $isNewbie = $this->isNewbie();
1509 
1510  if ( $id == 0 ) {
1511  // "shared anon" limit, for all anons combined
1512  if ( isset( $limits['anon'] ) ) {
1513  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1514  }
1515  } else {
1516  // "global per name" limit, across sites
1517  if ( isset( $limits['user-global'] ) ) {
1518  $lookup = MediaWikiServices::getInstance()
1519  ->getCentralIdLookupFactory()
1520  ->getNonLocalLookup();
1521 
1522  $centralId = $lookup
1523  ? $lookup->centralIdFromLocalUser( $this, CentralIdLookup::AUDIENCE_RAW )
1524  : 0;
1525 
1526  if ( $centralId ) {
1527  // We don't have proper realms, use provider ID.
1528  $realm = $lookup->getProviderId();
1529 
1530  $globalKey = $cache->makeGlobalKey( 'limiter', $action, 'user-global',
1531  $realm, $centralId );
1532  } else {
1533  // Fall back to a local key for a local ID
1534  $globalKey = $cache->makeKey( 'limiter', $action, 'user-global',
1535  'local', $id );
1536  }
1537  $keys[$globalKey] = $limits['user-global'];
1538  }
1539  }
1540 
1541  if ( $isNewbie ) {
1542  // "per ip" limit for anons and newbie users
1543  if ( isset( $limits['ip'] ) ) {
1544  $ip = $this->getRequest()->getIP();
1545  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
1546  }
1547  // "per subnet" limit for anons and newbie users
1548  if ( isset( $limits['subnet'] ) ) {
1549  $ip = $this->getRequest()->getIP();
1550  $subnet = IPUtils::getSubnet( $ip );
1551  if ( $subnet !== false ) {
1552  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
1553  }
1554  }
1555  }
1556 
1557  // determine the "per user account" limit
1558  $userLimit = false;
1559  if ( $id !== 0 && isset( $limits['user'] ) ) {
1560  // default limit for logged-in users
1561  $userLimit = $limits['user'];
1562  }
1563  // limits for newbie logged-in users (overrides all the normal user limits)
1564  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1565  $userLimit = $limits['newbie'];
1566  } else {
1567  // Check for group-specific limits
1568  // If more than one group applies, use the highest allowance (if higher than the default)
1569  $userGroups = MediaWikiServices::getInstance()->getUserGroupManager()->getUserGroups( $this );
1570  foreach ( $userGroups as $group ) {
1571  if ( isset( $limits[$group] ) ) {
1572  if ( $userLimit === false
1573  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1574  ) {
1575  $userLimit = $limits[$group];
1576  }
1577  }
1578  }
1579  }
1580 
1581  // Set the user limit key
1582  if ( $userLimit !== false ) {
1583  // phan is confused because &can-bypass's value is a bool, so it assumes
1584  // that $userLimit is also a bool here.
1585  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1586  list( $max, $period ) = $userLimit;
1587  $logger->debug( __METHOD__ . ": effective user limit: $max in {$period}s" );
1588  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
1589  }
1590 
1591  // ip-based limits for all ping-limitable users
1592  if ( isset( $limits['ip-all'] ) ) {
1593  $ip = $this->getRequest()->getIP();
1594  // ignore if user limit is more permissive
1595  if ( $isNewbie || $userLimit === false
1596  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1597  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip-all', $ip )] = $limits['ip-all'];
1598  }
1599  }
1600 
1601  // subnet-based limits for all ping-limitable users
1602  if ( isset( $limits['subnet-all'] ) ) {
1603  $ip = $this->getRequest()->getIP();
1604  $subnet = IPUtils::getSubnet( $ip );
1605  if ( $subnet !== false ) {
1606  // ignore if user limit is more permissive
1607  if ( $isNewbie || $userLimit === false
1608  || $limits['ip-all'][0] / $limits['ip-all'][1]
1609  > $userLimit[0] / $userLimit[1] ) {
1610  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet-all', $subnet )] = $limits['subnet-all'];
1611  }
1612  }
1613  }
1614 
1615  // XXX: We may want to use $cache->getCurrentTime() here, but that would make it
1616  // harder to test for T246991. Also $cache->getCurrentTime() is documented
1617  // as being for testing only, so it apparently should not be called here.
1618  $now = MWTimestamp::time();
1619  $clockFudge = 3; // avoid log spam when a clock is slightly off
1620 
1621  $triggered = false;
1622  foreach ( $keys as $key => $limit ) {
1623 
1624  // Do the update in a merge callback, for atomicity.
1625  // To use merge(), we need to explicitly track the desired expiry timestamp.
1626  // This tracking was introduced to investigate T246991. Once it is no longer needed,
1627  // we could go back to incrWithInit(), though that has more potential for race
1628  // conditions between the get() and incrWithInit() calls.
1629  $cache->merge(
1630  $key,
1631  function ( $cache, $key, $data, &$expiry )
1632  use ( $action, $logger, &$triggered, $now, $clockFudge, $limit, $incrBy )
1633  {
1634  // phan is confused because &can-bypass's value is a bool, so it assumes
1635  // that $userLimit is also a bool here.
1636  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1637  list( $max, $period ) = $limit;
1638 
1639  $expiry = $now + (int)$period;
1640  $count = 0;
1641 
1642  // Already pinged?
1643  if ( $data ) {
1644  // NOTE: in order to investigate T246991, we write the expiry time
1645  // into the payload, along with the count.
1646  $fields = explode( '|', $data );
1647  $storedCount = (int)( $fields[0] ?? 0 );
1648  $storedExpiry = (int)( $fields[1] ?? PHP_INT_MAX );
1649 
1650  // Found a stale entry. This should not happen!
1651  if ( $storedExpiry < ( $now + $clockFudge ) ) {
1652  $logger->info(
1653  'User::pingLimiter: '
1654  . 'Stale rate limit entry, cache key failed to expire (T246991)',
1655  [
1656  'action' => $action,
1657  'user' => $this->getName(),
1658  'limit' => $max,
1659  'period' => $period,
1660  'count' => $storedCount,
1661  'key' => $key,
1662  'expiry' => MWTimestamp::convert( TS_DB, $storedExpiry ),
1663  ]
1664  );
1665  } else {
1666  // NOTE: We'll keep the original expiry when bumping counters,
1667  // resulting in a kind of fixed-window throttle.
1668  $expiry = min( $storedExpiry, $now + (int)$period );
1669  $count = $storedCount;
1670  }
1671  }
1672 
1673  // Limit exceeded!
1674  if ( $count >= $max ) {
1675  if ( !$triggered ) {
1676  $logger->info(
1677  'User::pingLimiter: User tripped rate limit',
1678  [
1679  'action' => $action,
1680  'user' => $this->getName(),
1681  'ip' => $this->getRequest()->getIP(),
1682  'limit' => $max,
1683  'period' => $period,
1684  'count' => $count,
1685  'key' => $key
1686  ]
1687  );
1688  }
1689 
1690  $triggered = true;
1691  }
1692 
1693  $count += $incrBy;
1694  $data = "$count|$expiry";
1695  return $data;
1696  }
1697  );
1698  }
1699 
1700  return $triggered;
1701  }
1702 
1715  public function isBlocked( $fromReplica = true ) {
1716  return $this->getBlock( $fromReplica ) !== null;
1717  }
1718 
1731  public function getBlock(
1732  $freshness = self::READ_NORMAL,
1733  $disableIpBlockExemptChecking = false
1734  ): ?Block {
1735  if ( is_bool( $freshness ) ) {
1736  $fromReplica = $freshness;
1737  } else {
1738  $fromReplica = ( $freshness !== self::READ_LATEST );
1739  }
1740 
1741  $this->getBlockedStatus( $fromReplica, $disableIpBlockExemptChecking );
1742  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
1743  }
1744 
1756  public function isBlockedFrom( $title, $fromReplica = false ) {
1757  // TODO: remove the cast when PermissionManager accepts PageIdentity
1759  return MediaWikiServices::getInstance()->getPermissionManager()
1760  ->isBlockedFrom( $this, $title, $fromReplica );
1761  }
1762 
1769  public function blockedBy() {
1770  wfDeprecated( __METHOD__, '1.38' );
1771  $this->getBlockedStatus();
1772  return $this->mBlockedby;
1773  }
1774 
1781  public function blockedFor() {
1782  $this->getBlockedStatus();
1783  return $this->mBlockreason;
1784  }
1785 
1792  public function getBlockId() {
1793  wfDeprecated( __METHOD__, '1.38' );
1794  $this->getBlockedStatus();
1795  return ( $this->mBlock ? $this->mBlock->getId() : false );
1796  }
1797 
1806  public function isBlockedGlobally( $ip = '' ) {
1807  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
1808  }
1809 
1820  public function getGlobalBlock( $ip = '' ) {
1821  if ( $this->mGlobalBlock !== null ) {
1822  return $this->mGlobalBlock ?: null;
1823  }
1824  // User is already an IP?
1825  if ( IPUtils::isIPAddress( $this->getName() ) ) {
1826  $ip = $this->getName();
1827  } elseif ( !$ip ) {
1828  $ip = $this->getRequest()->getIP();
1829  }
1830  $blocked = false;
1831  $block = null;
1832  $this->getHookRunner()->onUserIsBlockedGlobally( $this, $ip, $blocked, $block );
1833 
1834  if ( $blocked && $block === null ) {
1835  // back-compat: UserIsBlockedGlobally didn't have $block param first
1836  $block = new SystemBlock( [
1837  'address' => $ip,
1838  'systemBlock' => 'global-block'
1839  ] );
1840  }
1841 
1842  $this->mGlobalBlock = $blocked ? $block : false;
1843  return $this->mGlobalBlock ?: null;
1844  }
1845 
1851  public function isLocked() {
1852  if ( $this->mLocked !== null ) {
1853  return $this->mLocked;
1854  }
1855  // Reset for hook
1856  $this->mLocked = false;
1857  $this->getHookRunner()->onUserIsLocked( $this, $this->mLocked );
1858  return $this->mLocked;
1859  }
1860 
1866  public function isHidden() {
1867  if ( $this->mHideName !== null ) {
1868  return (bool)$this->mHideName;
1869  }
1870  $this->getBlockedStatus();
1871  return (bool)$this->mHideName;
1872  }
1873 
1879  public function getId( $wikiId = self::LOCAL ): int {
1880  $this->deprecateInvalidCrossWiki( $wikiId, '1.36' );
1881  if ( $this->mId === null && $this->mName !== null ) {
1882  $userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils();
1883  if ( $userNameUtils->isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) ) {
1884  // Special case, we know the user is anonymous
1885  // Note that "external" users are "local" (they have an actor ID that is relative to
1886  // the local wiki).
1887  return 0;
1888  }
1889  }
1890 
1891  if ( !$this->isItemLoaded( 'id' ) ) {
1892  // Don't load if this was initialized from an ID
1893  $this->load();
1894  }
1895 
1896  return (int)$this->mId;
1897  }
1898 
1903  public function setId( $v ) {
1904  $this->mId = $v;
1905  $this->clearInstanceCache( 'id' );
1906  }
1907 
1912  public function getName(): string {
1913  if ( $this->isItemLoaded( 'name', 'only' ) ) {
1914  // Special case optimisation
1915  return $this->mName;
1916  }
1917 
1918  $this->load();
1919  if ( $this->mName === false ) {
1920  // Clean up IPs
1921  $this->mName = IPUtils::sanitizeIP( $this->getRequest()->getIP() );
1922  }
1923 
1924  return $this->mName;
1925  }
1926 
1940  public function setName( $str ) {
1941  $this->load();
1942  $this->mName = $str;
1943  }
1944 
1958  public function getActorId( $dbwOrWikiId = self::LOCAL ): int {
1959  if ( $dbwOrWikiId ) {
1960  wfDeprecatedMsg( 'Passing a parameter to getActorId() is deprecated', '1.36' );
1961  }
1962 
1963  if ( is_string( $dbwOrWikiId ) ) {
1964  $this->assertWiki( $dbwOrWikiId );
1965  }
1966 
1967  if ( !$this->isItemLoaded( 'actor' ) ) {
1968  $this->load();
1969  }
1970 
1971  if ( !$this->mActorId && $dbwOrWikiId instanceof IDatabase ) {
1972  MediaWikiServices::getInstance()
1973  ->getActorStoreFactory()
1974  ->getActorNormalization( $dbwOrWikiId->getDomainID() )
1975  ->acquireActorId( $this, $dbwOrWikiId );
1976  // acquireActorId will call setActorId on $this
1977  Assert::postcondition(
1978  $this->mActorId !== null,
1979  "Failed to acquire actor ID for user id {$this->mId} name {$this->mName}"
1980  );
1981  }
1982 
1983  return (int)$this->mActorId;
1984  }
1985 
1995  public function setActorId( int $actorId ) {
1996  $this->mActorId = $actorId;
1997  $this->setItemLoaded( 'actor' );
1998  }
1999 
2004  public function getTitleKey() {
2005  return str_replace( ' ', '_', $this->getName() );
2006  }
2007 
2014  private function newTouchedTimestamp() {
2015  $time = (int)ConvertibleTimestamp::now( TS_UNIX );
2016  if ( $this->mTouched ) {
2017  $time = max( $time, (int)ConvertibleTimestamp::convert( TS_UNIX, $this->mTouched ) + 1 );
2018  }
2019 
2020  return ConvertibleTimestamp::convert( TS_MW, $time );
2021  }
2022 
2033  public function clearSharedCache( $mode = 'refresh' ) {
2034  if ( !$this->getId() ) {
2035  return;
2036  }
2037 
2038  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2039  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2040  $key = $this->getCacheKey( $cache );
2041 
2042  if ( $mode === 'refresh' ) {
2043  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2044  } else {
2045  $lb->getConnectionRef( DB_PRIMARY )->onTransactionPreCommitOrIdle(
2046  static function () use ( $cache, $key ) {
2047  $cache->delete( $key );
2048  },
2049  __METHOD__
2050  );
2051  }
2052  }
2053 
2059  public function invalidateCache() {
2060  $this->touch();
2061  $this->clearSharedCache( 'changed' );
2062  }
2063 
2076  public function touch() {
2077  $id = $this->getId();
2078  if ( $id ) {
2079  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2080  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2081  $cache->touchCheckKey( $key );
2082  $this->mQuickTouched = null;
2083  }
2084  }
2085 
2091  public function validateCache( $timestamp ) {
2092  return ( $timestamp >= $this->getTouched() );
2093  }
2094 
2103  public function getTouched() {
2104  $this->load();
2105 
2106  if ( $this->mId ) {
2107  if ( $this->mQuickTouched === null ) {
2108  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2109  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2110 
2111  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2112  }
2113 
2114  return max( $this->mTouched, $this->mQuickTouched );
2115  }
2116 
2117  return $this->mTouched;
2118  }
2119 
2125  public function getDBTouched() {
2126  $this->load();
2127 
2128  return $this->mTouched;
2129  }
2130 
2143  public function changeAuthenticationData( array $data ) {
2144  $manager = MediaWikiServices::getInstance()->getAuthManager();
2145  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2146  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2147 
2148  $status = Status::newGood( 'ignored' );
2149  foreach ( $reqs as $req ) {
2150  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2151  }
2152  if ( $status->getValue() === 'ignored' ) {
2153  $status->warning( 'authenticationdatachange-ignored' );
2154  }
2155 
2156  if ( $status->isGood() ) {
2157  foreach ( $reqs as $req ) {
2158  $manager->changeAuthenticationData( $req );
2159  }
2160  }
2161  return $status;
2162  }
2163 
2170  public function getToken( $forceCreation = true ) {
2171  $authenticationTokenVersion = MediaWikiServices::getInstance()
2172  ->getMainConfig()->get( 'AuthenticationTokenVersion' );
2173 
2174  $this->load();
2175  if ( !$this->mToken && $forceCreation ) {
2176  $this->setToken();
2177  }
2178 
2179  if ( !$this->mToken ) {
2180  // The user doesn't have a token, return null to indicate that.
2181  return null;
2182  }
2183 
2184  if ( $this->mToken === self::INVALID_TOKEN ) {
2185  // We return a random value here so existing token checks are very
2186  // likely to fail.
2187  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2188  }
2189 
2190  if ( $authenticationTokenVersion === null ) {
2191  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2192  return $this->mToken;
2193  }
2194 
2195  // $wgAuthenticationTokenVersion in use, so hmac it.
2196  $ret = MWCryptHash::hmac( $authenticationTokenVersion, $this->mToken, false );
2197 
2198  // The raw hash can be overly long. Shorten it up.
2199  $len = max( 32, self::TOKEN_LENGTH );
2200  if ( strlen( $ret ) < $len ) {
2201  // Should never happen, even md5 is 128 bits
2202  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2203  }
2204 
2205  return substr( $ret, -$len );
2206  }
2207 
2214  public function setToken( $token = false ) {
2215  $this->load();
2216  if ( $this->mToken === self::INVALID_TOKEN ) {
2217  LoggerFactory::getInstance( 'session' )
2218  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2219  } elseif ( !$token ) {
2220  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2221  } else {
2222  $this->mToken = $token;
2223  }
2224  }
2225 
2230  public function getEmail(): string {
2231  $this->load();
2232  $email = $this->mEmail;
2233  $this->getHookRunner()->onUserGetEmail( $this, $email );
2234  // In case a hook handler returns e.g. null
2235  $this->mEmail = is_string( $email ) ? $email : '';
2236  return $this->mEmail;
2237  }
2238 
2244  $this->load();
2245  $this->getHookRunner()->onUserGetEmailAuthenticationTimestamp(
2246  $this, $this->mEmailAuthenticated );
2248  }
2249 
2254  public function setEmail( string $str ) {
2255  $this->load();
2256  if ( $str == $this->getEmail() ) {
2257  return;
2258  }
2259  $this->invalidateEmail();
2260  $this->mEmail = $str;
2261  $this->getHookRunner()->onUserSetEmail( $this, $this->mEmail );
2262  }
2263 
2271  public function setEmailWithConfirmation( string $str ) {
2272  $enableEmail = MediaWikiServices::getInstance()->getMainConfig()->get( 'EnableEmail' );
2273  $emailAuthentication = MediaWikiServices::getInstance()->getMainConfig()->get( 'EmailAuthentication' );
2274  if ( !$enableEmail ) {
2275  return Status::newFatal( 'emaildisabled' );
2276  }
2277 
2278  $oldaddr = $this->getEmail();
2279  if ( $str === $oldaddr ) {
2280  return Status::newGood( true );
2281  }
2282 
2283  $type = $oldaddr != '' ? 'changed' : 'set';
2284  $notificationResult = null;
2285 
2286  if ( $emailAuthentication && $type === 'changed' ) {
2287  // Send the user an email notifying the user of the change in registered
2288  // email address on their previous email address
2289  $change = $str != '' ? 'changed' : 'removed';
2290  $notificationResult = $this->sendMail(
2291  wfMessage( 'notificationemail_subject_' . $change )->text(),
2292  wfMessage( 'notificationemail_body_' . $change,
2293  $this->getRequest()->getIP(),
2294  $this->getName(),
2295  $str )->text()
2296  );
2297  }
2298 
2299  $this->setEmail( $str );
2300 
2301  if ( $str !== '' && $emailAuthentication ) {
2302  // Send a confirmation request to the new address if needed
2303  $result = $this->sendConfirmationMail( $type );
2304 
2305  if ( $notificationResult !== null ) {
2306  $result->merge( $notificationResult );
2307  }
2308 
2309  if ( $result->isGood() ) {
2310  // Say to the caller that a confirmation and notification mail has been sent
2311  $result->value = 'eauth';
2312  }
2313  } else {
2314  $result = Status::newGood( true );
2315  }
2316 
2317  return $result;
2318  }
2319 
2324  public function getRealName(): string {
2325  if ( !$this->isItemLoaded( 'realname' ) ) {
2326  $this->load();
2327  }
2328 
2329  return $this->mRealName;
2330  }
2331 
2336  public function setRealName( string $str ) {
2337  $this->load();
2338  $this->mRealName = $str;
2339  }
2340 
2351  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2352  if ( $oname === null ) {
2353  return null; // b/c
2354  }
2355  return MediaWikiServices::getInstance()
2356  ->getUserOptionsLookup()
2357  ->getOption( $this, $oname, $defaultOverride, $ignoreHidden );
2358  }
2359 
2370  public function getTokenFromOption( $oname ) {
2371  $hiddenPrefs = MediaWikiServices::getInstance()->getMainConfig()->get( 'HiddenPrefs' );
2372 
2373  $id = $this->getId();
2374  if ( !$id || in_array( $oname, $hiddenPrefs ) ) {
2375  return false;
2376  }
2377 
2378  $token = $this->getOption( $oname );
2379  if ( !$token ) {
2380  // Default to a value based on the user token to avoid space
2381  // wasted on storing tokens for all users. When this option
2382  // is set manually by the user, only then is it stored.
2383  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2384  }
2385 
2386  return $token;
2387  }
2388 
2398  public function resetTokenFromOption( $oname ) {
2399  $hiddenPrefs = MediaWikiServices::getInstance()->getMainConfig()->get( 'HiddenPrefs' );
2400  if ( in_array( $oname, $hiddenPrefs ) ) {
2401  return false;
2402  }
2403 
2404  $token = MWCryptRand::generateHex( 40 );
2405  MediaWikiServices::getInstance()
2406  ->getUserOptionsManager()
2407  ->setOption( $this, $oname, $token );
2408  return $token;
2409  }
2410 
2415  public function getDatePreference() {
2416  // Important migration for old data rows
2417  if ( $this->mDatePreference === null ) {
2418  global $wgLang;
2419  $value = $this->getOption( 'date' );
2420  $map = $wgLang->getDatePreferenceMigrationMap();
2421  if ( isset( $map[$value] ) ) {
2422  $value = $map[$value];
2423  }
2424  $this->mDatePreference = $value;
2425  }
2426  return $this->mDatePreference;
2427  }
2428 
2435  public function requiresHTTPS() {
2436  $forceHTTPS = MediaWikiServices::getInstance()->getMainConfig()->get( 'ForceHTTPS' );
2437  $secureLogin = MediaWikiServices::getInstance()->getMainConfig()->get( 'SecureLogin' );
2438  if ( $forceHTTPS ) {
2439  return true;
2440  }
2441  if ( !$secureLogin ) {
2442  return false;
2443  }
2444  return MediaWikiServices::getInstance()
2445  ->getUserOptionsLookup()
2446  ->getBoolOption( $this, 'prefershttps' );
2447  }
2448 
2457  public function getGroups() {
2458  return MediaWikiServices::getInstance()
2459  ->getUserGroupManager()
2460  ->getUserGroups( $this, $this->queryFlagsUsed );
2461  }
2462 
2472  public function getGroupMemberships() {
2473  return MediaWikiServices::getInstance()
2474  ->getUserGroupManager()
2475  ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
2476  }
2477 
2482  public function getEditCount() {
2483  return MediaWikiServices::getInstance()
2484  ->getUserEditTracker()
2485  ->getUserEditCount( $this );
2486  }
2487 
2501  public function addGroup( $group, $expiry = null ) {
2502  return MediaWikiServices::getInstance()
2503  ->getUserGroupManager()
2504  ->addUserToGroup( $this, $group, $expiry, true );
2505  }
2506 
2516  public function removeGroup( $group ) {
2517  return MediaWikiServices::getInstance()
2518  ->getUserGroupManager()
2519  ->removeUserFromGroup( $this, $group );
2520  }
2521 
2530  public function isRegistered(): bool {
2531  return $this->getId() != 0;
2532  }
2533 
2538  public function isAnon() {
2539  return !$this->isRegistered();
2540  }
2541 
2546  public function isBot() {
2547  $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
2548  if ( in_array( 'bot', $userGroupManager->getUserGroups( $this ) ) && $this->isAllowed( 'bot' ) ) {
2549  return true;
2550  }
2551 
2552  $isBot = false;
2553  $this->getHookRunner()->onUserIsBot( $this, $isBot );
2554 
2555  return $isBot;
2556  }
2557 
2567  public function isSystemUser() {
2568  $this->load();
2569  if ( $this->getEmail() || $this->mToken !== self::INVALID_TOKEN ||
2570  MediaWikiServices::getInstance()->getAuthManager()->userCanAuthenticate( $this->mName )
2571  ) {
2572  return false;
2573  }
2574  return true;
2575  }
2576 
2577  public function isAllowedAny( ...$permissions ): bool {
2578  return $this->getThisAsAuthority()->isAllowedAny( ...$permissions );
2579  }
2580 
2581  public function isAllowedAll( ...$permissions ): bool {
2582  return $this->getThisAsAuthority()->isAllowedAll( ...$permissions );
2583  }
2584 
2585  public function isAllowed( string $permission ): bool {
2586  return $this->getThisAsAuthority()->isAllowed( $permission );
2587  }
2588 
2593  public function useRCPatrol() {
2594  $useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseRCPatrol' );
2595  return $useRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
2596  }
2597 
2602  public function useNPPatrol() {
2603  $useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseRCPatrol' );
2604  $useNPPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseNPPatrol' );
2605  return (
2606  ( $useRCPatrol || $useNPPatrol )
2607  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
2608  );
2609  }
2610 
2615  public function useFilePatrol() {
2616  $useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseRCPatrol' );
2617  $useFilePatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseFilePatrol' );
2618  return (
2619  ( $useRCPatrol || $useFilePatrol )
2620  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
2621  );
2622  }
2623 
2629  public function getRequest() {
2630  if ( $this->mRequest ) {
2631  return $this->mRequest;
2632  }
2633  return RequestContext::getMain()->getRequest();
2634  }
2635 
2641  public function getExperienceLevel() {
2642  $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
2643  $learnerEdits = $mainConfig->get( 'LearnerEdits' );
2644  $experiencedUserEdits = $mainConfig->get( 'ExperiencedUserEdits' );
2645  $learnerMemberSince = $mainConfig->get( 'LearnerMemberSince' );
2646  $experiencedUserMemberSince = $mainConfig->get( 'ExperiencedUserMemberSince' );
2647  if ( $this->isAnon() ) {
2648  return false;
2649  }
2650 
2651  $editCount = $this->getEditCount();
2652  $registration = $this->getRegistration();
2653  $now = time();
2654  $learnerRegistration = wfTimestamp( TS_MW, $now - $learnerMemberSince * 86400 );
2655  $experiencedRegistration = wfTimestamp( TS_MW, $now - $experiencedUserMemberSince * 86400 );
2656  if ( $registration === null ) {
2657  // for some very old accounts, this information is missing in the database
2658  // treat them as old enough to be 'experienced'
2659  $registration = $experiencedRegistration;
2660  }
2661 
2662  if ( $editCount < $learnerEdits ||
2663  $registration > $learnerRegistration ) {
2664  return 'newcomer';
2665  }
2666 
2667  if ( $editCount > $experiencedUserEdits &&
2668  $registration <= $experiencedRegistration
2669  ) {
2670  return 'experienced';
2671  }
2672 
2673  return 'learner';
2674  }
2675 
2684  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
2685  $this->load();
2686  if ( $this->mId == 0 ) {
2687  return;
2688  }
2689 
2690  $session = $this->getRequest()->getSession();
2691  if ( $request && $session->getRequest() !== $request ) {
2692  $session = $session->sessionWithRequest( $request );
2693  }
2694  $delay = $session->delaySave();
2695 
2696  if ( !$session->getUser()->equals( $this ) ) {
2697  if ( !$session->canSetUser() ) {
2698  LoggerFactory::getInstance( 'session' )
2699  ->warning( __METHOD__ .
2700  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
2701  );
2702  return;
2703  }
2704  $session->setUser( $this );
2705  }
2706 
2707  $session->setRememberUser( $rememberMe );
2708  if ( $secure !== null ) {
2709  $session->setForceHTTPS( $secure );
2710  }
2711 
2712  $session->persist();
2713 
2714  ScopedCallback::consume( $delay );
2715  }
2716 
2720  public function logout() {
2721  // Avoid PHP 7.1 warning of passing $this by reference
2722  $user = $this;
2723  if ( $this->getHookRunner()->onUserLogout( $user ) ) {
2724  $this->doLogout();
2725  }
2726  }
2727 
2732  public function doLogout() {
2733  $session = $this->getRequest()->getSession();
2734  if ( !$session->canSetUser() ) {
2735  LoggerFactory::getInstance( 'session' )
2736  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
2737  $error = 'immutable';
2738  } elseif ( !$session->getUser()->equals( $this ) ) {
2739  LoggerFactory::getInstance( 'session' )
2740  ->warning( __METHOD__ .
2741  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
2742  );
2743  // But we still may as well make this user object anon
2744  $this->clearInstanceCache( 'defaults' );
2745  $error = 'wronguser';
2746  } else {
2747  $this->clearInstanceCache( 'defaults' );
2748  $delay = $session->delaySave();
2749  $session->unpersist(); // Clear cookies (T127436)
2750  $session->setLoggedOutTimestamp( time() );
2751  $session->setUser( new User );
2752  $session->set( 'wsUserID', 0 ); // Other code expects this
2753  $session->resetAllTokens();
2754  ScopedCallback::consume( $delay );
2755  $error = false;
2756  }
2757  LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
2758  'event' => 'logout',
2759  'successful' => $error === false,
2760  'status' => $error ?: 'success',
2761  ] );
2762  }
2763 
2768  public function saveSettings() {
2769  if ( wfReadOnly() ) {
2770  // @TODO: caller should deal with this instead!
2771  // This should really just be an exception.
2773  null,
2774  "Could not update user with ID '{$this->mId}'; DB is read-only."
2775  ) );
2776  return;
2777  }
2778 
2779  $this->load();
2780  if ( $this->mId == 0 ) {
2781  return; // anon
2782  }
2783 
2784  // Get a new user_touched that is higher than the old one.
2785  // This will be used for a CAS check as a last-resort safety
2786  // check against race conditions and replica DB lag.
2787  $newTouched = $this->newTouchedTimestamp();
2788 
2789  $dbw = wfGetDB( DB_PRIMARY );
2790  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
2791  $dbw->update( 'user',
2792  [ /* SET */
2793  'user_name' => $this->mName,
2794  'user_real_name' => $this->mRealName,
2795  'user_email' => $this->mEmail,
2796  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
2797  'user_touched' => $dbw->timestamp( $newTouched ),
2798  'user_token' => strval( $this->mToken ),
2799  'user_email_token' => $this->mEmailToken,
2800  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
2801  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
2802  'user_id' => $this->mId,
2803  ] ), $fname
2804  );
2805 
2806  if ( !$dbw->affectedRows() ) {
2807  // Maybe the problem was a missed cache update; clear it to be safe
2808  $this->clearSharedCache( 'refresh' );
2809  // User was changed in the meantime or loaded with stale data
2810  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'primary' : 'replica';
2811  LoggerFactory::getInstance( 'preferences' )->warning(
2812  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
2813  [ 'user_id' => $this->mId, 'db_flag' => $from ]
2814  );
2815  throw new MWException( "CAS update failed on user_touched. " .
2816  "The version of the user to be saved is older than the current version."
2817  );
2818  }
2819 
2820  $dbw->update(
2821  'actor',
2822  [ 'actor_name' => $this->mName ],
2823  [ 'actor_user' => $this->mId ],
2824  $fname
2825  );
2826  MediaWikiServices::getInstance()->getActorStore()->deleteUserIdentityFromCache( $this );
2827  } );
2828 
2829  $this->mTouched = $newTouched;
2830  MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptionsInternal( $this, $dbw );
2831 
2832  $this->getHookRunner()->onUserSaveSettings( $this );
2833  $this->clearSharedCache( 'changed' );
2834  $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2835  $hcu->purgeTitleUrls( $this->getUserPage(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2836  }
2837 
2844  public function idForName( $flags = self::READ_NORMAL ) {
2845  $s = trim( $this->getName() );
2846  if ( $s === '' ) {
2847  return 0;
2848  }
2849 
2850  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
2851  $db = wfGetDB( $index );
2852 
2853  $id = $db->selectField( 'user',
2854  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
2855 
2856  return (int)$id;
2857  }
2858 
2873  public static function createNew( $name, $params = [] ) {
2874  return self::insertNewUser( static function ( UserIdentity $actor, IDatabase $dbw ) {
2875  return MediaWikiServices::getInstance()->getActorStore()->createNewActor( $actor, $dbw );
2876  }, $name, $params );
2877  }
2878 
2886  private static function insertNewUser( callable $insertActor, $name, $params = [] ) {
2887  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
2888  if ( isset( $params[$field] ) ) {
2889  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
2890  unset( $params[$field] );
2891  }
2892  }
2893 
2894  $user = new User;
2895  $user->load();
2896  $user->setToken(); // init token
2897  if ( isset( $params['options'] ) ) {
2898  MediaWikiServices::getInstance()
2899  ->getUserOptionsManager()
2900  ->loadUserOptions( $user, $user->queryFlagsUsed, $params['options'] );
2901  unset( $params['options'] );
2902  }
2903  $dbw = wfGetDB( DB_PRIMARY );
2904 
2905  $noPass = PasswordFactory::newInvalidPassword()->toString();
2906 
2907  $fields = [
2908  'user_name' => $name,
2909  'user_password' => $noPass,
2910  'user_newpassword' => $noPass,
2911  'user_email' => $user->mEmail,
2912  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
2913  'user_real_name' => $user->mRealName,
2914  'user_token' => strval( $user->mToken ),
2915  'user_registration' => $dbw->timestamp( $user->mRegistration ),
2916  'user_editcount' => 0,
2917  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
2918  ];
2919  foreach ( $params as $name => $value ) {
2920  $fields["user_$name"] = $value;
2921  }
2922 
2923  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields, $insertActor ) {
2924  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
2925  if ( $dbw->affectedRows() ) {
2926  $newUser = self::newFromId( $dbw->insertId() );
2927  $newUser->mName = $fields['user_name'];
2928  // Don't pass $this, since calling ::getId, ::getName might force ::load
2929  // and this user might not be ready for the yet.
2930  $newUser->mActorId = $insertActor( new UserIdentityValue( $newUser->mId, $newUser->mName ), $dbw );
2931  // Load the user from primary DB to avoid replica lag
2932  $newUser->load( self::READ_LATEST );
2933  } else {
2934  $newUser = null;
2935  }
2936  return $newUser;
2937  } );
2938  }
2939 
2966  public function addToDatabase() {
2967  $this->load();
2968  if ( !$this->mToken ) {
2969  $this->setToken(); // init token
2970  }
2971 
2972  if ( !is_string( $this->mName ) ) {
2973  throw new RuntimeException( "User name field is not set." );
2974  }
2975 
2976  $this->mTouched = $this->newTouchedTimestamp();
2977 
2978  $dbw = wfGetDB( DB_PRIMARY );
2979  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
2980  $noPass = PasswordFactory::newInvalidPassword()->toString();
2981  $dbw->insert( 'user',
2982  [
2983  'user_name' => $this->mName,
2984  'user_password' => $noPass,
2985  'user_newpassword' => $noPass,
2986  'user_email' => $this->mEmail,
2987  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
2988  'user_real_name' => $this->mRealName,
2989  'user_token' => strval( $this->mToken ),
2990  'user_registration' => $dbw->timestamp( $this->mRegistration ),
2991  'user_editcount' => 0,
2992  'user_touched' => $dbw->timestamp( $this->mTouched ),
2993  ], $fname,
2994  [ 'IGNORE' ]
2995  );
2996  if ( !$dbw->affectedRows() ) {
2997  // Use locking reads to bypass any REPEATABLE-READ snapshot.
2998  $this->mId = $dbw->selectField(
2999  'user',
3000  'user_id',
3001  [ 'user_name' => $this->mName ],
3002  $fname,
3003  [ 'LOCK IN SHARE MODE' ]
3004  );
3005  $loaded = false;
3006  if ( $this->mId && $this->loadFromDatabase( IDBAccessObject::READ_LOCKING ) ) {
3007  $loaded = true;
3008  }
3009  if ( !$loaded ) {
3010  throw new MWException( $fname . ": hit a key conflict attempting " .
3011  "to insert user '{$this->mName}' row, but it was not present in select!" );
3012  }
3013  return Status::newFatal( 'userexists' );
3014  }
3015  $this->mId = $dbw->insertId();
3016 
3017  // Don't pass $this, since calling ::getId, ::getName might force ::load
3018  // and this user might not be ready for the yet.
3019  $this->mActorId = MediaWikiServices::getInstance()
3020  ->getActorNormalization()
3021  ->acquireActorId( new UserIdentityValue( $this->mId, $this->mName ), $dbw );
3022  return Status::newGood();
3023  } );
3024  if ( !$status->isGood() ) {
3025  return $status;
3026  }
3027 
3028  // Clear instance cache other than user table data and actor, which is already accurate
3029  $this->clearInstanceCache();
3030 
3031  MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3032  return Status::newGood();
3033  }
3034 
3040  public function spreadAnyEditBlock() {
3041  if ( $this->isRegistered() && $this->getBlock() ) {
3042  return $this->spreadBlock();
3043  }
3044 
3045  return false;
3046  }
3047 
3053  protected function spreadBlock() {
3054  wfDebug( __METHOD__ . "()" );
3055  $this->load();
3056  if ( $this->mId == 0 ) {
3057  return false;
3058  }
3059 
3060  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
3061  if ( !$userblock ) {
3062  return false;
3063  }
3064 
3065  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
3066  }
3067 
3073  public function isBlockedFromCreateAccount() {
3074  $this->getBlockedStatus();
3075  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
3076  return $this->mBlock;
3077  }
3078 
3079  # T15611: if the IP address the user is trying to create an account from is
3080  # blocked with createaccount disabled, prevent new account creation there even
3081  # when the user is logged in
3082  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
3083  $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
3084  null, $this->getRequest()->getIP()
3085  );
3086  }
3087  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
3088  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
3089  ? $this->mBlockedFromCreateAccount
3090  : false;
3091  }
3092 
3097  public function isBlockedFromEmailuser() {
3098  $this->getBlockedStatus();
3099  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
3100  }
3101 
3108  public function isBlockedFromUpload() {
3109  $this->getBlockedStatus();
3110  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
3111  }
3112 
3117  public function isAllowedToCreateAccount() {
3118  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
3119  }
3120 
3126  public function getUserPage() {
3127  return Title::makeTitle( NS_USER, $this->getName() );
3128  }
3129 
3135  public function getTalkPage() {
3136  $title = $this->getUserPage();
3137  return $title->getTalkPage();
3138  }
3139 
3145  public function isNewbie() {
3146  return !$this->isAllowed( 'autoconfirmed' );
3147  }
3148 
3161  public function getEditTokenObject( $salt = '', $request = null ) {
3162  if ( $this->isAnon() ) {
3163  return new LoggedOutEditToken();
3164  }
3165 
3166  if ( !$request ) {
3167  $request = $this->getRequest();
3168  }
3169  return $request->getSession()->getToken( $salt );
3170  }
3171 
3186  public function getEditToken( $salt = '', $request = null ) {
3187  return $this->getEditTokenObject( $salt, $request )->toString();
3188  }
3189 
3203  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
3204  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
3205  }
3206 
3214  public function sendConfirmationMail( $type = 'created' ) {
3215  global $wgLang;
3216  $expiration = null; // gets passed-by-ref and defined in next line.
3217  $token = $this->confirmationToken( $expiration );
3218  $url = $this->confirmationTokenUrl( $token );
3219  $invalidateURL = $this->invalidationTokenUrl( $token );
3220  $this->saveSettings();
3221 
3222  if ( $type == 'created' || $type === false ) {
3223  $message = 'confirmemail_body';
3224  $type = 'created';
3225  } elseif ( $type === true ) {
3226  $message = 'confirmemail_body_changed';
3227  $type = 'changed';
3228  } else {
3229  // Messages: confirmemail_body_changed, confirmemail_body_set
3230  $message = 'confirmemail_body_' . $type;
3231  }
3232 
3233  $mail = [
3234  'subject' => wfMessage( 'confirmemail_subject' )->text(),
3235  'body' => wfMessage( $message,
3236  $this->getRequest()->getIP(),
3237  $this->getName(),
3238  $url,
3239  $wgLang->userTimeAndDate( $expiration, $this ),
3240  $invalidateURL,
3241  $wgLang->userDate( $expiration, $this ),
3242  $wgLang->userTime( $expiration, $this ) )->text(),
3243  'from' => null,
3244  'replyTo' => null,
3245  ];
3246  $info = [
3247  'type' => $type,
3248  'ip' => $this->getRequest()->getIP(),
3249  'confirmURL' => $url,
3250  'invalidateURL' => $invalidateURL,
3251  'expiration' => $expiration
3252  ];
3253 
3254  $this->getHookRunner()->onUserSendConfirmationMail( $this, $mail, $info );
3255  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
3256  }
3257 
3269  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
3270  $passwordSender = MediaWikiServices::getInstance()->getMainConfig()->get( 'PasswordSender' );
3271 
3272  if ( $from instanceof User ) {
3273  $sender = MailAddress::newFromUser( $from );
3274  } else {
3275  $sender = new MailAddress( $passwordSender,
3276  wfMessage( 'emailsender' )->inContentLanguage()->text() );
3277  }
3278  $to = MailAddress::newFromUser( $this );
3279 
3280  return UserMailer::send( $to, $sender, $subject, $body, [
3281  'replyTo' => $replyto,
3282  ] );
3283  }
3284 
3295  protected function confirmationToken( &$expiration ) {
3296  $userEmailConfirmationTokenExpiry = MediaWikiServices::getInstance()
3297  ->getMainConfig()->get( 'UserEmailConfirmationTokenExpiry' );
3298  $now = time();
3299  $expires = $now + $userEmailConfirmationTokenExpiry;
3300  $expiration = wfTimestamp( TS_MW, $expires );
3301  $this->load();
3302  $token = MWCryptRand::generateHex( 32 );
3303  $hash = md5( $token );
3304  $this->mEmailToken = $hash;
3305  $this->mEmailTokenExpires = $expiration;
3306  return $token;
3307  }
3308 
3314  protected function confirmationTokenUrl( $token ) {
3315  return $this->getTokenUrl( 'ConfirmEmail', $token );
3316  }
3317 
3323  protected function invalidationTokenUrl( $token ) {
3324  return $this->getTokenUrl( 'InvalidateEmail', $token );
3325  }
3326 
3341  protected function getTokenUrl( $page, $token ) {
3342  // Hack to bypass localization of 'Special:'
3343  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
3344  return $title->getCanonicalURL();
3345  }
3346 
3354  public function confirmEmail() {
3355  // Check if it's already confirmed, so we don't touch the database
3356  // and fire the ConfirmEmailComplete hook on redundant confirmations.
3357  if ( !$this->isEmailConfirmed() ) {
3359  $this->getHookRunner()->onConfirmEmailComplete( $this );
3360  }
3361  return true;
3362  }
3363 
3371  public function invalidateEmail() {
3372  $this->load();
3373  $this->mEmailToken = null;
3374  $this->mEmailTokenExpires = null;
3375  $this->setEmailAuthenticationTimestamp( null );
3376  $this->mEmail = '';
3377  $this->getHookRunner()->onInvalidateEmailComplete( $this );
3378  return true;
3379  }
3380 
3385  public function setEmailAuthenticationTimestamp( $timestamp ) {
3386  $this->load();
3387  $this->mEmailAuthenticated = $timestamp;
3388  $this->getHookRunner()->onUserSetEmailAuthenticationTimestamp(
3389  $this, $this->mEmailAuthenticated );
3390  }
3391 
3397  public function canSendEmail() {
3398  $enableEmail = MediaWikiServices::getInstance()->getMainConfig()->get( 'EnableEmail' );
3399  $enableUserEmail = MediaWikiServices::getInstance()->getMainConfig()->get( 'EnableUserEmail' );
3400  if ( !$enableEmail || !$enableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
3401  return false;
3402  }
3403  $hookErr = $this->isEmailConfirmed();
3404  $this->getHookRunner()->onUserCanSendEmail( $this, $hookErr );
3405  return $hookErr;
3406  }
3407 
3413  public function canReceiveEmail() {
3414  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
3415  }
3416 
3427  public function isEmailConfirmed(): bool {
3428  $emailAuthentication = MediaWikiServices::getInstance()->getMainConfig()->get( 'EmailAuthentication' );
3429  $this->load();
3430  // Avoid PHP 7.1 warning of passing $this by reference
3431  $user = $this;
3432  $confirmed = true;
3433  if ( $this->getHookRunner()->onEmailConfirmed( $user, $confirmed ) ) {
3434  if ( $this->isAnon() ) {
3435  return false;
3436  }
3437  if ( !Sanitizer::validateEmail( $this->getEmail() ) ) {
3438  return false;
3439  }
3440  if ( $emailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
3441  return false;
3442  }
3443  return true;
3444  }
3445 
3446  return $confirmed;
3447  }
3448 
3453  public function isEmailConfirmationPending() {
3454  $emailAuthentication = MediaWikiServices::getInstance()->getMainConfig()->get( 'EmailAuthentication' );
3455  return $emailAuthentication &&
3456  !$this->isEmailConfirmed() &&
3457  $this->mEmailToken &&
3458  $this->mEmailTokenExpires > wfTimestamp();
3459  }
3460 
3468  public function getRegistration() {
3469  if ( $this->isAnon() ) {
3470  return false;
3471  }
3472  $this->load();
3473  return $this->mRegistration;
3474  }
3475 
3485  public static function getGroupPermissions( $groups ) {
3486  return MediaWikiServices::getInstance()->getGroupPermissionsLookup()->getGroupPermissions( $groups );
3487  }
3488 
3498  public static function getGroupsWithPermission( $role ) {
3499  return MediaWikiServices::getInstance()->getGroupPermissionsLookup()->getGroupsWithPermission( $role );
3500  }
3501 
3517  public static function groupHasPermission( $group, $role ) {
3518  return MediaWikiServices::getInstance()->getGroupPermissionsLookup()
3519  ->groupHasPermission( $group, $role );
3520  }
3521 
3529  public static function getAllGroups() {
3530  return MediaWikiServices::getInstance()
3531  ->getUserGroupManager()
3532  ->listAllGroups();
3533  }
3534 
3539  public static function getImplicitGroups() {
3540  return MediaWikiServices::getInstance()
3541  ->getUserGroupManager()
3542  ->listAllImplicitGroups();
3543  }
3544 
3549  public function incEditCount() {
3550  MediaWikiServices::getInstance()->getUserEditTracker()->incrementUserEditCount( $this );
3551  }
3552 
3560  public static function getRightDescription( $right ) {
3561  $key = "right-$right";
3562  $msg = wfMessage( $key );
3563  return $msg->isDisabled() ? $right : $msg->text();
3564  }
3565 
3575  public static function getQueryInfo() {
3576  $ret = [
3577  'tables' => [ 'user', 'user_actor' => 'actor' ],
3578  'fields' => [
3579  'user_id',
3580  'user_name',
3581  'user_real_name',
3582  'user_email',
3583  'user_touched',
3584  'user_token',
3585  'user_email_authenticated',
3586  'user_email_token',
3587  'user_email_token_expires',
3588  'user_registration',
3589  'user_editcount',
3590  'user_actor.actor_id',
3591  ],
3592  'joins' => [
3593  'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
3594  ],
3595  ];
3596 
3597  return $ret;
3598  }
3599 
3607  public static function newFatalPermissionDeniedStatus( $permission ) {
3608  global $wgLang;
3609 
3610  $groups = [];
3611  foreach ( MediaWikiServices::getInstance()
3612  ->getGroupPermissionsLookup()
3613  ->getGroupsWithPermission( $permission ) as $group ) {
3614  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
3615  }
3616 
3617  if ( $groups ) {
3618  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
3619  }
3620 
3621  return Status::newFatal( 'badaccess-group0' );
3622  }
3623 
3633  public function getInstanceForUpdate() {
3634  if ( !$this->getId() ) {
3635  return null; // anon
3636  }
3637 
3638  $user = self::newFromId( $this->getId() );
3639  if ( !$user->loadFromId( IDBAccessObject::READ_EXCLUSIVE ) ) {
3640  return null;
3641  }
3642 
3643  return $user;
3644  }
3645 
3653  public function equals( ?UserIdentity $user ): bool {
3654  if ( !$user ) {
3655  return false;
3656  }
3657  // XXX it's not clear whether central ID providers are supposed to obey this
3658  return $this->getName() === $user->getName();
3659  }
3660 
3666  public function getUser(): UserIdentity {
3667  return $this;
3668  }
3669 
3677  public function probablyCan( string $action, PageIdentity $target, PermissionStatus $status = null ): bool {
3678  return $this->getThisAsAuthority()->probablyCan( $action, $target, $status );
3679  }
3680 
3688  public function definitelyCan( string $action, PageIdentity $target, PermissionStatus $status = null ): bool {
3689  return $this->getThisAsAuthority()->definitelyCan( $action, $target, $status );
3690  }
3691 
3699  public function authorizeRead( string $action, PageIdentity $target, PermissionStatus $status = null
3700  ): bool {
3701  return $this->getThisAsAuthority()->authorizeRead( $action, $target, $status );
3702  }
3703 
3711  public function authorizeWrite( string $action, PageIdentity $target, PermissionStatus $status = null ): bool {
3712  return $this->getThisAsAuthority()->authorizeWrite( $action, $target, $status );
3713  }
3714 
3720  private function getThisAsAuthority(): Authority {
3721  if ( !$this->mThisAsAuthority ) {
3722  // TODO: For users that are not User::isGlobalSessionUser,
3723  // creating a UserAuthority here is incorrect, since it depends
3724  // on global WebRequest, but that is what we've used to do before Authority.
3725  // When PermissionManager is refactored into Authority, we need
3726  // to provide base implementation, based on just user groups/rights,
3727  // and use it here.
3728  $this->mThisAsAuthority = new UserAuthority(
3729  $this,
3730  MediaWikiServices::getInstance()->getPermissionManager()
3731  );
3732  }
3733  return $this->mThisAsAuthority;
3734  }
3735 
3740  private function isGlobalSessionUser(): bool {
3741  // The session user is set up towards the end of Setup.php. Until then,
3742  // assume it's a logged-out user.
3743  $sessionUser = RequestContext::getMain()->getUser();
3744  $globalUserName = $sessionUser->isSafeToLoad()
3745  ? $sessionUser->getName()
3746  : IPUtils::sanitizeIP( $sessionUser->getRequest()->getIP() );
3747 
3748  return $this->getName() === $globalUserName;
3749  }
3750 }
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=self::READ_NORMAL)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:722
MediaWiki\User\UserIdentityValue
Value object representing a user's identity.
Definition: UserIdentityValue.php:35
Page\PageIdentity
Interface for objects (potentially) representing an editable wiki page.
Definition: PageIdentity.php:64
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:452
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:364
MediaWiki\DAO\WikiAwareEntityTrait
trait WikiAwareEntityTrait
Definition: WikiAwareEntityTrait.php:32
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:52
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:636
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:3314
User\__set
__set( $name, $value)
Definition: User.php:302
MediaWiki\DAO\deprecateInvalidCrossWiki
deprecateInvalidCrossWiki( $wikiId, string $since)
Emits a deprecation warning $since version if $wikiId is not the same as this wiki.
Definition: WikiAwareEntityTrait.php:71
User\$mToken
string $mToken
Definition: User.php:162
User\$mBlockedby
string int $mBlockedby
Definition: User.php:203
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:273
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:985
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:2615
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:2538
User\$mBlockedFromCreateAccount
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:235
User\$mBlockreason
string $mBlockreason
TODO: This should be removed when User::BlockedFor and AbstractBlock::getReason are hard deprecated.
Definition: User.php:211
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:3341
User\isRegistered
isRegistered()
Get whether the user is registered.
Definition: User.php:2530
User\$mCacheVars
static string[] $mCacheVars
List of member variables which are saved to the shared cache (memcached).
Definition: User.php:122
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:2398
User\isBlockedFrom
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:1756
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1293
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:203
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:3607
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:3161
User\isBot
isBot()
Definition: User.php:2546
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:2014
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:2482
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:3053
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:92
User\incEditCount
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition: User.php:3549
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:735
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:118
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:3453
User\getBlockId
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:1792
true
return true
Definition: router.php:90
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:158
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:258
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1649
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2170
User\setEmail
setEmail(string $str)
Set the user's e-mail address.
Definition: User.php:2254
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:3040
MediaWiki\Permissions\UserAuthority
Represents the authority of a given User.
Definition: UserAuthority.php:45
User\$mHideName
bool $mHideName
Definition: User.php:222
Sanitizer\validateEmail
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1744
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1082
User\$mLocked
bool $mLocked
Definition: User.php:215
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:595
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1186
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1167
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:3385
User\$mEmail
string $mEmail
Definition: User.php:156
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:3126
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:2457
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:672
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1056
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:2602
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:2415
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:347
$success
$success
Definition: NoLocalSettings.php:42
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:3214
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:3517
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2243
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:1479
User\getActorId
getActorId( $dbwOrWikiId=self::LOCAL)
Get the user's actor ID.
Definition: User.php:1958
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:2593
User\invalidateEmail
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:3371
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:757
User\$mHash
string $mHash
Definition: User.php:205
User\getBlock
getBlock( $freshness=self::READ_NORMAL, $disableIpBlockExemptChecking=false)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1731
$wgLang
$wgLang
Definition: Setup.php:861
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
User\probablyCan
probablyCan(string $action, PageIdentity $target, PermissionStatus $status=null)
Definition: User.php:3677
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:2873
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:2629
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
User\getWikiId
getWikiId()
Returns self::LOCAL to indicate the user is associated with the local wiki.
Definition: User.php:268
User\INVALID_TOKEN
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:91
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the primary DB via a locking read.
Definition: User.php:3633
$dbr
$dbr
Definition: testCompression.php:54
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:803
Wikimedia\Rdbms\IDatabase\update
update( $table, $set, $conds, $fname=__METHOD__, $options=[])
Update all rows in a table that match a given condition.
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:2501
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:36
MWExceptionHandler\logException
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).
Definition: MWExceptionHandler.php:700
LoggedOutEditToken
Value object representing a logged-out user's edit token.
Definition: LoggedOutEditToken.php:37
User\matchEditToken
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:3203
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2230
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:51
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:3135
User\equals
equals(?UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:3653
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:2966
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:2059
TitleValue\castPageToLinkTarget
static castPageToLinkTarget(?PageReference $page)
Casts a PageReference to a LinkTarget.
Definition: TitleValue.php:124
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1028
MWException
MediaWiki exception.
Definition: MWException.php:29
User\invalidationTokenUrl
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:3323
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:1851
User\$mDatePreference
string $mDatePreference
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:196
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
User\authorizeWrite
authorizeWrite(string $action, PageIdentity $target, PermissionStatus $status=null)
Definition: User.php:3711
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:1011
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:3354
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1097
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:104
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:1781
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2186
$wgFullyInitialised
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:915
MailAddress\newFromUser
static newFromUser(UserEmailContact $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:72
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:3117
User\logout
logout()
Log this user out.
Definition: User.php:2720
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:3295
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:493
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1665
User\getImplicitGroups
static getImplicitGroups()
Definition: User.php:3539
User\setRealName
setRealName(string $str)
Set the user's real name.
Definition: User.php:2336
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:1866
User\definitelyCan
definitelyCan(string $action, PageIdentity $target, PermissionStatus $status=null)
Definition: User.php:3688
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:3097
User\setEmailWithConfirmation
setEmailWithConfirmation(string $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:2271
User\$mBlock
AbstractBlock null $mBlock
Definition: User.php:232
User\loadDefaults
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition: User.php:1049
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2091
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1452
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:2516
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:3413
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:3145
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2076
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:1366
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:102
User\TOKEN_LENGTH
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:86
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:648
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1678
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:160
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:701
User\getId
getId( $wikiId=self::LOCAL)
Get the user's ID.
Definition: User.php:1879
User\setName
setName( $str)
Set the user name.
Definition: User.php:1940
User\$mRealName
string $mRealName
Definition: User.php:153
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:52
User\getBlockedStatus
getBlockedStatus( $fromReplica=true, $disableIpBlockExemptChecking=false)
Get blocking information.
Definition: User.php:1400
UserPasswordPolicy
Check if a user's password complies with any password policies that apply to that user,...
Definition: UserPasswordPolicy.php:31
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:894
User\__get
& __get( $name)
Definition: User.php:279
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:2768
User\isAllowedAll
isAllowedAll(... $permissions)
Checks whether this authority has any of the given permissions in general.
Definition: User.php:2581
User\authorizeRead
authorizeRead(string $action, PageIdentity $target, PermissionStatus $status=null)
Definition: User.php:3699
MediaWiki\Block\Block
Represents a block that may prevent users from performing specific operations.
Definition: Block.php:40
MediaWiki\Permissions\Authority
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
MediaWiki\DAO\WikiAwareEntity\assertWiki
assertWiki( $wikiId)
Throws if $wikiId is different from the return value of getWikiId().
$s
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
Definition: mergeMessageFileList.php:206
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:910
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\isAllowedAny
isAllowedAny(... $permissions)
Checks whether this authority has any of the given permissions in general.
Definition: User.php:2577
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1108
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:1806
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2351
User\idForName
idForName( $flags=self::READ_NORMAL)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:2844
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
User\getTouched
getTouched()
Get the user touched timestamp.
Definition: User.php:2103
User\$mId
int $mId
Cache variables.
Definition: User.php:143
User\isSystemUser
isSystemUser()
Get whether the user is a system user.
Definition: User.php:2567
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:1820
User\__toString
__toString()
Definition: User.php:275
MediaWiki\Block\SystemBlock
System blocks are temporary blocks that are created on enforcement (e.g.
Definition: SystemBlock.php:35
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:83
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1328
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:131
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:2324
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:2684
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:3485
User\VERSION
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:97
MediaWiki\Mail\UserEmailContact
Definition: UserEmailContact.php:11
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:3529
NS_USER
const NS_USER
Definition: Defines.php:66
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:36
User\isBlockedFromUpload
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition: User.php:3108
User\setActorId
setActorId(int $actorId)
Sets the actor id.
Definition: User.php:1995
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:3575
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:1769
User\$mLoadedItems
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:177
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2125
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:102
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:43
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:35
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:1903
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:2641
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:3468
User\isAllowed
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
Definition: User.php:2585
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:242
MediaWiki\Block\AbstractBlock\appliesToRight
appliesToRight( $right)
Determine whether the block prevents a given right.
Definition: AbstractBlock.php:278
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:3560
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2143
$cache
$cache
Definition: mcc.php:33
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:34
User\$mActorId
int null $mActorId
Switched from protected to public for use in UserFactory.
Definition: User.php:151
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2004
User\purge
static purge( $dbDomain, $userId)
Definition: User.php:482
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2214
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:931
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:168
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:951
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:3427
User\$mGlobalBlock
AbstractBlock $mGlobalBlock
Definition: User.php:213
UserCache\singleton
static singleton()
Definition: UserCache.php:48
User\clearSharedCache
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2033
User\isBlocked
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:1715
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:653
User\$mThisAsAuthority
Authority null $mThisAsAuthority
lazy-initialized Authority of this user
Definition: User.php:241
$keys
$keys
Definition: testCompression.php:72
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:516
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:3269
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:2370
User\$mEmailToken
string $mEmailToken
Definition: User.php:166
Wikimedia\Rdbms\IDatabase\timestampOrNull
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user data from the database.
Definition: User.php:1134
User\$mName
string $mName
Definition: User.php:145
User\$mRegistration
string $mRegistration
Definition: User.php:170
MediaWiki\Block\AbstractBlock
Definition: AbstractBlock.php:38
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:107
User\$mRequest
WebRequest $mRequest
Definition: User.php:225
CentralIdLookup\AUDIENCE_RAW
const AUDIENCE_RAW
Definition: CentralIdLookup.php:38
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
User\makeUpdateConditions
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1309
User\__sleep
__sleep()
Definition: User.php:324
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:3073
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:3186
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:2435
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1085
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:67
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:238
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:920
ExternalUserNames\isExternal
static isExternal( $username)
Tells whether the username is external or not.
Definition: ExternalUserNames.php:149
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:1912
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:2732
MediaWiki\User\UserFactory
Creates User objects.
Definition: UserFactory.php:41
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:2472
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:3397
User\insertNewUser
static insertNewUser(callable $insertActor, $name, $params=[])
See ::createNew.
Definition: User.php:2886
User\isGlobalSessionUser
isGlobalSessionUser()
Check whether this is the global session user.
Definition: User.php:3740
User\getUser
getUser()
Definition: User.php:3666
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:504
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:38
User\MAINTENANCE_SCRIPT_USER
const MAINTENANCE_SCRIPT_USER
Username used for various maintenance scripts.
Definition: User.php:113
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:164
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:3498
User\$mFrom
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:190
User\getThisAsAuthority
getThisAsAuthority()
Returns the Authority of this User if it's the main request context user.
Definition: User.php:3720
$type
$type
Definition: testCompression.php:52