MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
35 use Wikimedia\IPSet;
40 
51 class User implements IDBAccessObject, UserIdentity {
52 
56  const TOKEN_LENGTH = 32;
57 
61  const INVALID_TOKEN = '*** INVALID ***';
62 
67  const VERSION = 14;
68 
74 
78  const CHECK_USER_RIGHTS = true;
79 
83  const IGNORE_USER_RIGHTS = false;
84 
92  protected static $mCacheVars = [
93  // user table
94  'mId',
95  'mName',
96  'mRealName',
97  'mEmail',
98  'mTouched',
99  'mToken',
100  'mEmailAuthenticated',
101  'mEmailToken',
102  'mEmailTokenExpires',
103  'mRegistration',
104  'mEditCount',
105  // user_groups table
106  'mGroupMemberships',
107  // user_properties table
108  'mOptionOverrides',
109  // actor table
110  'mActorId',
111  ];
112 
114  private static $reservedUsernames = false;
115 
117  // @{
119  public $mId;
121  public $mName;
123  protected $mActorId;
125  public $mRealName;
126 
128  public $mEmail;
130  public $mTouched;
132  protected $mQuickTouched;
134  protected $mToken;
138  protected $mEmailToken;
142  protected $mRegistration;
144  protected $mEditCount;
148  protected $mOptionOverrides;
149  // @}
150 
151  // @{
156 
160  protected $mLoadedItems = [];
161  // @}
162 
173  public $mFrom;
174 
179  protected $mNewtalk;
181  protected $mDatePreference;
188  public $mBlockedby;
190  protected $mHash;
196  protected $mBlockreason;
198  protected $mEffectiveGroups;
200  protected $mImplicitGroups;
202  protected $mFormerGroups;
204  protected $mGlobalBlock;
206  protected $mLocked;
213  public $mHideName;
215  public $mOptions;
216 
218  private $mRequest;
219 
225  public $mBlock;
226 
228  protected $mAllowUsertalk;
229 
231  private $mBlockedFromCreateAccount = false;
232 
234  protected $queryFlagsUsed = self::READ_NORMAL;
235 
237  public static $idCacheByName = [];
238 
250  public function __construct() {
251  $this->clearInstanceCache( 'defaults' );
252  }
253 
257  public function __toString() {
258  return (string)$this->getName();
259  }
260 
261  public function &__get( $name ) {
262  // A shortcut for $mRights deprecation phase
263  if ( $name === 'mRights' ) {
264  $copy = $this->getRights();
265  return $copy;
266  } elseif ( !property_exists( $this, $name ) ) {
267  // T227688 - do not break $u->foo['bar'] = 1
268  wfLogWarning( 'tried to get non-existent property' );
269  $this->$name = null;
270  return $this->$name;
271  } else {
272  wfLogWarning( 'tried to get non-visible property' );
273  $null = null;
274  return $null;
275  }
276  }
277 
278  public function __set( $name, $value ) {
279  // A shortcut for $mRights deprecation phase, only known legitimate use was for
280  // testing purposes, other uses seem bad in principle
281  if ( $name === 'mRights' ) {
282  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
283  $this,
284  is_null( $value ) ? [] : $value
285  );
286  } elseif ( !property_exists( $this, $name ) ) {
287  $this->$name = $value;
288  } else {
289  wfLogWarning( 'tried to set non-visible property' );
290  }
291  }
292 
307  public function isSafeToLoad() {
308  global $wgFullyInitialised;
309 
310  // The user is safe to load if:
311  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
312  // * mLoadedItems === true (already loaded)
313  // * mFrom !== 'session' (sessions not involved at all)
314 
315  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
316  $this->mLoadedItems === true || $this->mFrom !== 'session';
317  }
318 
324  public function load( $flags = self::READ_NORMAL ) {
325  global $wgFullyInitialised;
326 
327  if ( $this->mLoadedItems === true ) {
328  return;
329  }
330 
331  // Set it now to avoid infinite recursion in accessors
332  $oldLoadedItems = $this->mLoadedItems;
333  $this->mLoadedItems = true;
334  $this->queryFlagsUsed = $flags;
335 
336  // If this is called too early, things are likely to break.
337  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
339  ->warning( 'User::loadFromSession called before the end of Setup.php', [
340  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
341  ] );
342  $this->loadDefaults();
343  $this->mLoadedItems = $oldLoadedItems;
344  return;
345  }
346 
347  switch ( $this->mFrom ) {
348  case 'defaults':
349  $this->loadDefaults();
350  break;
351  case 'id':
352  // Make sure this thread sees its own changes, if the ID isn't 0
353  if ( $this->mId != 0 ) {
354  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
355  if ( $lb->hasOrMadeRecentMasterChanges() ) {
356  $flags |= self::READ_LATEST;
357  $this->queryFlagsUsed = $flags;
358  }
359  }
360 
361  $this->loadFromId( $flags );
362  break;
363  case 'actor':
364  case 'name':
365  // Make sure this thread sees its own changes
366  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
367  if ( $lb->hasOrMadeRecentMasterChanges() ) {
368  $flags |= self::READ_LATEST;
369  $this->queryFlagsUsed = $flags;
370  }
371 
372  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
373  $row = wfGetDB( $index )->selectRow(
374  'actor',
375  [ 'actor_id', 'actor_user', 'actor_name' ],
376  $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
377  __METHOD__,
378  $options
379  );
380 
381  if ( !$row ) {
382  // Ugh.
383  $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
384  } elseif ( $row->actor_user ) {
385  $this->mId = $row->actor_user;
386  $this->loadFromId( $flags );
387  } else {
388  $this->loadDefaults( $row->actor_name, $row->actor_id );
389  }
390  break;
391  case 'session':
392  if ( !$this->loadFromSession() ) {
393  // Loading from session failed. Load defaults.
394  $this->loadDefaults();
395  }
396  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
397  break;
398  default:
399  throw new UnexpectedValueException(
400  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
401  }
402  }
403 
409  public function loadFromId( $flags = self::READ_NORMAL ) {
410  if ( $this->mId == 0 ) {
411  // Anonymous users are not in the database (don't need cache)
412  $this->loadDefaults();
413  return false;
414  }
415 
416  // Try cache (unless this needs data from the master DB).
417  // NOTE: if this thread called saveSettings(), the cache was cleared.
418  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
419  if ( $latest ) {
420  if ( !$this->loadFromDatabase( $flags ) ) {
421  // Can't load from ID
422  return false;
423  }
424  } else {
425  $this->loadFromCache();
426  }
427 
428  $this->mLoadedItems = true;
429  $this->queryFlagsUsed = $flags;
430 
431  return true;
432  }
433 
439  public static function purge( $dbDomain, $userId ) {
440  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
441  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
442  $cache->delete( $key );
443  }
444 
450  protected function getCacheKey( WANObjectCache $cache ) {
451  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
452 
453  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
454  }
455 
462  $id = $this->getId();
463 
464  return $id ? [ $this->getCacheKey( $cache ) ] : [];
465  }
466 
473  protected function loadFromCache() {
474  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
475  $data = $cache->getWithSetCallback(
476  $this->getCacheKey( $cache ),
477  $cache::TTL_HOUR,
478  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
479  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
480  wfDebug( "User: cache miss for user {$this->mId}\n" );
481 
482  $this->loadFromDatabase( self::READ_NORMAL );
483  $this->loadGroups();
484  $this->loadOptions();
485 
486  $data = [];
487  foreach ( self::$mCacheVars as $name ) {
488  $data[$name] = $this->$name;
489  }
490 
491  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
492 
493  // if a user group membership is about to expire, the cache needs to
494  // expire at that time (T163691)
495  foreach ( $this->mGroupMemberships as $ugm ) {
496  if ( $ugm->getExpiry() ) {
497  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
498  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
499  $ttl = $secondsUntilExpiry;
500  }
501  }
502  }
503 
504  return $data;
505  },
506  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
507  );
508 
509  // Restore from cache
510  foreach ( self::$mCacheVars as $name ) {
511  $this->$name = $data[$name];
512  }
513 
514  return true;
515  }
516 
518  // @{
519 
536  public static function newFromName( $name, $validate = 'valid' ) {
537  if ( $validate === true ) {
538  $validate = 'valid';
539  }
540  $name = self::getCanonicalName( $name, $validate );
541  if ( $name === false ) {
542  return false;
543  }
544 
545  // Create unloaded user object
546  $u = new User;
547  $u->mName = $name;
548  $u->mFrom = 'name';
549  $u->setItemLoaded( 'name' );
550 
551  return $u;
552  }
553 
560  public static function newFromId( $id ) {
561  $u = new User;
562  $u->mId = $id;
563  $u->mFrom = 'id';
564  $u->setItemLoaded( 'id' );
565  return $u;
566  }
567 
575  public static function newFromActorId( $id ) {
576  $u = new User;
577  $u->mActorId = $id;
578  $u->mFrom = 'actor';
579  $u->setItemLoaded( 'actor' );
580  return $u;
581  }
582 
592  public static function newFromIdentity( UserIdentity $identity ) {
593  if ( $identity instanceof User ) {
594  return $identity;
595  }
596 
597  return self::newFromAnyId(
598  $identity->getId() === 0 ? null : $identity->getId(),
599  $identity->getName() === '' ? null : $identity->getName(),
600  $identity->getActorId() === 0 ? null : $identity->getActorId()
601  );
602  }
603 
617  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
618  // Stop-gap solution for the problem described in T222212.
619  // Force the User ID and Actor ID to zero for users loaded from the database
620  // of another wiki, to prevent subtle data corruption and confusing failure modes.
621  if ( $dbDomain !== false ) {
622  $userId = 0;
623  $actorId = 0;
624  }
625 
626  $user = new User;
627  $user->mFrom = 'defaults';
628 
629  if ( $actorId !== null ) {
630  $user->mActorId = (int)$actorId;
631  if ( $user->mActorId !== 0 ) {
632  $user->mFrom = 'actor';
633  }
634  $user->setItemLoaded( 'actor' );
635  }
636 
637  if ( $userName !== null && $userName !== '' ) {
638  $user->mName = $userName;
639  $user->mFrom = 'name';
640  $user->setItemLoaded( 'name' );
641  }
642 
643  if ( $userId !== null ) {
644  $user->mId = (int)$userId;
645  if ( $user->mId !== 0 ) {
646  $user->mFrom = 'id';
647  }
648  $user->setItemLoaded( 'id' );
649  }
650 
651  if ( $user->mFrom === 'defaults' ) {
652  throw new InvalidArgumentException(
653  'Cannot create a user with no name, no ID, and no actor ID'
654  );
655  }
656 
657  return $user;
658  }
659 
671  public static function newFromConfirmationCode( $code, $flags = 0 ) {
672  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
673  ? wfGetDB( DB_MASTER )
674  : wfGetDB( DB_REPLICA );
675 
676  $id = $db->selectField(
677  'user',
678  'user_id',
679  [
680  'user_email_token' => md5( $code ),
681  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
682  ]
683  );
684 
685  return $id ? self::newFromId( $id ) : null;
686  }
687 
695  public static function newFromSession( WebRequest $request = null ) {
696  $user = new User;
697  $user->mFrom = 'session';
698  $user->mRequest = $request;
699  return $user;
700  }
701 
717  public static function newFromRow( $row, $data = null ) {
718  $user = new User;
719  $user->loadFromRow( $row, $data );
720  return $user;
721  }
722 
758  public static function newSystemUser( $name, $options = [] ) {
759  $options += [
760  'validate' => 'valid',
761  'create' => true,
762  'steal' => false,
763  ];
764 
765  $name = self::getCanonicalName( $name, $options['validate'] );
766  if ( $name === false ) {
767  return null;
768  }
769 
770  $dbr = wfGetDB( DB_REPLICA );
771  $userQuery = self::getQueryInfo();
772  $row = $dbr->selectRow(
773  $userQuery['tables'],
774  $userQuery['fields'],
775  [ 'user_name' => $name ],
776  __METHOD__,
777  [],
778  $userQuery['joins']
779  );
780  if ( !$row ) {
781  // Try the master database...
782  $dbw = wfGetDB( DB_MASTER );
783  $row = $dbw->selectRow(
784  $userQuery['tables'],
785  $userQuery['fields'],
786  [ 'user_name' => $name ],
787  __METHOD__,
788  [],
789  $userQuery['joins']
790  );
791  }
792 
793  if ( !$row ) {
794  // No user. Create it?
795  if ( !$options['create'] ) {
796  // No.
797  return null;
798  }
799 
800  // If it's a reserved user that had an anonymous actor created for it at
801  // some point, we need special handling.
802  if ( !self::isValidUserName( $name ) || self::isUsableName( $name ) ) {
803  // Not reserved, so just create it.
804  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
805  }
806 
807  // It is reserved. Check for an anonymous actor row.
808  $dbw = wfGetDB( DB_MASTER );
809  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $name ) {
810  $row = $dbw->selectRow(
811  'actor',
812  [ 'actor_id' ],
813  [ 'actor_name' => $name, 'actor_user' => null ],
814  $fname,
815  [ 'FOR UPDATE' ]
816  );
817  if ( !$row ) {
818  // No anonymous actor.
819  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
820  }
821 
822  // There is an anonymous actor. Delete the actor row so we can create the user,
823  // then restore the old actor_id so as to not break existing references.
824  // @todo If MediaWiki ever starts using foreign keys for `actor`, this will break things.
825  $dbw->delete( 'actor', [ 'actor_id' => $row->actor_id ], $fname );
826  $user = self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
827  $dbw->update(
828  'actor',
829  [ 'actor_id' => $row->actor_id ],
830  [ 'actor_id' => $user->getActorId() ],
831  $fname
832  );
833  $user->clearInstanceCache( 'id' );
834  $user->invalidateCache();
835  return $user;
836  } );
837  }
838 
839  $user = self::newFromRow( $row );
840 
841  // A user is considered to exist as a non-system user if it can
842  // authenticate, or has an email set, or has a non-invalid token.
843  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
844  AuthManager::singleton()->userCanAuthenticate( $name )
845  ) {
846  // User exists. Steal it?
847  if ( !$options['steal'] ) {
848  return null;
849  }
850 
851  AuthManager::singleton()->revokeAccessForUser( $name );
852 
853  $user->invalidateEmail();
854  $user->mToken = self::INVALID_TOKEN;
855  $user->saveSettings();
856  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
857  }
858 
859  return $user;
860  }
861 
862  // @}
863 
869  public static function whoIs( $id ) {
870  return UserCache::singleton()->getProp( $id, 'name' );
871  }
872 
879  public static function whoIsReal( $id ) {
880  return UserCache::singleton()->getProp( $id, 'real_name' );
881  }
882 
889  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
890  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
891  $name = (string)$name;
892  $nt = Title::makeTitleSafe( NS_USER, $name );
893  if ( is_null( $nt ) ) {
894  // Illegal name
895  return null;
896  }
897 
898  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
899  return is_null( self::$idCacheByName[$name] ) ? null : (int)self::$idCacheByName[$name];
900  }
901 
902  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
903  $db = wfGetDB( $index );
904 
905  $s = $db->selectRow(
906  'user',
907  [ 'user_id' ],
908  [ 'user_name' => $nt->getText() ],
909  __METHOD__,
910  $options
911  );
912 
913  if ( $s === false ) {
914  $result = null;
915  } else {
916  $result = (int)$s->user_id;
917  }
918 
919  if ( count( self::$idCacheByName ) >= 1000 ) {
920  self::$idCacheByName = [];
921  }
922 
923  self::$idCacheByName[$name] = $result;
924 
925  return $result;
926  }
927 
931  public static function resetIdByNameCache() {
932  self::$idCacheByName = [];
933  }
934 
951  public static function isIP( $name ) {
952  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
953  || IP::isIPv6( $name );
954  }
955 
962  public function isIPRange() {
963  return IP::isValidRange( $this->mName );
964  }
965 
977  public static function isValidUserName( $name ) {
978  global $wgMaxNameChars;
979 
980  if ( $name == ''
981  || self::isIP( $name )
982  || strpos( $name, '/' ) !== false
983  || strlen( $name ) > $wgMaxNameChars
984  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
985  ) {
986  return false;
987  }
988 
989  // Ensure that the name can't be misresolved as a different title,
990  // such as with extra namespace keys at the start.
991  $parsed = Title::newFromText( $name );
992  if ( is_null( $parsed )
993  || $parsed->getNamespace()
994  || strcmp( $name, $parsed->getPrefixedText() ) ) {
995  return false;
996  }
997 
998  // Check an additional blacklist of troublemaker characters.
999  // Should these be merged into the title char list?
1000  $unicodeBlacklist = '/[' .
1001  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
1002  '\x{00a0}' . # non-breaking space
1003  '\x{2000}-\x{200f}' . # various whitespace
1004  '\x{2028}-\x{202f}' . # breaks and control chars
1005  '\x{3000}' . # ideographic space
1006  '\x{e000}-\x{f8ff}' . # private use
1007  ']/u';
1008  if ( preg_match( $unicodeBlacklist, $name ) ) {
1009  return false;
1010  }
1011 
1012  return true;
1013  }
1014 
1026  public static function isUsableName( $name ) {
1027  global $wgReservedUsernames;
1028  // Must be a valid username, obviously ;)
1029  if ( !self::isValidUserName( $name ) ) {
1030  return false;
1031  }
1032 
1033  if ( !self::$reservedUsernames ) {
1034  self::$reservedUsernames = $wgReservedUsernames;
1035  Hooks::run( 'UserGetReservedNames', [ &self::$reservedUsernames ] );
1036  }
1037 
1038  // Certain names may be reserved for batch processes.
1039  foreach ( self::$reservedUsernames as $reserved ) {
1040  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1041  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1042  }
1043  if ( $reserved == $name ) {
1044  return false;
1045  }
1046  }
1047  return true;
1048  }
1049 
1060  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1061  if ( $groups === [] ) {
1062  return UserArrayFromResult::newFromIDs( [] );
1063  }
1064 
1065  $groups = array_unique( (array)$groups );
1066  $limit = min( 5000, $limit );
1067 
1068  $conds = [ 'ug_group' => $groups ];
1069  if ( $after !== null ) {
1070  $conds[] = 'ug_user > ' . (int)$after;
1071  }
1072 
1073  $dbr = wfGetDB( DB_REPLICA );
1074  $ids = $dbr->selectFieldValues(
1075  'user_groups',
1076  'ug_user',
1077  $conds,
1078  __METHOD__,
1079  [
1080  'DISTINCT' => true,
1081  'ORDER BY' => 'ug_user',
1082  'LIMIT' => $limit,
1083  ]
1084  ) ?: [];
1085  return UserArray::newFromIDs( $ids );
1086  }
1087 
1100  public static function isCreatableName( $name ) {
1102 
1103  // Ensure that the username isn't longer than 235 bytes, so that
1104  // (at least for the builtin skins) user javascript and css files
1105  // will work. (T25080)
1106  if ( strlen( $name ) > 235 ) {
1107  wfDebugLog( 'username', __METHOD__ .
1108  ": '$name' invalid due to length" );
1109  return false;
1110  }
1111 
1112  // Preg yells if you try to give it an empty string
1113  if ( $wgInvalidUsernameCharacters !== '' &&
1114  preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name )
1115  ) {
1116  wfDebugLog( 'username', __METHOD__ .
1117  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1118  return false;
1119  }
1120 
1121  return self::isUsableName( $name );
1122  }
1123 
1130  public function isValidPassword( $password ) {
1131  // simple boolean wrapper for checkPasswordValidity
1132  return $this->checkPasswordValidity( $password )->isGood();
1133  }
1134 
1156  public function checkPasswordValidity( $password ) {
1157  global $wgPasswordPolicy;
1158 
1159  $upp = new UserPasswordPolicy(
1160  $wgPasswordPolicy['policies'],
1161  $wgPasswordPolicy['checks']
1162  );
1163 
1164  $status = Status::newGood( [] );
1165  $result = false; // init $result to false for the internal checks
1166 
1167  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1168  $status->error( $result );
1169  return $status;
1170  }
1171 
1172  if ( $result === false ) {
1173  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1174  return $status;
1175  }
1176 
1177  if ( $result === true ) {
1178  return $status;
1179  }
1180 
1181  $status->error( $result );
1182  return $status; // the isValidPassword hook set a string $result and returned true
1183  }
1184 
1198  public static function getCanonicalName( $name, $validate = 'valid' ) {
1199  // Force usernames to capital
1200  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1201 
1202  # Reject names containing '#'; these will be cleaned up
1203  # with title normalisation, but then it's too late to
1204  # check elsewhere
1205  if ( strpos( $name, '#' ) !== false ) {
1206  return false;
1207  }
1208 
1209  // Clean up name according to title rules,
1210  // but only when validation is requested (T14654)
1211  $t = ( $validate !== false ) ?
1212  Title::newFromText( $name, NS_USER ) : Title::makeTitle( NS_USER, $name );
1213  // Check for invalid titles
1214  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1215  return false;
1216  }
1217 
1218  $name = $t->getText();
1219 
1220  switch ( $validate ) {
1221  case false:
1222  break;
1223  case 'valid':
1224  if ( !self::isValidUserName( $name ) ) {
1225  $name = false;
1226  }
1227  break;
1228  case 'usable':
1229  if ( !self::isUsableName( $name ) ) {
1230  $name = false;
1231  }
1232  break;
1233  case 'creatable':
1234  if ( !self::isCreatableName( $name ) ) {
1235  $name = false;
1236  }
1237  break;
1238  default:
1239  throw new InvalidArgumentException(
1240  'Invalid parameter value for $validate in ' . __METHOD__ );
1241  }
1242  return $name;
1243  }
1244 
1254  public function loadDefaults( $name = false, $actorId = null ) {
1255  $this->mId = 0;
1256  $this->mName = $name;
1257  $this->mActorId = $actorId;
1258  $this->mRealName = '';
1259  $this->mEmail = '';
1260  $this->mOptionOverrides = null;
1261  $this->mOptionsLoaded = false;
1262 
1263  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1264  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1265  if ( $loggedOut !== 0 ) {
1266  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1267  } else {
1268  $this->mTouched = '1'; # Allow any pages to be cached
1269  }
1270 
1271  $this->mToken = null; // Don't run cryptographic functions till we need a token
1272  $this->mEmailAuthenticated = null;
1273  $this->mEmailToken = '';
1274  $this->mEmailTokenExpires = null;
1275  $this->mRegistration = wfTimestamp( TS_MW );
1276  $this->mGroupMemberships = [];
1277 
1278  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1279  }
1280 
1293  public function isItemLoaded( $item, $all = 'all' ) {
1294  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1295  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1296  }
1297 
1303  protected function setItemLoaded( $item ) {
1304  if ( is_array( $this->mLoadedItems ) ) {
1305  $this->mLoadedItems[$item] = true;
1306  }
1307  }
1308 
1314  private function loadFromSession() {
1315  // MediaWiki\Session\Session already did the necessary authentication of the user
1316  // returned here, so just use it if applicable.
1317  $session = $this->getRequest()->getSession();
1318  $user = $session->getUser();
1319  if ( $user->isLoggedIn() ) {
1320  $this->loadFromUserObject( $user );
1321 
1322  // Other code expects these to be set in the session, so set them.
1323  $session->set( 'wsUserID', $this->getId() );
1324  $session->set( 'wsUserName', $this->getName() );
1325  $session->set( 'wsToken', $this->getToken() );
1326 
1327  return true;
1328  }
1329 
1330  return false;
1331  }
1332 
1338  public function trackBlockWithCookie() {
1339  wfDeprecated( __METHOD__, '1.34' );
1340  // Obsolete.
1341  // MediaWiki::preOutputCommit() handles this whenever possible.
1342  }
1343 
1351  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1352  // Paranoia
1353  $this->mId = intval( $this->mId );
1354 
1355  if ( !$this->mId ) {
1356  // Anonymous users are not in the database
1357  $this->loadDefaults();
1358  return false;
1359  }
1360 
1361  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1362  $db = wfGetDB( $index );
1363 
1364  $userQuery = self::getQueryInfo();
1365  $s = $db->selectRow(
1366  $userQuery['tables'],
1367  $userQuery['fields'],
1368  [ 'user_id' => $this->mId ],
1369  __METHOD__,
1370  $options,
1371  $userQuery['joins']
1372  );
1373 
1374  $this->queryFlagsUsed = $flags;
1375  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1376 
1377  if ( $s !== false ) {
1378  // Initialise user table data
1379  $this->loadFromRow( $s );
1380  $this->mGroupMemberships = null; // deferred
1381  $this->getEditCount(); // revalidation for nulls
1382  return true;
1383  }
1384 
1385  // Invalid user_id
1386  $this->mId = 0;
1387  $this->loadDefaults();
1388 
1389  return false;
1390  }
1391 
1404  protected function loadFromRow( $row, $data = null ) {
1405  if ( !is_object( $row ) ) {
1406  throw new InvalidArgumentException( '$row must be an object' );
1407  }
1408 
1409  $all = true;
1410 
1411  $this->mGroupMemberships = null; // deferred
1412 
1413  if ( isset( $row->actor_id ) ) {
1414  $this->mActorId = (int)$row->actor_id;
1415  if ( $this->mActorId !== 0 ) {
1416  $this->mFrom = 'actor';
1417  }
1418  $this->setItemLoaded( 'actor' );
1419  } else {
1420  $all = false;
1421  }
1422 
1423  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1424  $this->mName = $row->user_name;
1425  $this->mFrom = 'name';
1426  $this->setItemLoaded( 'name' );
1427  } else {
1428  $all = false;
1429  }
1430 
1431  if ( isset( $row->user_real_name ) ) {
1432  $this->mRealName = $row->user_real_name;
1433  $this->setItemLoaded( 'realname' );
1434  } else {
1435  $all = false;
1436  }
1437 
1438  if ( isset( $row->user_id ) ) {
1439  $this->mId = intval( $row->user_id );
1440  if ( $this->mId !== 0 ) {
1441  $this->mFrom = 'id';
1442  }
1443  $this->setItemLoaded( 'id' );
1444  } else {
1445  $all = false;
1446  }
1447 
1448  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1449  self::$idCacheByName[$row->user_name] = $row->user_id;
1450  }
1451 
1452  if ( isset( $row->user_editcount ) ) {
1453  $this->mEditCount = $row->user_editcount;
1454  } else {
1455  $all = false;
1456  }
1457 
1458  if ( isset( $row->user_touched ) ) {
1459  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1460  } else {
1461  $all = false;
1462  }
1463 
1464  if ( isset( $row->user_token ) ) {
1465  // The definition for the column is binary(32), so trim the NULs
1466  // that appends. The previous definition was char(32), so trim
1467  // spaces too.
1468  $this->mToken = rtrim( $row->user_token, " \0" );
1469  if ( $this->mToken === '' ) {
1470  $this->mToken = null;
1471  }
1472  } else {
1473  $all = false;
1474  }
1475 
1476  if ( isset( $row->user_email ) ) {
1477  $this->mEmail = $row->user_email;
1478  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1479  $this->mEmailToken = $row->user_email_token;
1480  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1481  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1482  } else {
1483  $all = false;
1484  }
1485 
1486  if ( $all ) {
1487  $this->mLoadedItems = true;
1488  }
1489 
1490  if ( is_array( $data ) ) {
1491  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1492  if ( $data['user_groups'] === [] ) {
1493  $this->mGroupMemberships = [];
1494  } else {
1495  $firstGroup = reset( $data['user_groups'] );
1496  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1497  $this->mGroupMemberships = [];
1498  foreach ( $data['user_groups'] as $row ) {
1499  $ugm = UserGroupMembership::newFromRow( (object)$row );
1500  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1501  }
1502  }
1503  }
1504  }
1505  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1506  $this->loadOptions( $data['user_properties'] );
1507  }
1508  }
1509  }
1510 
1516  protected function loadFromUserObject( $user ) {
1517  $user->load();
1518  foreach ( self::$mCacheVars as $var ) {
1519  $this->$var = $user->$var;
1520  }
1521  }
1522 
1526  private function loadGroups() {
1527  if ( is_null( $this->mGroupMemberships ) ) {
1528  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1529  ? wfGetDB( DB_MASTER )
1530  : wfGetDB( DB_REPLICA );
1531  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1532  $this->mId, $db );
1533  }
1534  }
1535 
1550  public function addAutopromoteOnceGroups( $event ) {
1552 
1553  if ( wfReadOnly() || !$this->getId() ) {
1554  return [];
1555  }
1556 
1557  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1558  if ( $toPromote === [] ) {
1559  return [];
1560  }
1561 
1562  if ( !$this->checkAndSetTouched() ) {
1563  return []; // raced out (bug T48834)
1564  }
1565 
1566  $oldGroups = $this->getGroups(); // previous groups
1567  $oldUGMs = $this->getGroupMemberships();
1568  foreach ( $toPromote as $group ) {
1569  $this->addGroup( $group );
1570  }
1571  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1572  $newUGMs = $this->getGroupMemberships();
1573 
1574  // update groups in external authentication database
1575  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1576 
1577  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1578  $logEntry->setPerformer( $this );
1579  $logEntry->setTarget( $this->getUserPage() );
1580  $logEntry->setParameters( [
1581  '4::oldgroups' => $oldGroups,
1582  '5::newgroups' => $newGroups,
1583  ] );
1584  $logid = $logEntry->insert();
1585  if ( $wgAutopromoteOnceLogInRC ) {
1586  $logEntry->publish( $logid );
1587  }
1588 
1589  return $toPromote;
1590  }
1591 
1601  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1602  if ( $this->mTouched ) {
1603  // CAS check: only update if the row wasn't changed sicne it was loaded.
1604  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1605  }
1606 
1607  return $conditions;
1608  }
1609 
1619  protected function checkAndSetTouched() {
1620  $this->load();
1621 
1622  if ( !$this->mId ) {
1623  return false; // anon
1624  }
1625 
1626  // Get a new user_touched that is higher than the old one
1627  $newTouched = $this->newTouchedTimestamp();
1628 
1629  $dbw = wfGetDB( DB_MASTER );
1630  $dbw->update( 'user',
1631  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1632  $this->makeUpdateConditions( $dbw, [
1633  'user_id' => $this->mId,
1634  ] ),
1635  __METHOD__
1636  );
1637  $success = ( $dbw->affectedRows() > 0 );
1638 
1639  if ( $success ) {
1640  $this->mTouched = $newTouched;
1641  $this->clearSharedCache( 'changed' );
1642  } else {
1643  // Clears on failure too since that is desired if the cache is stale
1644  $this->clearSharedCache( 'refresh' );
1645  }
1646 
1647  return $success;
1648  }
1649 
1657  public function clearInstanceCache( $reloadFrom = false ) {
1658  global $wgFullyInitialised;
1659 
1660  $this->mNewtalk = -1;
1661  $this->mDatePreference = null;
1662  $this->mBlockedby = -1; # Unset
1663  $this->mHash = false;
1664  $this->mEffectiveGroups = null;
1665  $this->mImplicitGroups = null;
1666  $this->mGroupMemberships = null;
1667  $this->mOptions = null;
1668  $this->mOptionsLoaded = false;
1669  $this->mEditCount = null;
1670 
1671  // Replacement of former `$this->mRights = null` line
1672  if ( $wgFullyInitialised && $this->mFrom ) {
1673  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
1674  $this
1675  );
1676  }
1677 
1678  if ( $reloadFrom ) {
1679  $this->mLoadedItems = [];
1680  $this->mFrom = $reloadFrom;
1681  }
1682  }
1683 
1685  private static $defOpt = null;
1687  private static $defOptLang = null;
1688 
1695  public static function resetGetDefaultOptionsForTestsOnly() {
1696  Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
1697  self::$defOpt = null;
1698  self::$defOptLang = null;
1699  }
1700 
1707  public static function getDefaultOptions() {
1711 
1712  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1713  if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
1714  // The content language does not change (and should not change) mid-request, but the
1715  // unit tests change it anyway, and expect this method to return values relevant to the
1716  // current content language.
1717  return self::$defOpt;
1718  }
1719 
1720  self::$defOpt = $wgDefaultUserOptions;
1721  // Default language setting
1722  self::$defOptLang = $contLang->getCode();
1723  self::$defOpt['language'] = self::$defOptLang;
1724  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1725  if ( $langCode === $contLang->getCode() ) {
1726  self::$defOpt['variant'] = $langCode;
1727  } else {
1728  self::$defOpt["variant-$langCode"] = $langCode;
1729  }
1730  }
1731 
1732  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1733  // since extensions may change the set of searchable namespaces depending
1734  // on user groups/permissions.
1735  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1736  self::$defOpt['searchNs' . $nsnum] = (bool)$val;
1737  }
1738  self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1739 
1740  Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
1741 
1742  return self::$defOpt;
1743  }
1744 
1751  public static function getDefaultOption( $opt ) {
1752  $defOpts = self::getDefaultOptions();
1753  return $defOpts[$opt] ?? null;
1754  }
1755 
1765  private function getBlockedStatus( $fromReplica = true ) {
1766  if ( $this->mBlockedby != -1 ) {
1767  return;
1768  }
1769 
1770  wfDebug( __METHOD__ . ": checking...\n" );
1771 
1772  // Initialize data...
1773  // Otherwise something ends up stomping on $this->mBlockedby when
1774  // things get lazy-loaded later, causing false positive block hits
1775  // due to -1 !== 0. Probably session-related... Nothing should be
1776  // overwriting mBlockedby, surely?
1777  $this->load();
1778 
1779  // TODO: Block checking shouldn't really be done from the User object. Block
1780  // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1781  // which need more knowledge of the request context than the User should have.
1782  // Since we do currently check blocks from the User, we have to do the following
1783  // here:
1784  // - Check if this is the user associated with the main request
1785  // - If so, pass the relevant request information to the block manager
1786  $request = null;
1787 
1788  // The session user is set up towards the end of Setup.php. Until then,
1789  // assume it's a logged-out user.
1790  $sessionUser = RequestContext::getMain()->getUser();
1791  $globalUserName = $sessionUser->isSafeToLoad()
1792  ? $sessionUser->getName()
1793  : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1794 
1795  if ( $this->getName() === $globalUserName ) {
1796  // This is the global user, so we need to pass the request
1797  $request = $this->getRequest();
1798  }
1799 
1800  // @phan-suppress-next-line PhanAccessMethodInternal It's the only allowed use
1801  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1802  $this,
1803  $request,
1804  $fromReplica
1805  );
1806 
1807  if ( $block ) {
1808  $this->mBlock = $block;
1809  $this->mBlockedby = $block->getByName();
1810  $this->mBlockreason = $block->getReason();
1811  $this->mHideName = $block->getHideName();
1812  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1813  } else {
1814  $this->mBlock = null;
1815  $this->mBlockedby = '';
1816  $this->mBlockreason = '';
1817  $this->mHideName = 0;
1818  $this->mAllowUsertalk = false;
1819  }
1820  }
1821 
1830  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1831  return MediaWikiServices::getInstance()->getBlockManager()
1832  ->isDnsBlacklisted( $ip, $checkWhitelist );
1833  }
1834 
1843  public function inDnsBlacklist( $ip, $bases ) {
1844  wfDeprecated( __METHOD__, '1.34' );
1845 
1846  $found = false;
1847  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1848  if ( IP::isIPv4( $ip ) ) {
1849  // Reverse IP, T23255
1850  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1851 
1852  foreach ( (array)$bases as $base ) {
1853  // Make hostname
1854  // If we have an access key, use that too (ProjectHoneypot, etc.)
1855  $basename = $base;
1856  if ( is_array( $base ) ) {
1857  if ( count( $base ) >= 2 ) {
1858  // Access key is 1, base URL is 0
1859  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1860  } else {
1861  $host = "$ipReversed.{$base[0]}";
1862  }
1863  $basename = $base[0];
1864  } else {
1865  $host = "$ipReversed.$base";
1866  }
1867 
1868  // Send query
1869  $ipList = gethostbynamel( $host );
1870 
1871  if ( $ipList ) {
1872  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1873  $found = true;
1874  break;
1875  }
1876 
1877  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1878  }
1879  }
1880 
1881  return $found;
1882  }
1883 
1891  public static function isLocallyBlockedProxy( $ip ) {
1892  wfDeprecated( __METHOD__, '1.34' );
1893 
1894  global $wgProxyList;
1895 
1896  if ( !$wgProxyList ) {
1897  return false;
1898  }
1899 
1900  if ( !is_array( $wgProxyList ) ) {
1901  // Load values from the specified file
1902  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1903  }
1904 
1905  $resultProxyList = [];
1906  $deprecatedIPEntries = [];
1907 
1908  // backward compatibility: move all ip addresses in keys to values
1909  foreach ( $wgProxyList as $key => $value ) {
1910  $keyIsIP = IP::isIPAddress( $key );
1911  $valueIsIP = IP::isIPAddress( $value );
1912  if ( $keyIsIP && !$valueIsIP ) {
1913  $deprecatedIPEntries[] = $key;
1914  $resultProxyList[] = $key;
1915  } elseif ( $keyIsIP && $valueIsIP ) {
1916  $deprecatedIPEntries[] = $key;
1917  $resultProxyList[] = $key;
1918  $resultProxyList[] = $value;
1919  } else {
1920  $resultProxyList[] = $value;
1921  }
1922  }
1923 
1924  if ( $deprecatedIPEntries ) {
1925  wfDeprecated(
1926  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1927  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
1928  }
1929 
1930  $proxyListIPSet = new IPSet( $resultProxyList );
1931  return $proxyListIPSet->match( $ip );
1932  }
1933 
1939  public function isPingLimitable() {
1940  global $wgRateLimitsExcludedIPs;
1941  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1942  // No other good way currently to disable rate limits
1943  // for specific IPs. :P
1944  // But this is a crappy hack and should die.
1945  return false;
1946  }
1947  return !$this->isAllowed( 'noratelimit' );
1948  }
1949 
1964  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1965  // Avoid PHP 7.1 warning of passing $this by reference
1966  $user = $this;
1967  // Call the 'PingLimiter' hook
1968  $result = false;
1969  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1970  return $result;
1971  }
1972 
1973  global $wgRateLimits;
1974  if ( !isset( $wgRateLimits[$action] ) ) {
1975  return false;
1976  }
1977 
1978  $limits = array_merge(
1979  [ '&can-bypass' => true ],
1980  $wgRateLimits[$action]
1981  );
1982 
1983  // Some groups shouldn't trigger the ping limiter, ever
1984  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1985  return false;
1986  }
1987 
1988  $keys = [];
1989  $id = $this->getId();
1990  $userLimit = false;
1991  $isNewbie = $this->isNewbie();
1993 
1994  if ( $id == 0 ) {
1995  // limits for anons
1996  if ( isset( $limits['anon'] ) ) {
1997  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1998  }
1999  } elseif ( isset( $limits['user'] ) ) {
2000  // limits for logged-in users
2001  $userLimit = $limits['user'];
2002  }
2003 
2004  // limits for anons and for newbie logged-in users
2005  if ( $isNewbie ) {
2006  // ip-based limits
2007  if ( isset( $limits['ip'] ) ) {
2008  $ip = $this->getRequest()->getIP();
2009  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
2010  }
2011  // subnet-based limits
2012  if ( isset( $limits['subnet'] ) ) {
2013  $ip = $this->getRequest()->getIP();
2014  $subnet = IP::getSubnet( $ip );
2015  if ( $subnet !== false ) {
2016  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
2017  }
2018  }
2019  }
2020 
2021  // Check for group-specific permissions
2022  // If more than one group applies, use the group with the highest limit ratio (max/period)
2023  foreach ( $this->getGroups() as $group ) {
2024  if ( isset( $limits[$group] ) ) {
2025  if ( $userLimit === false
2026  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2027  ) {
2028  $userLimit = $limits[$group];
2029  }
2030  }
2031  }
2032 
2033  // limits for newbie logged-in users (override all the normal user limits)
2034  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2035  $userLimit = $limits['newbie'];
2036  }
2037 
2038  // Set the user limit key
2039  if ( $userLimit !== false ) {
2040  // phan is confused because &can-bypass's value is a bool, so it assumes
2041  // that $userLimit is also a bool here.
2042  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2043  list( $max, $period ) = $userLimit;
2044  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2045  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2046  }
2047 
2048  // ip-based limits for all ping-limitable users
2049  if ( isset( $limits['ip-all'] ) ) {
2050  $ip = $this->getRequest()->getIP();
2051  // ignore if user limit is more permissive
2052  if ( $isNewbie || $userLimit === false
2053  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2054  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2055  }
2056  }
2057 
2058  // subnet-based limits for all ping-limitable users
2059  if ( isset( $limits['subnet-all'] ) ) {
2060  $ip = $this->getRequest()->getIP();
2061  $subnet = IP::getSubnet( $ip );
2062  if ( $subnet !== false ) {
2063  // ignore if user limit is more permissive
2064  if ( $isNewbie || $userLimit === false
2065  || $limits['ip-all'][0] / $limits['ip-all'][1]
2066  > $userLimit[0] / $userLimit[1] ) {
2067  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2068  }
2069  }
2070  }
2071 
2072  $triggered = false;
2073  foreach ( $keys as $key => $limit ) {
2074  // phan is confused because &can-bypass's value is a bool, so it assumes
2075  // that $userLimit is also a bool here.
2076  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2077  list( $max, $period ) = $limit;
2078  $summary = "(limit $max in {$period}s)";
2079  $count = $cache->get( $key );
2080  // Already pinged?
2081  if ( $count && $count >= $max ) {
2082  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2083  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2084  $triggered = true;
2085  } else {
2086  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2087  if ( $incrBy > 0 ) {
2088  $cache->add( $key, 0, intval( $period ) ); // first ping
2089  }
2090  }
2091  if ( $incrBy > 0 ) {
2092  $cache->incrWithInit( $key, (int)$period, $incrBy, $incrBy );
2093  }
2094  }
2095 
2096  return $triggered;
2097  }
2098 
2110  public function isBlocked( $fromReplica = true ) {
2111  return $this->getBlock( $fromReplica ) instanceof AbstractBlock &&
2112  $this->getBlock()->appliesToRight( 'edit' );
2113  }
2114 
2121  public function getBlock( $fromReplica = true ) {
2122  $this->getBlockedStatus( $fromReplica );
2123  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
2124  }
2125 
2137  public function isBlockedFrom( $title, $fromReplica = false ) {
2138  return MediaWikiServices::getInstance()->getPermissionManager()
2139  ->isBlockedFrom( $this, $title, $fromReplica );
2140  }
2141 
2146  public function blockedBy() {
2147  $this->getBlockedStatus();
2148  return $this->mBlockedby;
2149  }
2150 
2157  public function blockedFor() {
2158  $this->getBlockedStatus();
2159  return $this->mBlockreason;
2160  }
2161 
2166  public function getBlockId() {
2167  $this->getBlockedStatus();
2168  return ( $this->mBlock ? $this->mBlock->getId() : false );
2169  }
2170 
2179  public function isBlockedGlobally( $ip = '' ) {
2180  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
2181  }
2182 
2193  public function getGlobalBlock( $ip = '' ) {
2194  if ( $this->mGlobalBlock !== null ) {
2195  return $this->mGlobalBlock ?: null;
2196  }
2197  // User is already an IP?
2198  if ( IP::isIPAddress( $this->getName() ) ) {
2199  $ip = $this->getName();
2200  } elseif ( !$ip ) {
2201  $ip = $this->getRequest()->getIP();
2202  }
2203  // Avoid PHP 7.1 warning of passing $this by reference
2204  $user = $this;
2205  $blocked = false;
2206  $block = null;
2207  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2208 
2209  if ( $blocked && $block === null ) {
2210  // back-compat: UserIsBlockedGlobally didn't have $block param first
2211  $block = new SystemBlock( [
2212  'address' => $ip,
2213  'systemBlock' => 'global-block'
2214  ] );
2215  }
2216 
2217  $this->mGlobalBlock = $blocked ? $block : false;
2218  return $this->mGlobalBlock ?: null;
2219  }
2220 
2226  public function isLocked() {
2227  if ( $this->mLocked !== null ) {
2228  return $this->mLocked;
2229  }
2230  // Reset for hook
2231  $this->mLocked = false;
2232  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2233  return $this->mLocked;
2234  }
2235 
2241  public function isHidden() {
2242  if ( $this->mHideName !== null ) {
2243  return (bool)$this->mHideName;
2244  }
2245  $this->getBlockedStatus();
2246  if ( !$this->mHideName ) {
2247  // Reset for hook
2248  $this->mHideName = false;
2249  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ], '1.34' );
2250  }
2251  return (bool)$this->mHideName;
2252  }
2253 
2258  public function getId() {
2259  if ( $this->mId === null && $this->mName !== null &&
2260  ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
2261  ) {
2262  // Special case, we know the user is anonymous
2263  return 0;
2264  }
2265 
2266  if ( !$this->isItemLoaded( 'id' ) ) {
2267  // Don't load if this was initialized from an ID
2268  $this->load();
2269  }
2270 
2271  return (int)$this->mId;
2272  }
2273 
2278  public function setId( $v ) {
2279  $this->mId = $v;
2280  $this->clearInstanceCache( 'id' );
2281  }
2282 
2287  public function getName() {
2288  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2289  // Special case optimisation
2290  return $this->mName;
2291  }
2292 
2293  $this->load();
2294  if ( $this->mName === false ) {
2295  // Clean up IPs
2296  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2297  }
2298 
2299  return $this->mName;
2300  }
2301 
2315  public function setName( $str ) {
2316  $this->load();
2317  $this->mName = $str;
2318  }
2319 
2326  public function getActorId( IDatabase $dbw = null ) {
2327  if ( !$this->isItemLoaded( 'actor' ) ) {
2328  $this->load();
2329  }
2330 
2331  if ( !$this->mActorId && $dbw ) {
2332  $q = [
2333  'actor_user' => $this->getId() ?: null,
2334  'actor_name' => (string)$this->getName(),
2335  ];
2336  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2337  throw new CannotCreateActorException(
2338  'Cannot create an actor for a usable name that is not an existing user'
2339  );
2340  }
2341  if ( $q['actor_name'] === '' ) {
2342  throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2343  }
2344  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2345  if ( $dbw->affectedRows() ) {
2346  $this->mActorId = (int)$dbw->insertId();
2347  } else {
2348  // Outdated cache?
2349  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2350  $this->mActorId = (int)$dbw->selectField(
2351  'actor',
2352  'actor_id',
2353  $q,
2354  __METHOD__,
2355  [ 'LOCK IN SHARE MODE' ]
2356  );
2357  if ( !$this->mActorId ) {
2358  throw new CannotCreateActorException(
2359  "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2360  );
2361  }
2362  }
2363  $this->invalidateCache();
2364  $this->setItemLoaded( 'actor' );
2365  }
2366 
2367  return (int)$this->mActorId;
2368  }
2369 
2374  public function getTitleKey() {
2375  return str_replace( ' ', '_', $this->getName() );
2376  }
2377 
2382  public function getNewtalk() {
2383  $this->load();
2384 
2385  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2386  if ( $this->mNewtalk === -1 ) {
2387  $this->mNewtalk = false; # reset talk page status
2388 
2389  // Check memcached separately for anons, who have no
2390  // entire User object stored in there.
2391  if ( !$this->mId ) {
2392  global $wgDisableAnonTalk;
2393  if ( $wgDisableAnonTalk ) {
2394  // Anon newtalk disabled by configuration.
2395  $this->mNewtalk = false;
2396  } else {
2397  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2398  }
2399  } else {
2400  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2401  }
2402  }
2403 
2404  return (bool)$this->mNewtalk;
2405  }
2406 
2420  public function getNewMessageLinks() {
2421  // Avoid PHP 7.1 warning of passing $this by reference
2422  $user = $this;
2423  $talks = [];
2424  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2425  return $talks;
2426  }
2427 
2428  if ( !$this->getNewtalk() ) {
2429  return [];
2430  }
2431  $utp = $this->getTalkPage();
2432  $dbr = wfGetDB( DB_REPLICA );
2433  // Get the "last viewed rev" timestamp from the oldest message notification
2434  $timestamp = $dbr->selectField( 'user_newtalk',
2435  'MIN(user_last_timestamp)',
2436  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2437  __METHOD__ );
2438  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2439  return [
2440  [
2442  'link' => $utp->getLocalURL(),
2443  'rev' => $rev
2444  ]
2445  ];
2446  }
2447 
2453  public function getNewMessageRevisionId() {
2454  $newMessageRevisionId = null;
2455  $newMessageLinks = $this->getNewMessageLinks();
2456 
2457  // Note: getNewMessageLinks() never returns more than a single link
2458  // and it is always for the same wiki, but we double-check here in
2459  // case that changes some time in the future.
2460  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2461  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2462  && $newMessageLinks[0]['rev']
2463  ) {
2465  $newMessageRevision = $newMessageLinks[0]['rev'];
2466  $newMessageRevisionId = $newMessageRevision->getId();
2467  }
2468 
2469  return $newMessageRevisionId;
2470  }
2471 
2480  protected function checkNewtalk( $field, $id ) {
2481  $dbr = wfGetDB( DB_REPLICA );
2482 
2483  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2484 
2485  return $ok !== false;
2486  }
2487 
2495  protected function updateNewtalk( $field, $id, $curRev = null ) {
2496  // Get timestamp of the talk page revision prior to the current one
2497  $prevRev = $curRev ? $curRev->getPrevious() : false;
2498  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2499  // Mark the user as having new messages since this revision
2500  $dbw = wfGetDB( DB_MASTER );
2501  $dbw->insert( 'user_newtalk',
2502  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2503  __METHOD__,
2504  [ 'IGNORE' ] );
2505  if ( $dbw->affectedRows() ) {
2506  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2507  return true;
2508  }
2509 
2510  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2511  return false;
2512  }
2513 
2520  protected function deleteNewtalk( $field, $id ) {
2521  $dbw = wfGetDB( DB_MASTER );
2522  $dbw->delete( 'user_newtalk',
2523  [ $field => $id ],
2524  __METHOD__ );
2525  if ( $dbw->affectedRows() ) {
2526  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2527  return true;
2528  }
2529 
2530  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2531  return false;
2532  }
2533 
2540  public function setNewtalk( $val, $curRev = null ) {
2541  if ( wfReadOnly() ) {
2542  return;
2543  }
2544 
2545  $this->load();
2546  $this->mNewtalk = $val;
2547 
2548  if ( $this->isAnon() ) {
2549  $field = 'user_ip';
2550  $id = $this->getName();
2551  } else {
2552  $field = 'user_id';
2553  $id = $this->getId();
2554  }
2555 
2556  if ( $val ) {
2557  $changed = $this->updateNewtalk( $field, $id, $curRev );
2558  } else {
2559  $changed = $this->deleteNewtalk( $field, $id );
2560  }
2561 
2562  if ( $changed ) {
2563  $this->invalidateCache();
2564  }
2565  }
2566 
2573  private function newTouchedTimestamp() {
2574  $time = time();
2575  if ( $this->mTouched ) {
2576  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2577  }
2578 
2579  return wfTimestamp( TS_MW, $time );
2580  }
2581 
2592  public function clearSharedCache( $mode = 'refresh' ) {
2593  if ( !$this->getId() ) {
2594  return;
2595  }
2596 
2597  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2598  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2599  $key = $this->getCacheKey( $cache );
2600 
2601  if ( $mode === 'refresh' ) {
2602  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2603  } else {
2604  $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2605  function () use ( $cache, $key ) {
2606  $cache->delete( $key );
2607  },
2608  __METHOD__
2609  );
2610  }
2611  }
2612 
2618  public function invalidateCache() {
2619  $this->touch();
2620  $this->clearSharedCache( 'changed' );
2621  }
2622 
2635  public function touch() {
2636  $id = $this->getId();
2637  if ( $id ) {
2638  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2639  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2640  $cache->touchCheckKey( $key );
2641  $this->mQuickTouched = null;
2642  }
2643  }
2644 
2650  public function validateCache( $timestamp ) {
2651  return ( $timestamp >= $this->getTouched() );
2652  }
2653 
2662  public function getTouched() {
2663  $this->load();
2664 
2665  if ( $this->mId ) {
2666  if ( $this->mQuickTouched === null ) {
2667  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2668  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2669 
2670  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2671  }
2672 
2673  return max( $this->mTouched, $this->mQuickTouched );
2674  }
2675 
2676  return $this->mTouched;
2677  }
2678 
2684  public function getDBTouched() {
2685  $this->load();
2686 
2687  return $this->mTouched;
2688  }
2689 
2706  public function setPassword( $str ) {
2707  wfDeprecated( __METHOD__, '1.27' );
2708  return $this->setPasswordInternal( $str );
2709  }
2710 
2719  public function setInternalPassword( $str ) {
2720  wfDeprecated( __METHOD__, '1.27' );
2721  $this->setPasswordInternal( $str );
2722  }
2723 
2732  private function setPasswordInternal( $str ) {
2733  $manager = AuthManager::singleton();
2734 
2735  // If the user doesn't exist yet, fail
2736  if ( !$manager->userExists( $this->getName() ) ) {
2737  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2738  }
2739 
2740  $status = $this->changeAuthenticationData( [
2741  'username' => $this->getName(),
2742  'password' => $str,
2743  'retype' => $str,
2744  ] );
2745  if ( !$status->isGood() ) {
2747  ->info( __METHOD__ . ': Password change rejected: '
2748  . $status->getWikiText( null, null, 'en' ) );
2749  return false;
2750  }
2751 
2752  $this->setOption( 'watchlisttoken', false );
2753  SessionManager::singleton()->invalidateSessionsForUser( $this );
2754 
2755  return true;
2756  }
2757 
2770  public function changeAuthenticationData( array $data ) {
2771  $manager = AuthManager::singleton();
2772  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2773  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2774 
2775  $status = Status::newGood( 'ignored' );
2776  foreach ( $reqs as $req ) {
2777  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2778  }
2779  if ( $status->getValue() === 'ignored' ) {
2780  $status->warning( 'authenticationdatachange-ignored' );
2781  }
2782 
2783  if ( $status->isGood() ) {
2784  foreach ( $reqs as $req ) {
2785  $manager->changeAuthenticationData( $req );
2786  }
2787  }
2788  return $status;
2789  }
2790 
2797  public function getToken( $forceCreation = true ) {
2799 
2800  $this->load();
2801  if ( !$this->mToken && $forceCreation ) {
2802  $this->setToken();
2803  }
2804 
2805  if ( !$this->mToken ) {
2806  // The user doesn't have a token, return null to indicate that.
2807  return null;
2808  }
2809 
2810  if ( $this->mToken === self::INVALID_TOKEN ) {
2811  // We return a random value here so existing token checks are very
2812  // likely to fail.
2813  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2814  }
2815 
2816  if ( $wgAuthenticationTokenVersion === null ) {
2817  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2818  return $this->mToken;
2819  }
2820 
2821  // $wgAuthenticationTokenVersion in use, so hmac it.
2822  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2823 
2824  // The raw hash can be overly long. Shorten it up.
2825  $len = max( 32, self::TOKEN_LENGTH );
2826  if ( strlen( $ret ) < $len ) {
2827  // Should never happen, even md5 is 128 bits
2828  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2829  }
2830 
2831  return substr( $ret, -$len );
2832  }
2833 
2840  public function setToken( $token = false ) {
2841  $this->load();
2842  if ( $this->mToken === self::INVALID_TOKEN ) {
2844  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2845  } elseif ( !$token ) {
2846  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2847  } else {
2848  $this->mToken = $token;
2849  }
2850  }
2851 
2856  public function getEmail() {
2857  $this->load();
2858  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2859  return $this->mEmail;
2860  }
2861 
2867  $this->load();
2868  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2870  }
2871 
2876  public function setEmail( $str ) {
2877  $this->load();
2878  if ( $str == $this->mEmail ) {
2879  return;
2880  }
2881  $this->invalidateEmail();
2882  $this->mEmail = $str;
2883  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2884  }
2885 
2893  public function setEmailWithConfirmation( $str ) {
2895 
2896  if ( !$wgEnableEmail ) {
2897  return Status::newFatal( 'emaildisabled' );
2898  }
2899 
2900  $oldaddr = $this->getEmail();
2901  if ( $str === $oldaddr ) {
2902  return Status::newGood( true );
2903  }
2904 
2905  $type = $oldaddr != '' ? 'changed' : 'set';
2906  $notificationResult = null;
2907 
2908  if ( $wgEmailAuthentication && $type === 'changed' ) {
2909  // Send the user an email notifying the user of the change in registered
2910  // email address on their previous email address
2911  $change = $str != '' ? 'changed' : 'removed';
2912  $notificationResult = $this->sendMail(
2913  wfMessage( 'notificationemail_subject_' . $change )->text(),
2914  wfMessage( 'notificationemail_body_' . $change,
2915  $this->getRequest()->getIP(),
2916  $this->getName(),
2917  $str )->text()
2918  );
2919  }
2920 
2921  $this->setEmail( $str );
2922 
2923  if ( $str !== '' && $wgEmailAuthentication ) {
2924  // Send a confirmation request to the new address if needed
2925  $result = $this->sendConfirmationMail( $type );
2926 
2927  if ( $notificationResult !== null ) {
2928  $result->merge( $notificationResult );
2929  }
2930 
2931  if ( $result->isGood() ) {
2932  // Say to the caller that a confirmation and notification mail has been sent
2933  $result->value = 'eauth';
2934  }
2935  } else {
2936  $result = Status::newGood( true );
2937  }
2938 
2939  return $result;
2940  }
2941 
2946  public function getRealName() {
2947  if ( !$this->isItemLoaded( 'realname' ) ) {
2948  $this->load();
2949  }
2950 
2951  return $this->mRealName;
2952  }
2953 
2958  public function setRealName( $str ) {
2959  $this->load();
2960  $this->mRealName = $str;
2961  }
2962 
2973  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2974  global $wgHiddenPrefs;
2975  $this->loadOptions();
2976 
2977  # We want 'disabled' preferences to always behave as the default value for
2978  # users, even if they have set the option explicitly in their settings (ie they
2979  # set it, and then it was disabled removing their ability to change it). But
2980  # we don't want to erase the preferences in the database in case the preference
2981  # is re-enabled again. So don't touch $mOptions, just override the returned value
2982  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2983  return self::getDefaultOption( $oname );
2984  }
2985 
2986  if ( array_key_exists( $oname, $this->mOptions ) ) {
2987  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable Obvious false positive
2988  return $this->mOptions[$oname];
2989  }
2990 
2991  return $defaultOverride;
2992  }
2993 
3002  public function getOptions( $flags = 0 ) {
3003  global $wgHiddenPrefs;
3004  $this->loadOptions();
3005  $options = $this->mOptions;
3006 
3007  # We want 'disabled' preferences to always behave as the default value for
3008  # users, even if they have set the option explicitly in their settings (ie they
3009  # set it, and then it was disabled removing their ability to change it). But
3010  # we don't want to erase the preferences in the database in case the preference
3011  # is re-enabled again. So don't touch $mOptions, just override the returned value
3012  foreach ( $wgHiddenPrefs as $pref ) {
3013  $default = self::getDefaultOption( $pref );
3014  if ( $default !== null ) {
3015  $options[$pref] = $default;
3016  }
3017  }
3018 
3019  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3020  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3021  }
3022 
3023  return $options;
3024  }
3025 
3033  public function getBoolOption( $oname ) {
3034  return (bool)$this->getOption( $oname );
3035  }
3036 
3045  public function getIntOption( $oname, $defaultOverride = 0 ) {
3046  $val = $this->getOption( $oname );
3047  if ( $val == '' ) {
3048  $val = $defaultOverride;
3049  }
3050  return intval( $val );
3051  }
3052 
3061  public function setOption( $oname, $val ) {
3062  $this->loadOptions();
3063 
3064  // Explicitly NULL values should refer to defaults
3065  if ( is_null( $val ) ) {
3066  $val = self::getDefaultOption( $oname );
3067  }
3068 
3069  $this->mOptions[$oname] = $val;
3070  }
3071 
3082  public function getTokenFromOption( $oname ) {
3083  global $wgHiddenPrefs;
3084 
3085  $id = $this->getId();
3086  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3087  return false;
3088  }
3089 
3090  $token = $this->getOption( $oname );
3091  if ( !$token ) {
3092  // Default to a value based on the user token to avoid space
3093  // wasted on storing tokens for all users. When this option
3094  // is set manually by the user, only then is it stored.
3095  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3096  }
3097 
3098  return $token;
3099  }
3100 
3110  public function resetTokenFromOption( $oname ) {
3111  global $wgHiddenPrefs;
3112  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3113  return false;
3114  }
3115 
3116  $token = MWCryptRand::generateHex( 40 );
3117  $this->setOption( $oname, $token );
3118  return $token;
3119  }
3120 
3144  public static function listOptionKinds() {
3145  return [
3146  'registered',
3147  'registered-multiselect',
3148  'registered-checkmatrix',
3149  'userjs',
3150  'special',
3151  'unused'
3152  ];
3153  }
3154 
3167  public function getOptionKinds( IContextSource $context, $options = null ) {
3168  $this->loadOptions();
3169  if ( $options === null ) {
3170  $options = $this->mOptions;
3171  }
3172 
3173  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3174  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3175  $mapping = [];
3176 
3177  // Pull out the "special" options, so they don't get converted as
3178  // multiselect or checkmatrix.
3179  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3180  foreach ( $specialOptions as $name => $value ) {
3181  unset( $prefs[$name] );
3182  }
3183 
3184  // Multiselect and checkmatrix options are stored in the database with
3185  // one key per option, each having a boolean value. Extract those keys.
3186  $multiselectOptions = [];
3187  foreach ( $prefs as $name => $info ) {
3188  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3189  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3190  $opts = HTMLFormField::flattenOptions( $info['options'] );
3191  $prefix = $info['prefix'] ?? $name;
3192 
3193  foreach ( $opts as $value ) {
3194  $multiselectOptions["$prefix$value"] = true;
3195  }
3196 
3197  unset( $prefs[$name] );
3198  }
3199  }
3200  $checkmatrixOptions = [];
3201  foreach ( $prefs as $name => $info ) {
3202  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3203  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3204  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3205  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3206  $prefix = $info['prefix'] ?? $name;
3207 
3208  foreach ( $columns as $column ) {
3209  foreach ( $rows as $row ) {
3210  $checkmatrixOptions["$prefix$column-$row"] = true;
3211  }
3212  }
3213 
3214  unset( $prefs[$name] );
3215  }
3216  }
3217 
3218  // $value is ignored
3219  foreach ( $options as $key => $value ) {
3220  if ( isset( $prefs[$key] ) ) {
3221  $mapping[$key] = 'registered';
3222  } elseif ( isset( $multiselectOptions[$key] ) ) {
3223  $mapping[$key] = 'registered-multiselect';
3224  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3225  $mapping[$key] = 'registered-checkmatrix';
3226  } elseif ( isset( $specialOptions[$key] ) ) {
3227  $mapping[$key] = 'special';
3228  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3229  $mapping[$key] = 'userjs';
3230  } else {
3231  $mapping[$key] = 'unused';
3232  }
3233  }
3234 
3235  return $mapping;
3236  }
3237 
3252  public function resetOptions(
3253  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3254  IContextSource $context = null
3255  ) {
3256  $this->load();
3257  $defaultOptions = self::getDefaultOptions();
3258 
3259  if ( !is_array( $resetKinds ) ) {
3260  $resetKinds = [ $resetKinds ];
3261  }
3262 
3263  if ( in_array( 'all', $resetKinds ) ) {
3264  $newOptions = $defaultOptions;
3265  } else {
3266  if ( $context === null ) {
3268  }
3269 
3270  $optionKinds = $this->getOptionKinds( $context );
3271  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3272  $newOptions = [];
3273 
3274  // Use default values for the options that should be deleted, and
3275  // copy old values for the ones that shouldn't.
3276  foreach ( $this->mOptions as $key => $value ) {
3277  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3278  if ( array_key_exists( $key, $defaultOptions ) ) {
3279  $newOptions[$key] = $defaultOptions[$key];
3280  }
3281  } else {
3282  $newOptions[$key] = $value;
3283  }
3284  }
3285  }
3286 
3287  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3288 
3289  $this->mOptions = $newOptions;
3290  $this->mOptionsLoaded = true;
3291  }
3292 
3297  public function getDatePreference() {
3298  // Important migration for old data rows
3299  if ( is_null( $this->mDatePreference ) ) {
3300  global $wgLang;
3301  $value = $this->getOption( 'date' );
3302  $map = $wgLang->getDatePreferenceMigrationMap();
3303  if ( isset( $map[$value] ) ) {
3304  $value = $map[$value];
3305  }
3306  $this->mDatePreference = $value;
3307  }
3308  return $this->mDatePreference;
3309  }
3310 
3317  public function requiresHTTPS() {
3318  global $wgSecureLogin;
3319  if ( !$wgSecureLogin ) {
3320  return false;
3321  }
3322 
3323  $https = $this->getBoolOption( 'prefershttps' );
3324  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3325  if ( $https ) {
3326  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3327  }
3328 
3329  return $https;
3330  }
3331 
3337  public function getStubThreshold() {
3338  global $wgMaxArticleSize; # Maximum article size, in Kb
3339  $threshold = $this->getIntOption( 'stubthreshold' );
3340  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3341  // If they have set an impossible value, disable the preference
3342  // so we can use the parser cache again.
3343  $threshold = 0;
3344  }
3345  return $threshold;
3346  }
3347 
3356  public function getRights() {
3357  return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
3358  }
3359 
3366  public function getGroups() {
3367  $this->load();
3368  $this->loadGroups();
3369  return array_keys( $this->mGroupMemberships );
3370  }
3371 
3379  public function getGroupMemberships() {
3380  $this->load();
3381  $this->loadGroups();
3382  return $this->mGroupMemberships;
3383  }
3384 
3392  public function getEffectiveGroups( $recache = false ) {
3393  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3394  $this->mEffectiveGroups = array_unique( array_merge(
3395  $this->getGroups(), // explicit groups
3396  $this->getAutomaticGroups( $recache ) // implicit groups
3397  ) );
3398  // Avoid PHP 7.1 warning of passing $this by reference
3399  $user = $this;
3400  // Hook for additional groups
3401  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3402  // Force reindexation of groups when a hook has unset one of them
3403  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3404  }
3405  return $this->mEffectiveGroups;
3406  }
3407 
3415  public function getAutomaticGroups( $recache = false ) {
3416  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3417  $this->mImplicitGroups = [ '*' ];
3418  if ( $this->getId() ) {
3419  $this->mImplicitGroups[] = 'user';
3420 
3421  $this->mImplicitGroups = array_unique( array_merge(
3422  $this->mImplicitGroups,
3424  ) );
3425  }
3426  if ( $recache ) {
3427  // Assure data consistency with rights/groups,
3428  // as getEffectiveGroups() depends on this function
3429  $this->mEffectiveGroups = null;
3430  }
3431  }
3432  return $this->mImplicitGroups;
3433  }
3434 
3444  public function getFormerGroups() {
3445  $this->load();
3446 
3447  if ( is_null( $this->mFormerGroups ) ) {
3448  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3449  ? wfGetDB( DB_MASTER )
3450  : wfGetDB( DB_REPLICA );
3451  $res = $db->select( 'user_former_groups',
3452  [ 'ufg_group' ],
3453  [ 'ufg_user' => $this->mId ],
3454  __METHOD__ );
3455  $this->mFormerGroups = [];
3456  foreach ( $res as $row ) {
3457  $this->mFormerGroups[] = $row->ufg_group;
3458  }
3459  }
3460 
3461  return $this->mFormerGroups;
3462  }
3463 
3468  public function getEditCount() {
3469  if ( !$this->getId() ) {
3470  return null;
3471  }
3472 
3473  if ( $this->mEditCount === null ) {
3474  /* Populate the count, if it has not been populated yet */
3475  $dbr = wfGetDB( DB_REPLICA );
3476  // check if the user_editcount field has been initialized
3477  $count = $dbr->selectField(
3478  'user', 'user_editcount',
3479  [ 'user_id' => $this->mId ],
3480  __METHOD__
3481  );
3482 
3483  if ( $count === null ) {
3484  // it has not been initialized. do so.
3485  $count = $this->initEditCountInternal( $dbr );
3486  }
3487  $this->mEditCount = $count;
3488  }
3489  return (int)$this->mEditCount;
3490  }
3491 
3503  public function addGroup( $group, $expiry = null ) {
3504  $this->load();
3505  $this->loadGroups();
3506 
3507  if ( $expiry ) {
3508  $expiry = wfTimestamp( TS_MW, $expiry );
3509  }
3510 
3511  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3512  return false;
3513  }
3514 
3515  // create the new UserGroupMembership and put it in the DB
3516  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3517  if ( !$ugm->insert( true ) ) {
3518  return false;
3519  }
3520 
3521  $this->mGroupMemberships[$group] = $ugm;
3522 
3523  // Refresh the groups caches, and clear the rights cache so it will be
3524  // refreshed on the next call to $this->getRights().
3525  $this->getEffectiveGroups( true );
3526  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3527  $this->invalidateCache();
3528 
3529  return true;
3530  }
3531 
3538  public function removeGroup( $group ) {
3539  $this->load();
3540 
3541  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3542  return false;
3543  }
3544 
3545  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3546  // delete the membership entry
3547  if ( !$ugm || !$ugm->delete() ) {
3548  return false;
3549  }
3550 
3551  $this->loadGroups();
3552  unset( $this->mGroupMemberships[$group] );
3553 
3554  // Refresh the groups caches, and clear the rights cache so it will be
3555  // refreshed on the next call to $this->getRights().
3556  $this->getEffectiveGroups( true );
3557  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3558  $this->invalidateCache();
3559 
3560  return true;
3561  }
3562 
3572  public function isRegistered() {
3573  return $this->getId() != 0;
3574  }
3575 
3580  public function isLoggedIn() {
3581  return $this->isRegistered();
3582  }
3583 
3588  public function isAnon() {
3589  return !$this->isRegistered();
3590  }
3591 
3596  public function isBot() {
3597  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3598  return true;
3599  }
3600 
3601  $isBot = false;
3602  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3603 
3604  return $isBot;
3605  }
3606 
3616  public function isAllowedAny( ...$permissions ) {
3617  return MediaWikiServices::getInstance()
3618  ->getPermissionManager()
3619  ->userHasAnyRight( $this, ...$permissions );
3620  }
3621 
3628  public function isAllowedAll( ...$permissions ) {
3629  return MediaWikiServices::getInstance()
3630  ->getPermissionManager()
3631  ->userHasAllRights( $this, ...$permissions );
3632  }
3633 
3644  public function isAllowed( $action = '' ) {
3645  return MediaWikiServices::getInstance()->getPermissionManager()
3646  ->userHasRight( $this, $action );
3647  }
3648 
3653  public function useRCPatrol() {
3654  global $wgUseRCPatrol;
3655  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3656  }
3657 
3662  public function useNPPatrol() {
3664  return (
3665  ( $wgUseRCPatrol || $wgUseNPPatrol )
3666  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3667  );
3668  }
3669 
3674  public function useFilePatrol() {
3676  return (
3677  ( $wgUseRCPatrol || $wgUseFilePatrol )
3678  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3679  );
3680  }
3681 
3687  public function getRequest() {
3688  if ( $this->mRequest ) {
3689  return $this->mRequest;
3690  }
3691 
3692  global $wgRequest;
3693  return $wgRequest;
3694  }
3695 
3704  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3705  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3706  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3707  }
3708  return false;
3709  }
3710 
3718  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3719  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3720  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3721  $this,
3722  [ $title->getSubjectPage(), $title->getTalkPage() ]
3723  );
3724  }
3725  $this->invalidateCache();
3726  }
3727 
3735  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3736  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3737  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3738  $store->removeWatch( $this, $title->getSubjectPage() );
3739  $store->removeWatch( $this, $title->getTalkPage() );
3740  }
3741  $this->invalidateCache();
3742  }
3743 
3752  public function clearNotification( &$title, $oldid = 0 ) {
3754 
3755  // Do nothing if the database is locked to writes
3756  if ( wfReadOnly() ) {
3757  return;
3758  }
3759 
3760  // Do nothing if not allowed to edit the watchlist
3761  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3762  return;
3763  }
3764 
3765  // If we're working on user's talk page, we should update the talk page message indicator
3766  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3767  // Avoid PHP 7.1 warning of passing $this by reference
3768  $user = $this;
3769  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3770  return;
3771  }
3772 
3773  // Try to update the DB post-send and only if needed...
3774  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3775  if ( !$this->getNewtalk() ) {
3776  return; // no notifications to clear
3777  }
3778 
3779  // Delete the last notifications (they stack up)
3780  $this->setNewtalk( false );
3781 
3782  // If there is a new, unseen, revision, use its timestamp
3783  if ( $oldid ) {
3784  $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3785  $oldRev = $rl->getRevisionById( $oldid, Title::READ_LATEST );
3786  if ( $oldRev ) {
3787  $newRev = $rl->getNextRevision( $oldRev );
3788  if ( $newRev ) {
3789  // TODO: actually no need to wrap in a revision,
3790  // setNewtalk really only needs a RevRecord
3791  $this->setNewtalk( true, new Revision( $newRev ) );
3792  }
3793  }
3794  }
3795  } );
3796  }
3797 
3798  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3799  return;
3800  }
3801 
3802  if ( $this->isAnon() ) {
3803  // Nothing else to do...
3804  return;
3805  }
3806 
3807  // Only update the timestamp if the page is being watched.
3808  // The query to find out if it is watched is cached both in memcached and per-invocation,
3809  // and when it does have to be executed, it can be on a replica DB
3810  // If this is the user's newtalk page, we always update the timestamp
3811  $force = '';
3812  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3813  $force = 'force';
3814  }
3815 
3816  MediaWikiServices::getInstance()->getWatchedItemStore()
3817  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3818  }
3819 
3826  public function clearAllNotifications() {
3828  // Do nothing if not allowed to edit the watchlist
3829  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3830  return;
3831  }
3832 
3833  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3834  $this->setNewtalk( false );
3835  return;
3836  }
3837 
3838  $id = $this->getId();
3839  if ( !$id ) {
3840  return;
3841  }
3842 
3843  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
3844  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
3845 
3846  // We also need to clear here the "you have new message" notification for the own
3847  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3848  }
3849 
3855  public function getExperienceLevel() {
3856  global $wgLearnerEdits,
3860 
3861  if ( $this->isAnon() ) {
3862  return false;
3863  }
3864 
3865  $editCount = $this->getEditCount();
3866  $registration = $this->getRegistration();
3867  $now = time();
3868  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3869  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3870 
3871  if ( $editCount < $wgLearnerEdits ||
3872  $registration > $learnerRegistration ) {
3873  return 'newcomer';
3874  }
3875 
3876  if ( $editCount > $wgExperiencedUserEdits &&
3877  $registration <= $experiencedRegistration
3878  ) {
3879  return 'experienced';
3880  }
3881 
3882  return 'learner';
3883  }
3884 
3893  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3894  $this->load();
3895  if ( $this->mId == 0 ) {
3896  return;
3897  }
3898 
3899  $session = $this->getRequest()->getSession();
3900  if ( $request && $session->getRequest() !== $request ) {
3901  $session = $session->sessionWithRequest( $request );
3902  }
3903  $delay = $session->delaySave();
3904 
3905  if ( !$session->getUser()->equals( $this ) ) {
3906  if ( !$session->canSetUser() ) {
3908  ->warning( __METHOD__ .
3909  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3910  );
3911  return;
3912  }
3913  $session->setUser( $this );
3914  }
3915 
3916  $session->setRememberUser( $rememberMe );
3917  if ( $secure !== null ) {
3918  $session->setForceHTTPS( $secure );
3919  }
3920 
3921  $session->persist();
3922 
3923  ScopedCallback::consume( $delay );
3924  }
3925 
3929  public function logout() {
3930  // Avoid PHP 7.1 warning of passing $this by reference
3931  $user = $this;
3932  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
3933  $this->doLogout();
3934  }
3935  }
3936 
3941  public function doLogout() {
3942  $session = $this->getRequest()->getSession();
3943  if ( !$session->canSetUser() ) {
3945  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3946  $error = 'immutable';
3947  } elseif ( !$session->getUser()->equals( $this ) ) {
3949  ->warning( __METHOD__ .
3950  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3951  );
3952  // But we still may as well make this user object anon
3953  $this->clearInstanceCache( 'defaults' );
3954  $error = 'wronguser';
3955  } else {
3956  $this->clearInstanceCache( 'defaults' );
3957  $delay = $session->delaySave();
3958  $session->unpersist(); // Clear cookies (T127436)
3959  $session->setLoggedOutTimestamp( time() );
3960  $session->setUser( new User );
3961  $session->set( 'wsUserID', 0 ); // Other code expects this
3962  $session->resetAllTokens();
3963  ScopedCallback::consume( $delay );
3964  $error = false;
3965  }
3966  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3967  'event' => 'logout',
3968  'successful' => $error === false,
3969  'status' => $error ?: 'success',
3970  ] );
3971  }
3972 
3977  public function saveSettings() {
3978  if ( wfReadOnly() ) {
3979  // @TODO: caller should deal with this instead!
3980  // This should really just be an exception.
3982  null,
3983  "Could not update user with ID '{$this->mId}'; DB is read-only."
3984  ) );
3985  return;
3986  }
3987 
3988  $this->load();
3989  if ( $this->mId == 0 ) {
3990  return; // anon
3991  }
3992 
3993  // Get a new user_touched that is higher than the old one.
3994  // This will be used for a CAS check as a last-resort safety
3995  // check against race conditions and replica DB lag.
3996  $newTouched = $this->newTouchedTimestamp();
3997 
3998  $dbw = wfGetDB( DB_MASTER );
3999  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
4000  $dbw->update( 'user',
4001  [ /* SET */
4002  'user_name' => $this->mName,
4003  'user_real_name' => $this->mRealName,
4004  'user_email' => $this->mEmail,
4005  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4006  'user_touched' => $dbw->timestamp( $newTouched ),
4007  'user_token' => strval( $this->mToken ),
4008  'user_email_token' => $this->mEmailToken,
4009  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4010  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4011  'user_id' => $this->mId,
4012  ] ), $fname
4013  );
4014 
4015  if ( !$dbw->affectedRows() ) {
4016  // Maybe the problem was a missed cache update; clear it to be safe
4017  $this->clearSharedCache( 'refresh' );
4018  // User was changed in the meantime or loaded with stale data
4019  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4020  LoggerFactory::getInstance( 'preferences' )->warning(
4021  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4022  [ 'user_id' => $this->mId, 'db_flag' => $from ]
4023  );
4024  throw new MWException( "CAS update failed on user_touched. " .
4025  "The version of the user to be saved is older than the current version."
4026  );
4027  }
4028 
4029  $dbw->update(
4030  'actor',
4031  [ 'actor_name' => $this->mName ],
4032  [ 'actor_user' => $this->mId ],
4033  $fname
4034  );
4035  } );
4036 
4037  $this->mTouched = $newTouched;
4038  $this->saveOptions();
4039 
4040  Hooks::run( 'UserSaveSettings', [ $this ] );
4041  $this->clearSharedCache( 'changed' );
4042  $this->getUserPage()->purgeSquid();
4043  }
4044 
4051  public function idForName( $flags = 0 ) {
4052  $s = trim( $this->getName() );
4053  if ( $s === '' ) {
4054  return 0;
4055  }
4056 
4057  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4058  ? wfGetDB( DB_MASTER )
4059  : wfGetDB( DB_REPLICA );
4060 
4061  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4062  ? [ 'LOCK IN SHARE MODE' ]
4063  : [];
4064 
4065  $id = $db->selectField( 'user',
4066  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4067 
4068  return (int)$id;
4069  }
4070 
4086  public static function createNew( $name, $params = [] ) {
4087  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4088  if ( isset( $params[$field] ) ) {
4089  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4090  unset( $params[$field] );
4091  }
4092  }
4093 
4094  $user = new User;
4095  $user->load();
4096  $user->setToken(); // init token
4097  if ( isset( $params['options'] ) ) {
4098  $user->mOptions = $params['options'] + (array)$user->mOptions;
4099  unset( $params['options'] );
4100  }
4101  $dbw = wfGetDB( DB_MASTER );
4102 
4103  $noPass = PasswordFactory::newInvalidPassword()->toString();
4104 
4105  $fields = [
4106  'user_name' => $name,
4107  'user_password' => $noPass,
4108  'user_newpassword' => $noPass,
4109  'user_email' => $user->mEmail,
4110  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4111  'user_real_name' => $user->mRealName,
4112  'user_token' => strval( $user->mToken ),
4113  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4114  'user_editcount' => 0,
4115  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4116  ];
4117  foreach ( $params as $name => $value ) {
4118  $fields["user_$name"] = $value;
4119  }
4120 
4121  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
4122  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4123  if ( $dbw->affectedRows() ) {
4124  $newUser = self::newFromId( $dbw->insertId() );
4125  $newUser->mName = $fields['user_name'];
4126  $newUser->updateActorId( $dbw );
4127  // Load the user from master to avoid replica lag
4128  $newUser->load( self::READ_LATEST );
4129  } else {
4130  $newUser = null;
4131  }
4132  return $newUser;
4133  } );
4134  }
4135 
4162  public function addToDatabase() {
4163  $this->load();
4164  if ( !$this->mToken ) {
4165  $this->setToken(); // init token
4166  }
4167 
4168  if ( !is_string( $this->mName ) ) {
4169  throw new RuntimeException( "User name field is not set." );
4170  }
4171 
4172  $this->mTouched = $this->newTouchedTimestamp();
4173 
4174  $dbw = wfGetDB( DB_MASTER );
4175  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
4176  $noPass = PasswordFactory::newInvalidPassword()->toString();
4177  $dbw->insert( 'user',
4178  [
4179  'user_name' => $this->mName,
4180  'user_password' => $noPass,
4181  'user_newpassword' => $noPass,
4182  'user_email' => $this->mEmail,
4183  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4184  'user_real_name' => $this->mRealName,
4185  'user_token' => strval( $this->mToken ),
4186  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4187  'user_editcount' => 0,
4188  'user_touched' => $dbw->timestamp( $this->mTouched ),
4189  ], $fname,
4190  [ 'IGNORE' ]
4191  );
4192  if ( !$dbw->affectedRows() ) {
4193  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4194  $this->mId = $dbw->selectField(
4195  'user',
4196  'user_id',
4197  [ 'user_name' => $this->mName ],
4198  $fname,
4199  [ 'LOCK IN SHARE MODE' ]
4200  );
4201  $loaded = false;
4202  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
4203  $loaded = true;
4204  }
4205  if ( !$loaded ) {
4206  throw new MWException( $fname . ": hit a key conflict attempting " .
4207  "to insert user '{$this->mName}' row, but it was not present in select!" );
4208  }
4209  return Status::newFatal( 'userexists' );
4210  }
4211  $this->mId = $dbw->insertId();
4212  self::$idCacheByName[$this->mName] = $this->mId;
4213  $this->updateActorId( $dbw );
4214 
4215  return Status::newGood();
4216  } );
4217  if ( !$status->isGood() ) {
4218  return $status;
4219  }
4220 
4221  // Clear instance cache other than user table data and actor, which is already accurate
4222  $this->clearInstanceCache();
4223 
4224  $this->saveOptions();
4225  return Status::newGood();
4226  }
4227 
4232  private function updateActorId( IDatabase $dbw ) {
4233  $dbw->insert(
4234  'actor',
4235  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4236  __METHOD__
4237  );
4238  $this->mActorId = (int)$dbw->insertId();
4239  }
4240 
4246  public function spreadAnyEditBlock() {
4247  if ( $this->isLoggedIn() && $this->getBlock() ) {
4248  return $this->spreadBlock();
4249  }
4250 
4251  return false;
4252  }
4253 
4259  protected function spreadBlock() {
4260  wfDebug( __METHOD__ . "()\n" );
4261  $this->load();
4262  if ( $this->mId == 0 ) {
4263  return false;
4264  }
4265 
4266  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
4267  if ( !$userblock ) {
4268  return false;
4269  }
4270 
4271  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4272  }
4273 
4278  public function isBlockedFromCreateAccount() {
4279  $this->getBlockedStatus();
4280  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4281  return $this->mBlock;
4282  }
4283 
4284  # T15611: if the IP address the user is trying to create an account from is
4285  # blocked with createaccount disabled, prevent new account creation there even
4286  # when the user is logged in
4287  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4288  $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
4289  null, $this->getRequest()->getIP()
4290  );
4291  }
4292  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
4293  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4294  ? $this->mBlockedFromCreateAccount
4295  : false;
4296  }
4297 
4302  public function isBlockedFromEmailuser() {
4303  $this->getBlockedStatus();
4304  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4305  }
4306 
4313  public function isBlockedFromUpload() {
4314  $this->getBlockedStatus();
4315  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4316  }
4317 
4322  public function isAllowedToCreateAccount() {
4323  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4324  }
4325 
4331  public function getUserPage() {
4332  return Title::makeTitle( NS_USER, $this->getName() );
4333  }
4334 
4340  public function getTalkPage() {
4341  $title = $this->getUserPage();
4342  return $title->getTalkPage();
4343  }
4344 
4350  public function isNewbie() {
4351  return !$this->isAllowed( 'autoconfirmed' );
4352  }
4353 
4360  public function checkPassword( $password ) {
4361  wfDeprecated( __METHOD__, '1.27' );
4362 
4363  $manager = AuthManager::singleton();
4364  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4365  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4366  [
4367  'username' => $this->getName(),
4368  'password' => $password,
4369  ]
4370  );
4371  $res = $manager->beginAuthentication( $reqs, 'null:' );
4372  switch ( $res->status ) {
4373  case AuthenticationResponse::PASS:
4374  return true;
4375  case AuthenticationResponse::FAIL:
4376  // Hope it's not a PreAuthenticationProvider that failed...
4377  LoggerFactory::getInstance( 'authentication' )
4378  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4379  return false;
4380  default:
4381  throw new BadMethodCallException(
4382  'AuthManager returned a response unsupported by ' . __METHOD__
4383  );
4384  }
4385  }
4386 
4395  public function checkTemporaryPassword( $plaintext ) {
4396  wfDeprecated( __METHOD__, '1.27' );
4397  // Can't check the temporary password individually.
4398  return $this->checkPassword( $plaintext );
4399  }
4400 
4412  public function getEditTokenObject( $salt = '', $request = null ) {
4413  if ( $this->isAnon() ) {
4414  return new LoggedOutEditToken();
4415  }
4416 
4417  if ( !$request ) {
4418  $request = $this->getRequest();
4419  }
4420  return $request->getSession()->getToken( $salt );
4421  }
4422 
4436  public function getEditToken( $salt = '', $request = null ) {
4437  return $this->getEditTokenObject( $salt, $request )->toString();
4438  }
4439 
4452  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4453  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4454  }
4455 
4466  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4467  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4468  return $this->matchEditToken( $val, $salt, $request, $maxage );
4469  }
4470 
4478  public function sendConfirmationMail( $type = 'created' ) {
4479  global $wgLang;
4480  $expiration = null; // gets passed-by-ref and defined in next line.
4481  $token = $this->confirmationToken( $expiration );
4482  $url = $this->confirmationTokenUrl( $token );
4483  $invalidateURL = $this->invalidationTokenUrl( $token );
4484  $this->saveSettings();
4485 
4486  if ( $type == 'created' || $type === false ) {
4487  $message = 'confirmemail_body';
4488  $type = 'created';
4489  } elseif ( $type === true ) {
4490  $message = 'confirmemail_body_changed';
4491  $type = 'changed';
4492  } else {
4493  // Messages: confirmemail_body_changed, confirmemail_body_set
4494  $message = 'confirmemail_body_' . $type;
4495  }
4496 
4497  $mail = [
4498  'subject' => wfMessage( 'confirmemail_subject' )->text(),
4499  'body' => wfMessage( $message,
4500  $this->getRequest()->getIP(),
4501  $this->getName(),
4502  $url,
4503  $wgLang->userTimeAndDate( $expiration, $this ),
4504  $invalidateURL,
4505  $wgLang->userDate( $expiration, $this ),
4506  $wgLang->userTime( $expiration, $this ) )->text(),
4507  'from' => null,
4508  'replyTo' => null,
4509  ];
4510  $info = [
4511  'type' => $type,
4512  'ip' => $this->getRequest()->getIP(),
4513  'confirmURL' => $url,
4514  'invalidateURL' => $invalidateURL,
4515  'expiration' => $expiration
4516  ];
4517 
4518  Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4519  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4520  }
4521 
4533  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4534  global $wgPasswordSender;
4535 
4536  if ( $from instanceof User ) {
4537  $sender = MailAddress::newFromUser( $from );
4538  } else {
4539  $sender = new MailAddress( $wgPasswordSender,
4540  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4541  }
4542  $to = MailAddress::newFromUser( $this );
4543 
4544  return UserMailer::send( $to, $sender, $subject, $body, [
4545  'replyTo' => $replyto,
4546  ] );
4547  }
4548 
4559  protected function confirmationToken( &$expiration ) {
4561  $now = time();
4562  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4563  $expiration = wfTimestamp( TS_MW, $expires );
4564  $this->load();
4565  $token = MWCryptRand::generateHex( 32 );
4566  $hash = md5( $token );
4567  $this->mEmailToken = $hash;
4568  $this->mEmailTokenExpires = $expiration;
4569  return $token;
4570  }
4571 
4577  protected function confirmationTokenUrl( $token ) {
4578  return $this->getTokenUrl( 'ConfirmEmail', $token );
4579  }
4580 
4586  protected function invalidationTokenUrl( $token ) {
4587  return $this->getTokenUrl( 'InvalidateEmail', $token );
4588  }
4589 
4604  protected function getTokenUrl( $page, $token ) {
4605  // Hack to bypass localization of 'Special:'
4606  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4607  return $title->getCanonicalURL();
4608  }
4609 
4617  public function confirmEmail() {
4618  // Check if it's already confirmed, so we don't touch the database
4619  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4620  if ( !$this->isEmailConfirmed() ) {
4622  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4623  }
4624  return true;
4625  }
4626 
4634  public function invalidateEmail() {
4635  $this->load();
4636  $this->mEmailToken = null;
4637  $this->mEmailTokenExpires = null;
4638  $this->setEmailAuthenticationTimestamp( null );
4639  $this->mEmail = '';
4640  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4641  return true;
4642  }
4643 
4648  public function setEmailAuthenticationTimestamp( $timestamp ) {
4649  $this->load();
4650  $this->mEmailAuthenticated = $timestamp;
4651  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4652  }
4653 
4659  public function canSendEmail() {
4661  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4662  return false;
4663  }
4664  $canSend = $this->isEmailConfirmed();
4665  // Avoid PHP 7.1 warning of passing $this by reference
4666  $user = $this;
4667  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4668  return $canSend;
4669  }
4670 
4676  public function canReceiveEmail() {
4677  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4678  }
4679 
4690  public function isEmailConfirmed() {
4691  global $wgEmailAuthentication;
4692  $this->load();
4693  // Avoid PHP 7.1 warning of passing $this by reference
4694  $user = $this;
4695  $confirmed = true;
4696  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4697  if ( $this->isAnon() ) {
4698  return false;
4699  }
4700  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4701  return false;
4702  }
4703  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4704  return false;
4705  }
4706  return true;
4707  }
4708 
4709  return $confirmed;
4710  }
4711 
4716  public function isEmailConfirmationPending() {
4717  global $wgEmailAuthentication;
4718  return $wgEmailAuthentication &&
4719  !$this->isEmailConfirmed() &&
4720  $this->mEmailToken &&
4721  $this->mEmailTokenExpires > wfTimestamp();
4722  }
4723 
4731  public function getRegistration() {
4732  if ( $this->isAnon() ) {
4733  return false;
4734  }
4735  $this->load();
4736  return $this->mRegistration;
4737  }
4738 
4745  public function getFirstEditTimestamp() {
4746  return $this->getEditTimestamp( true );
4747  }
4748 
4756  public function getLatestEditTimestamp() {
4757  return $this->getEditTimestamp( false );
4758  }
4759 
4767  private function getEditTimestamp( $first ) {
4768  if ( $this->getId() == 0 ) {
4769  return false; // anons
4770  }
4771  $dbr = wfGetDB( DB_REPLICA );
4772  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4773  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4774  ? 'revactor_timestamp' : 'rev_timestamp';
4775  $sortOrder = $first ? 'ASC' : 'DESC';
4776  $time = $dbr->selectField(
4777  [ 'revision' ] + $actorWhere['tables'],
4778  $tsField,
4779  [ $actorWhere['conds'] ],
4780  __METHOD__,
4781  [ 'ORDER BY' => "$tsField $sortOrder" ],
4782  $actorWhere['joins']
4783  );
4784  if ( !$time ) {
4785  return false; // no edits
4786  }
4787  return wfTimestamp( TS_MW, $time );
4788  }
4789 
4799  public static function getGroupPermissions( $groups ) {
4800  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4801  }
4802 
4812  public static function getGroupsWithPermission( $role ) {
4813  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4814  }
4815 
4831  public static function groupHasPermission( $group, $role ) {
4832  return MediaWikiServices::getInstance()->getPermissionManager()
4833  ->groupHasPermission( $group, $role );
4834  }
4835 
4853  public static function isEveryoneAllowed( $right ) {
4854  wfDeprecated( __METHOD__, '1.34' );
4855  return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4856  }
4857 
4864  public static function getAllGroups() {
4866  return array_values( array_diff(
4867  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4868  self::getImplicitGroups()
4869  ) );
4870  }
4871 
4879  public static function getAllRights() {
4880  wfDeprecated( __METHOD__, '1.34' );
4881  return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4882  }
4883 
4890  public static function getImplicitGroups() {
4891  global $wgImplicitGroups;
4892  return $wgImplicitGroups;
4893  }
4894 
4904  public static function changeableByGroup( $group ) {
4906 
4907  $groups = [
4908  'add' => [],
4909  'remove' => [],
4910  'add-self' => [],
4911  'remove-self' => []
4912  ];
4913 
4914  if ( empty( $wgAddGroups[$group] ) ) {
4915  // Don't add anything to $groups
4916  } elseif ( $wgAddGroups[$group] === true ) {
4917  // You get everything
4918  $groups['add'] = self::getAllGroups();
4919  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4920  $groups['add'] = $wgAddGroups[$group];
4921  }
4922 
4923  // Same thing for remove
4924  if ( empty( $wgRemoveGroups[$group] ) ) {
4925  // Do nothing
4926  } elseif ( $wgRemoveGroups[$group] === true ) {
4927  $groups['remove'] = self::getAllGroups();
4928  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4929  $groups['remove'] = $wgRemoveGroups[$group];
4930  }
4931 
4932  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4933  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4934  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4935  if ( is_int( $key ) ) {
4936  $wgGroupsAddToSelf['user'][] = $value;
4937  }
4938  }
4939  }
4940 
4941  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4942  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4943  if ( is_int( $key ) ) {
4944  $wgGroupsRemoveFromSelf['user'][] = $value;
4945  }
4946  }
4947  }
4948 
4949  // Now figure out what groups the user can add to him/herself
4950  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4951  // Do nothing
4952  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4953  // No idea WHY this would be used, but it's there
4954  $groups['add-self'] = self::getAllGroups();
4955  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4956  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4957  }
4958 
4959  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4960  // Do nothing
4961  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4962  $groups['remove-self'] = self::getAllGroups();
4963  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4964  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4965  }
4966 
4967  return $groups;
4968  }
4969 
4977  public function changeableGroups() {
4978  if ( $this->isAllowed( 'userrights' ) ) {
4979  // This group gives the right to modify everything (reverse-
4980  // compatibility with old "userrights lets you change
4981  // everything")
4982  // Using array_merge to make the groups reindexed
4983  $all = array_merge( self::getAllGroups() );
4984  return [
4985  'add' => $all,
4986  'remove' => $all,
4987  'add-self' => [],
4988  'remove-self' => []
4989  ];
4990  }
4991 
4992  // Okay, it's not so simple, we will have to go through the arrays
4993  $groups = [
4994  'add' => [],
4995  'remove' => [],
4996  'add-self' => [],
4997  'remove-self' => []
4998  ];
4999  $addergroups = $this->getEffectiveGroups();
5000 
5001  foreach ( $addergroups as $addergroup ) {
5002  $groups = array_merge_recursive(
5003  $groups, $this->changeableByGroup( $addergroup )
5004  );
5005  $groups['add'] = array_unique( $groups['add'] );
5006  $groups['remove'] = array_unique( $groups['remove'] );
5007  $groups['add-self'] = array_unique( $groups['add-self'] );
5008  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5009  }
5010  return $groups;
5011  }
5012 
5016  public function incEditCount() {
5017  if ( $this->isAnon() ) {
5018  return; // sanity
5019  }
5020 
5022  new UserEditCountUpdate( $this, 1 ),
5024  );
5025  }
5026 
5032  public function setEditCountInternal( $count ) {
5033  $this->mEditCount = $count;
5034  }
5035 
5043  public function initEditCountInternal( IDatabase $dbr ) {
5044  // Pull from a replica DB to be less cruel to servers
5045  // Accuracy isn't the point anyway here
5046  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5047  $count = (int)$dbr->selectField(
5048  [ 'revision' ] + $actorWhere['tables'],
5049  'COUNT(*)',
5050  [ $actorWhere['conds'] ],
5051  __METHOD__,
5052  [],
5053  $actorWhere['joins']
5054  );
5055 
5056  $dbw = wfGetDB( DB_MASTER );
5057  $dbw->update(
5058  'user',
5059  [ 'user_editcount' => $count ],
5060  [
5061  'user_id' => $this->getId(),
5062  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5063  ],
5064  __METHOD__
5065  );
5066 
5067  return $count;
5068  }
5069 
5077  public static function getRightDescription( $right ) {
5078  $key = "right-$right";
5079  $msg = wfMessage( $key );
5080  return $msg->isDisabled() ? $right : $msg->text();
5081  }
5082 
5090  public static function getGrantName( $grant ) {
5091  $key = "grant-$grant";
5092  $msg = wfMessage( $key );
5093  return $msg->isDisabled() ? $grant : $msg->text();
5094  }
5095 
5116  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5117  return true; // disabled
5118  }
5119 
5128  public function addNewUserLogEntryAutoCreate() {
5129  wfDeprecated( __METHOD__, '1.27' );
5130  $this->addNewUserLogEntry( 'autocreate' );
5131 
5132  return true;
5133  }
5134 
5140  protected function loadOptions( $data = null ) {
5141  $this->load();
5142 
5143  if ( $this->mOptionsLoaded ) {
5144  return;
5145  }
5146 
5147  $this->mOptions = self::getDefaultOptions();
5148 
5149  if ( !$this->getId() ) {
5150  // For unlogged-in users, load language/variant options from request.
5151  // There's no need to do it for logged-in users: they can set preferences,
5152  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5153  // so don't override user's choice (especially when the user chooses site default).
5154  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5155  $this->mOptions['variant'] = $variant;
5156  $this->mOptions['language'] = $variant;
5157  $this->mOptionsLoaded = true;
5158  return;
5159  }
5160 
5161  // Maybe load from the object
5162  if ( !is_null( $this->mOptionOverrides ) ) {
5163  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5164  foreach ( $this->mOptionOverrides as $key => $value ) {
5165  $this->mOptions[$key] = $value;
5166  }
5167  } else {
5168  if ( !is_array( $data ) ) {
5169  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5170  // Load from database
5171  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5172  ? wfGetDB( DB_MASTER )
5173  : wfGetDB( DB_REPLICA );
5174 
5175  $res = $dbr->select(
5176  'user_properties',
5177  [ 'up_property', 'up_value' ],
5178  [ 'up_user' => $this->getId() ],
5179  __METHOD__
5180  );
5181 
5182  $this->mOptionOverrides = [];
5183  $data = [];
5184  foreach ( $res as $row ) {
5185  // Convert '0' to 0. PHP's boolean conversion considers them both
5186  // false, but e.g. JavaScript considers the former as true.
5187  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5188  // and convert all values here.
5189  if ( $row->up_value === '0' ) {
5190  $row->up_value = 0;
5191  }
5192  $data[$row->up_property] = $row->up_value;
5193  }
5194  }
5195 
5196  foreach ( $data as $property => $value ) {
5197  $this->mOptionOverrides[$property] = $value;
5198  $this->mOptions[$property] = $value;
5199  }
5200  }
5201 
5202  // Replace deprecated language codes
5203  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5204  $this->mOptions['language']
5205  );
5206 
5207  $this->mOptionsLoaded = true;
5208 
5209  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5210  }
5211 
5217  protected function saveOptions() {
5218  $this->loadOptions();
5219 
5220  // Not using getOptions(), to keep hidden preferences in database
5221  $saveOptions = $this->mOptions;
5222 
5223  // Allow hooks to abort, for instance to save to a global profile.
5224  // Reset options to default state before saving.
5225  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5226  return;
5227  }
5228 
5229  $userId = $this->getId();
5230 
5231  $insert_rows = []; // all the new preference rows
5232  foreach ( $saveOptions as $key => $value ) {
5233  // Don't bother storing default values
5234  $defaultOption = self::getDefaultOption( $key );
5235  if ( ( $defaultOption === null && $value !== false && $value !== null )
5236  || $value != $defaultOption
5237  ) {
5238  $insert_rows[] = [
5239  'up_user' => $userId,
5240  'up_property' => $key,
5241  'up_value' => $value,
5242  ];
5243  }
5244  }
5245 
5246  $dbw = wfGetDB( DB_MASTER );
5247 
5248  $res = $dbw->select( 'user_properties',
5249  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5250 
5251  // Find prior rows that need to be removed or updated. These rows will
5252  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5253  $keysDelete = [];
5254  foreach ( $res as $row ) {
5255  if ( !isset( $saveOptions[$row->up_property] )
5256  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5257  ) {
5258  $keysDelete[] = $row->up_property;
5259  }
5260  }
5261 
5262  if ( count( $keysDelete ) ) {
5263  // Do the DELETE by PRIMARY KEY for prior rows.
5264  // In the past a very large portion of calls to this function are for setting
5265  // 'rememberpassword' for new accounts (a preference that has since been removed).
5266  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5267  // caused gap locks on [max user ID,+infinity) which caused high contention since
5268  // updates would pile up on each other as they are for higher (newer) user IDs.
5269  // It might not be necessary these days, but it shouldn't hurt either.
5270  $dbw->delete( 'user_properties',
5271  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5272  }
5273  // Insert the new preference rows
5274  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5275  }
5276 
5283  public static function selectFields() {
5284  wfDeprecated( __METHOD__, '1.31' );
5285  return [
5286  'user_id',
5287  'user_name',
5288  'user_real_name',
5289  'user_email',
5290  'user_touched',
5291  'user_token',
5292  'user_email_authenticated',
5293  'user_email_token',
5294  'user_email_token_expires',
5295  'user_registration',
5296  'user_editcount',
5297  ];
5298  }
5299 
5309  public static function getQueryInfo() {
5310  $ret = [
5311  'tables' => [ 'user', 'user_actor' => 'actor' ],
5312  'fields' => [
5313  'user_id',
5314  'user_name',
5315  'user_real_name',
5316  'user_email',
5317  'user_touched',
5318  'user_token',
5319  'user_email_authenticated',
5320  'user_email_token',
5321  'user_email_token_expires',
5322  'user_registration',
5323  'user_editcount',
5324  'user_actor.actor_id',
5325  ],
5326  'joins' => [
5327  'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
5328  ],
5329  ];
5330 
5331  return $ret;
5332  }
5333 
5341  static function newFatalPermissionDeniedStatus( $permission ) {
5342  global $wgLang;
5343 
5344  $groups = [];
5345  foreach ( MediaWikiServices::getInstance()
5347  ->getGroupsWithPermission( $permission ) as $group ) {
5348  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5349  }
5350 
5351  if ( $groups ) {
5352  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5353  }
5354 
5355  return Status::newFatal( 'badaccess-group0' );
5356  }
5357 
5367  public function getInstanceForUpdate() {
5368  if ( !$this->getId() ) {
5369  return null; // anon
5370  }
5371 
5372  $user = self::newFromId( $this->getId() );
5373  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5374  return null;
5375  }
5376 
5377  return $user;
5378  }
5379 
5387  public function equals( UserIdentity $user ) {
5388  // XXX it's not clear whether central ID providers are supposed to obey this
5389  return $this->getName() === $user->getName();
5390  }
5391 
5397  public function isAllowUsertalk() {
5398  return $this->mAllowUsertalk;
5399  }
5400 
5401 }
static array null $defOpt
Definition: User.php:1685
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition: User.php:5043
getEmail()
Get the user&#39;s e-mail address.
Definition: User.php:2856
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
isHidden()
Check if user account is hidden.
Definition: User.php:2241
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
static getMembership( $userId, $group, IDatabase $db=null)
Returns a UserGroupMembership object that pertains to the given user and group, or false if the user ...
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2719
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:125
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:67
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
setRealName( $str)
Set the user&#39;s real name.
Definition: User.php:2958
$wgMaxArticleSize
Maximum article size in kilobytes.
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1303
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2420
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1657
string $mDatePreference
Definition: User.php:181
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4350
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:869
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:104
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3718
isAllowedAny(... $permissions)
Check if user is allowed to access a feature / make an action.
Definition: User.php:3616
$context
Definition: load.php:45
$wgMaxNameChars
Maximum number of bytes in username.
UserGroupMembership [] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:146
affectedRows()
Get the number of rows affected by the last write query.
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
loadFromSession()
Load user data from the session.
Definition: User.php:1314
const NS_MAIN
Definition: Defines.php:60
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4904
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4322
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
$success
logout()
Log this user out.
Definition: User.php:3929
clearNotification(&$title, $oldid=0)
Clear the user&#39;s notification timestamp for the given title.
Definition: User.php:3752
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4799
static getImplicitGroups()
Get a list of implicit groups TODO: Should we deprecate this? It&#39;s trivial, but we don&#39;t want to enco...
Definition: User.php:4890
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
saveSettings()
Save this user&#39;s settings into the database.
Definition: User.php:3977
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2278
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Definition: UserMailer.php:115
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4745
AbstractBlock null $mBlock
Definition: User.php:225
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4853
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:951
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2650
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:73
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4302
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3392
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:56
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2635
Handles increment the edit count for a given set of users.
AbstractBlock $mGlobalBlock
Definition: User.php:204
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2179
setName( $str)
Set the user name.
Definition: User.php:2315
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:2973
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2146
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:160
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:132
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
& __get( $name)
Definition: User.php:261
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:297
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:58
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:617
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2121
static getWikiIdFromDbDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:269
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3855
__toString()
Definition: User.php:257
Exception thrown when an actor can&#39;t be created.
getRealName()
Get the user&#39;s real name.
Definition: User.php:2946
$wgSecureLogin
This is to let user authenticate using https when they come from http.
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2480
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user&#39;s account.
Definition: User.php:4533
Check if a user&#39;s password complies with any password policies that apply to that user...
checkAndSetTouched()
Bump user_touched if it didn&#39;t change since this object was loaded.
Definition: User.php:1619
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4977
clearAllNotifications()
Resets all of the given user&#39;s page-change notification timestamps.
Definition: User.php:3826
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4466
static string null $defOptLang
Definition: User.php:1687
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3580
static getLocalClusterInstance()
Get the main cluster-local cache object.
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
$wgGroupsRemoveFromSelf
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2840
insertId()
Get the inserted value of an auto-increment row.
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:889
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:671
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition: User.php:4313
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4395
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
$wgHiddenPrefs
An array of preferences to not show for the user.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object...
Definition: User.php:5309
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:931
array $mFormerGroups
Definition: User.php:202
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2684
$wgRevokePermissions
Permission keys revoked from users in each group.
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:575
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3061
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2193
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition: User.php:4767
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1830
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:2110
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Value object representing a logged-out user&#39;s edit token.
const DB_MASTER
Definition: defines.php:26
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition: IP.php:729
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user&#39;s session (e.g.
Definition: User.php:3893
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2287
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5283
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4232
string $mName
Cache variables.
Definition: User.php:121
string $mRegistration
Cache variables.
Definition: User.php:142
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
int $mEditCount
Cache variables.
Definition: User.php:144
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2770
int null $mActorId
Cache variables.
Definition: User.php:123
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3735
static purge( $dbDomain, $userId)
Definition: User.php:439
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4360
getTitleKey()
Get the user&#39;s name escaped by underscores.
Definition: User.php:2374
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4864
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3144
$wgGroupPermissions
Permission keys given to users in each group.
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1060
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:2165
Interface for objects representing user identity.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
string $mEmailTokenExpires
Cache variables.
Definition: User.php:140
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4879
isEmailConfirmed()
Is this user&#39;s e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4690
int bool $mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:179
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:4436
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1198
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
getPermissionManager()
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4278
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:409
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:879
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2495
string $mEmailToken
Cache variables.
Definition: User.php:138
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it&#39;s empty (and savin...
Definition: User.php:3082
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1550
$wgPasswordPolicy
Password policy for the wiki.
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4731
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects...
Definition: User.php:3379
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
wfReadOnly()
Check whether the wiki is in read-only mode.
$wgLang
Definition: Setup.php:858
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5077
static newMigration()
Static constructor.
static getMain()
Get the RequestContext object associated with the main request.
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations, but may be used internally either by batch processes or by user accounts which have already been created.
Definition: User.php:1100
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4577
string int $mBlockedby
Definition: User.php:188
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:461
static newFromIDs( $ids)
Definition: UserArray.php:42
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5128
$wgExperiencedUserEdits
Specify the difference engine to use.
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1130
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4812
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:139
getBoolOption( $oname)
Get the user&#39;s current setting for a given option, as a boolean value.
Definition: User.php:3033
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1601
string $mBlockreason
TODO: This should be removed when User::BlockedFor and AbstractBlock::getReason are hard deprecated...
Definition: User.php:196
isAnon()
Get whether the user is anonymous.
Definition: User.php:3588
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5397
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1765
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4604
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2592
static int [] $idCacheByName
Definition: User.php:237
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1891
Stores a single person&#39;s name and email address.
Definition: MailAddress.php:32
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5090
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3110
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:234
$wgImplicitGroups
Implicit groups, aren&#39;t shown on Special:Listusers or somewhere else.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
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:4412
doLogout()
Clear the user&#39;s session, and reset the instance cache.
Definition: User.php:3941
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:324
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5387
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:473
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4716
incEditCount()
Schedule a deferred update to update the user&#39;s edit count.
Definition: User.php:5016
System blocks are temporary blocks that are created on enforcement (e.g.
Definition: SystemBlock.php:33
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2573
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1843
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
getOptionKinds(IContextSource $context, $options=null)
Return an associative array mapping preferences keys to the kind of a preference they&#39;re used for...
Definition: User.php:3167
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1351
$wgProxyList
Big list of banned IP addresses.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:130
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4659
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4756
array $mEffectiveGroups
Definition: User.php:198
$cache
Definition: mcc.php:33
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2732
const IGNORE_USER_RIGHTS
Definition: User.php:83
string $mEmailAuthenticated
Cache variables.
Definition: User.php:136
static logException( $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log an exception to the exception log (if enabled).
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3644
array $wgLearnerEdits
The following variables define 3 user experience levels:
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:173
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3704
WebRequest $mRequest
Definition: User.php:218
$wgRemoveGroups
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format...
Definition: MWCryptRand.php:36
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1293
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:37
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:1026
trackBlockWithCookie()
Set the &#39;BlockID&#39; cookie depending on block type and user authentication status.
Definition: User.php:1338
requiresHTTPS()
Determine based on the wiki configuration and the user&#39;s options, whether this user must be over HTTP...
Definition: User.php:3317
$wgRateLimits
Simple rate limiter options to brake edit floods.
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2137
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3572
array $mImplicitGroups
Definition: User.php:200
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1516
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they&#39;ve successfully logged in from...
Definition: User.php:4259
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
static hasFlags( $bitfield, $flags)
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
$wgExperiencedUserMemberSince
Specify the difference engine to use.
isBot()
Definition: User.php:3596
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4648
__set( $name, $value)
Definition: User.php:278
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:5032
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history...
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:592
string $mToken
Cache variables.
Definition: User.php:134
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4831
getToken( $forceCreation=true)
Get the user&#39;s current token.
Definition: User.php:2797
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:909
static resetGetDefaultOptionsForTestsOnly()
Reset the process cache of default user options.
Definition: User.php:1695
static newInvalidPassword()
Create an InvalidPassword.
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3653
$wgPasswordSender
Sender email address for e-mail notifications.
appliesToRight( $right)
Determine whether the block prevents a given right.
$wgLearnerMemberSince
Specify the difference engine to use.
array $mOptions
Definition: User.php:215
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:231
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1751
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
invalidateEmail()
Invalidate the user&#39;s e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4634
loadGroups()
Load the groups from the database if they aren&#39;t already loaded.
Definition: User.php:1526
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5217
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:610
static string [] $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:92
isIPRange()
Is the user an IP range?
Definition: User.php:962
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:584
static getCurrentWikiDbDomain()
Definition: WikiMap.php:293
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3337
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3687
string $mEmail
Cache variables.
Definition: User.php:128
getNewtalk()
Check if the user has new messages.
Definition: User.php:2382
bool $mLocked
Definition: User.php:206
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:560
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1404
$wgUseEnotif
Definition: Setup.php:435
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3366
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:250
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date...
getIntOption( $oname, $defaultOverride=0)
Get the user&#39;s current setting for a given option, as an integer value.
Definition: User.php:3045
idForName( $flags=0)
If only this user&#39;s username is known, and it exists, return the user ID.
Definition: User.php:4051
getOptions( $flags=0)
Get all user&#39;s options.
Definition: User.php:3002
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they&#39;ve successfully logged in from...
Definition: User.php:4246
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:313
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:977
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3674
getId()
Get the user&#39;s ID.
Definition: User.php:2258
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4452
getEmailAuthenticationTimestamp()
Get the timestamp of the user&#39;s e-mail authentication.
Definition: User.php:2866
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1964
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5140
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4162
getActorId(IDatabase $dbw=null)
Get the user&#39;s actor ID.
Definition: User.php:2326
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:717
bool $mAllowUsertalk
Definition: User.php:228
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2618
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
insert( $table, $rows, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4617
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5341
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
bool $mHideName
Definition: User.php:213
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:695
getEditCount()
Get the user&#39;s edit count.
Definition: User.php:3468
getUserPage()
Get this user&#39;s personal page title.
Definition: User.php:4331
setNewtalk( $val, $curRev=null)
Update the &#39;You have new messages!&#39; status.
Definition: User.php:2540
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5367
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3662
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3297
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2166
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:655
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user&#39;s give...
Definition: User.php:4478
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2453
static string [] false $reservedUsernames
Cache for self::isUsableName()
Definition: User.php:114
isLocked()
Check if user account is locked.
Definition: User.php:2226
makeGlobalKey( $class,... $components)
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:729
string $mHash
Definition: User.php:190
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2520
bool $mOptionsLoaded
Whether the cache variables have been loaded.
Definition: User.php:155
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4676
const DB_REPLICA
Definition: defines.php:25
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS...
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4586
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1156
setEmailWithConfirmation( $str)
Set the user&#39;s e-mail address and a confirmation mail if needed.
Definition: User.php:2893
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4086
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3415
array $mOptionOverrides
Cache variables.
Definition: User.php:148
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:536
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1707
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:61
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2706
string $mRealName
Cache variables.
Definition: User.php:125
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3503
isSafeToLoad()
Test if it&#39;s safe to load this User object.
Definition: User.php:307
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:65
getCacheKey(WANObjectCache $cache)
Definition: User.php:450
isAllowedAll(... $permissions)
Definition: User.php:3628
setEmail( $str)
Set the user&#39;s e-mail address.
Definition: User.php:2876
const NS_USER_TALK
Definition: Defines.php:63
static array $languagesWithVariants
languages supporting variants
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5116
getTalkPage()
Get this user&#39;s talk page title.
Definition: User.php:4340
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition: User.php:1254
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1939
static singleton()
Definition: UserCache.php:34
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3538
return true
Definition: router.php:92
const CHECK_USER_RIGHTS
Definition: User.php:78
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
getRights()
Get the permissions this user has.
Definition: User.php:3356
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3444
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2157
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
Base class for the more common types of database errors.
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3252
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:758
static isExternal( $username)
Tells whether the username is external or not.
int $mId
Cache variables.
Definition: User.php:119
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4559
getTouched()
Get the user touched timestamp.
Definition: User.php:2662
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:317