MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
34 use Wikimedia\Assert\Assert;
35 use Wikimedia\IPSet;
36 use Wikimedia\IPUtils;
40 use Wikimedia\ScopedCallback;
41 
52 class User implements IDBAccessObject, UserIdentity {
53 
57  const TOKEN_LENGTH = 32;
58 
62  const INVALID_TOKEN = '*** INVALID ***';
63 
68  const VERSION = 14;
69 
75 
79  const CHECK_USER_RIGHTS = true;
80 
84  const IGNORE_USER_RIGHTS = false;
85 
93  protected static $mCacheVars = [
94  // user table
95  'mId',
96  'mName',
97  'mRealName',
98  'mEmail',
99  'mTouched',
100  'mToken',
101  'mEmailAuthenticated',
102  'mEmailToken',
103  'mEmailTokenExpires',
104  'mRegistration',
105  'mEditCount',
106  // user_groups table
107  'mGroupMemberships',
108  // user_properties table
109  'mOptionOverrides',
110  // actor table
111  'mActorId',
112  ];
113 
115  private static $reservedUsernames = false;
116 
118  // @{
120  public $mId;
122  public $mName;
124  protected $mActorId;
126  public $mRealName;
127 
129  public $mEmail;
131  public $mTouched;
133  protected $mQuickTouched;
135  protected $mToken;
139  protected $mEmailToken;
143  protected $mRegistration;
145  protected $mEditCount;
149  protected $mOptionOverrides;
150  // @}
151 
152  // @{
157 
161  protected $mLoadedItems = [];
162  // @}
163 
174  public $mFrom;
175 
180  protected $mNewtalk;
182  protected $mDatePreference;
189  public $mBlockedby;
191  protected $mHash;
197  protected $mBlockreason;
199  protected $mEffectiveGroups;
201  protected $mImplicitGroups;
203  protected $mFormerGroups;
205  protected $mGlobalBlock;
207  protected $mLocked;
214  public $mHideName;
216  public $mOptions;
217 
219  private $mRequest;
220 
226  public $mBlock;
227 
229  protected $mAllowUsertalk;
230 
232  private $mBlockedFromCreateAccount = false;
233 
235  protected $queryFlagsUsed = self::READ_NORMAL;
236 
238  public static $idCacheByName = [];
239 
251  public function __construct() {
252  $this->clearInstanceCache( 'defaults' );
253  }
254 
258  public function __toString() {
259  return (string)$this->getName();
260  }
261 
262  public function &__get( $name ) {
263  // A shortcut for $mRights deprecation phase
264  if ( $name === 'mRights' ) {
265  $copy = $this->getRights();
266  return $copy;
267  } elseif ( !property_exists( $this, $name ) ) {
268  // T227688 - do not break $u->foo['bar'] = 1
269  wfLogWarning( 'tried to get non-existent property' );
270  $this->$name = null;
271  return $this->$name;
272  } else {
273  wfLogWarning( 'tried to get non-visible property' );
274  $null = null;
275  return $null;
276  }
277  }
278 
279  public function __set( $name, $value ) {
280  // A shortcut for $mRights deprecation phase, only known legitimate use was for
281  // testing purposes, other uses seem bad in principle
282  if ( $name === 'mRights' ) {
283  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
284  $this,
285  $value === null ? [] : $value
286  );
287  } elseif ( !property_exists( $this, $name ) ) {
288  $this->$name = $value;
289  } else {
290  wfLogWarning( 'tried to set non-visible property' );
291  }
292  }
293 
308  public function isSafeToLoad() {
309  global $wgFullyInitialised;
310 
311  // The user is safe to load if:
312  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
313  // * mLoadedItems === true (already loaded)
314  // * mFrom !== 'session' (sessions not involved at all)
315 
316  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
317  $this->mLoadedItems === true || $this->mFrom !== 'session';
318  }
319 
325  public function load( $flags = self::READ_NORMAL ) {
326  global $wgFullyInitialised;
327 
328  if ( $this->mLoadedItems === true ) {
329  return;
330  }
331 
332  // Set it now to avoid infinite recursion in accessors
333  $oldLoadedItems = $this->mLoadedItems;
334  $this->mLoadedItems = true;
335  $this->queryFlagsUsed = $flags;
336 
337  // If this is called too early, things are likely to break.
338  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
340  ->warning( 'User::loadFromSession called before the end of Setup.php', [
341  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
342  ] );
343  $this->loadDefaults();
344  $this->mLoadedItems = $oldLoadedItems;
345  return;
346  }
347 
348  switch ( $this->mFrom ) {
349  case 'defaults':
350  $this->loadDefaults();
351  break;
352  case 'id':
353  // Make sure this thread sees its own changes, if the ID isn't 0
354  if ( $this->mId != 0 ) {
355  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
356  if ( $lb->hasOrMadeRecentMasterChanges() ) {
357  $flags |= self::READ_LATEST;
358  $this->queryFlagsUsed = $flags;
359  }
360  }
361 
362  $this->loadFromId( $flags );
363  break;
364  case 'actor':
365  case 'name':
366  // Make sure this thread sees its own changes
367  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
368  if ( $lb->hasOrMadeRecentMasterChanges() ) {
369  $flags |= self::READ_LATEST;
370  $this->queryFlagsUsed = $flags;
371  }
372 
373  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
374  $row = wfGetDB( $index )->selectRow(
375  'actor',
376  [ 'actor_id', 'actor_user', 'actor_name' ],
377  $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
378  __METHOD__,
379  $options
380  );
381 
382  if ( !$row ) {
383  // Ugh.
384  $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
385  } elseif ( $row->actor_user ) {
386  $this->mId = $row->actor_user;
387  $this->loadFromId( $flags );
388  } else {
389  $this->loadDefaults( $row->actor_name, $row->actor_id );
390  }
391  break;
392  case 'session':
393  if ( !$this->loadFromSession() ) {
394  // Loading from session failed. Load defaults.
395  $this->loadDefaults();
396  }
397  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
398  break;
399  default:
400  throw new UnexpectedValueException(
401  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
402  }
403  }
404 
410  public function loadFromId( $flags = self::READ_NORMAL ) {
411  if ( $this->mId == 0 ) {
412  // Anonymous users are not in the database (don't need cache)
413  $this->loadDefaults();
414  return false;
415  }
416 
417  // Try cache (unless this needs data from the master DB).
418  // NOTE: if this thread called saveSettings(), the cache was cleared.
419  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
420  if ( $latest ) {
421  if ( !$this->loadFromDatabase( $flags ) ) {
422  // Can't load from ID
423  return false;
424  }
425  } else {
426  $this->loadFromCache();
427  }
428 
429  $this->mLoadedItems = true;
430  $this->queryFlagsUsed = $flags;
431 
432  return true;
433  }
434 
440  public static function purge( $dbDomain, $userId ) {
441  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
442  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
443  $cache->delete( $key );
444  }
445 
451  protected function getCacheKey( WANObjectCache $cache ) {
452  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
453 
454  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
455  }
456 
463  $id = $this->getId();
464 
465  return $id ? [ $this->getCacheKey( $cache ) ] : [];
466  }
467 
474  protected function loadFromCache() {
475  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
476  $data = $cache->getWithSetCallback(
477  $this->getCacheKey( $cache ),
478  $cache::TTL_HOUR,
479  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
480  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
481  wfDebug( "User: cache miss for user {$this->mId}\n" );
482 
483  $this->loadFromDatabase( self::READ_NORMAL );
484  $this->loadGroups();
485  $this->loadOptions();
486 
487  $data = [];
488  foreach ( self::$mCacheVars as $name ) {
489  $data[$name] = $this->$name;
490  }
491 
492  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
493 
494  // if a user group membership is about to expire, the cache needs to
495  // expire at that time (T163691)
496  foreach ( $this->mGroupMemberships as $ugm ) {
497  if ( $ugm->getExpiry() ) {
498  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
499  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
500  $ttl = $secondsUntilExpiry;
501  }
502  }
503  }
504 
505  return $data;
506  },
507  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
508  );
509 
510  // Restore from cache
511  foreach ( self::$mCacheVars as $name ) {
512  $this->$name = $data[$name];
513  }
514 
515  return true;
516  }
517 
519  // @{
520 
537  public static function newFromName( $name, $validate = 'valid' ) {
538  if ( $validate === true ) {
539  $validate = 'valid';
540  }
541  $name = self::getCanonicalName( $name, $validate );
542  if ( $name === false ) {
543  return false;
544  }
545 
546  // Create unloaded user object
547  $u = new User;
548  $u->mName = $name;
549  $u->mFrom = 'name';
550  $u->setItemLoaded( 'name' );
551 
552  return $u;
553  }
554 
561  public static function newFromId( $id ) {
562  $u = new User;
563  $u->mId = $id;
564  $u->mFrom = 'id';
565  $u->setItemLoaded( 'id' );
566  return $u;
567  }
568 
576  public static function newFromActorId( $id ) {
577  $u = new User;
578  $u->mActorId = $id;
579  $u->mFrom = 'actor';
580  $u->setItemLoaded( 'actor' );
581  return $u;
582  }
583 
593  public static function newFromIdentity( UserIdentity $identity ) {
594  if ( $identity instanceof User ) {
595  return $identity;
596  }
597 
598  return self::newFromAnyId(
599  $identity->getId() === 0 ? null : $identity->getId(),
600  $identity->getName() === '' ? null : $identity->getName(),
601  $identity->getActorId() === 0 ? null : $identity->getActorId()
602  );
603  }
604 
618  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
619  // Stop-gap solution for the problem described in T222212.
620  // Force the User ID and Actor ID to zero for users loaded from the database
621  // of another wiki, to prevent subtle data corruption and confusing failure modes.
622  if ( $dbDomain !== false ) {
623  $userId = 0;
624  $actorId = 0;
625  }
626 
627  $user = new User;
628  $user->mFrom = 'defaults';
629 
630  if ( $actorId !== null ) {
631  $user->mActorId = (int)$actorId;
632  if ( $user->mActorId !== 0 ) {
633  $user->mFrom = 'actor';
634  }
635  $user->setItemLoaded( 'actor' );
636  }
637 
638  if ( $userName !== null && $userName !== '' ) {
639  $user->mName = $userName;
640  $user->mFrom = 'name';
641  $user->setItemLoaded( 'name' );
642  }
643 
644  if ( $userId !== null ) {
645  $user->mId = (int)$userId;
646  if ( $user->mId !== 0 ) {
647  $user->mFrom = 'id';
648  }
649  $user->setItemLoaded( 'id' );
650  }
651 
652  if ( $user->mFrom === 'defaults' ) {
653  throw new InvalidArgumentException(
654  'Cannot create a user with no name, no ID, and no actor ID'
655  );
656  }
657 
658  return $user;
659  }
660 
672  public static function newFromConfirmationCode( $code, $flags = 0 ) {
673  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
674  ? wfGetDB( DB_MASTER )
675  : wfGetDB( DB_REPLICA );
676 
677  $id = $db->selectField(
678  'user',
679  'user_id',
680  [
681  'user_email_token' => md5( $code ),
682  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
683  ]
684  );
685 
686  return $id ? self::newFromId( $id ) : null;
687  }
688 
696  public static function newFromSession( WebRequest $request = null ) {
697  $user = new User;
698  $user->mFrom = 'session';
699  $user->mRequest = $request;
700  return $user;
701  }
702 
718  public static function newFromRow( $row, $data = null ) {
719  $user = new User;
720  $user->loadFromRow( $row, $data );
721  return $user;
722  }
723 
759  public static function newSystemUser( $name, $options = [] ) {
760  $options += [
761  'validate' => 'valid',
762  'create' => true,
763  'steal' => false,
764  ];
765 
766  $name = self::getCanonicalName( $name, $options['validate'] );
767  if ( $name === false ) {
768  return null;
769  }
770 
771  $dbr = wfGetDB( DB_REPLICA );
772  $userQuery = self::getQueryInfo();
773  $row = $dbr->selectRow(
774  $userQuery['tables'],
775  $userQuery['fields'],
776  [ 'user_name' => $name ],
777  __METHOD__,
778  [],
779  $userQuery['joins']
780  );
781  if ( !$row ) {
782  // Try the master database...
783  $dbw = wfGetDB( DB_MASTER );
784  $row = $dbw->selectRow(
785  $userQuery['tables'],
786  $userQuery['fields'],
787  [ 'user_name' => $name ],
788  __METHOD__,
789  [],
790  $userQuery['joins']
791  );
792  }
793 
794  if ( !$row ) {
795  // No user. Create it?
796  if ( !$options['create'] ) {
797  // No.
798  return null;
799  }
800 
801  // If it's a reserved user that had an anonymous actor created for it at
802  // some point, we need special handling.
803  if ( !self::isValidUserName( $name ) || self::isUsableName( $name ) ) {
804  // Not reserved, so just create it.
805  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
806  }
807 
808  // It is reserved. Check for an anonymous actor row.
809  $dbw = wfGetDB( DB_MASTER );
810  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $name ) {
811  $row = $dbw->selectRow(
812  'actor',
813  [ 'actor_id' ],
814  [ 'actor_name' => $name, 'actor_user' => null ],
815  $fname,
816  [ 'FOR UPDATE' ]
817  );
818  if ( !$row ) {
819  // No anonymous actor.
820  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
821  }
822 
823  // There is an anonymous actor. Delete the actor row so we can create the user,
824  // then restore the old actor_id so as to not break existing references.
825  // @todo If MediaWiki ever starts using foreign keys for `actor`, this will break things.
826  $dbw->delete( 'actor', [ 'actor_id' => $row->actor_id ], $fname );
827  $user = self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
828  $dbw->update(
829  'actor',
830  [ 'actor_id' => $row->actor_id ],
831  [ 'actor_id' => $user->getActorId() ],
832  $fname
833  );
834  $user->clearInstanceCache( 'id' );
835  $user->invalidateCache();
836  return $user;
837  } );
838  }
839 
840  $user = self::newFromRow( $row );
841 
842  if ( !$user->isSystemUser() ) {
843  // User exists. Steal it?
844  if ( !$options['steal'] ) {
845  return null;
846  }
847 
848  AuthManager::singleton()->revokeAccessForUser( $name );
849 
850  $user->invalidateEmail();
851  $user->mToken = self::INVALID_TOKEN;
852  $user->saveSettings();
853  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
854  }
855 
856  return $user;
857  }
858 
859  // @}
860 
866  public static function whoIs( $id ) {
867  return UserCache::singleton()->getProp( $id, 'name' );
868  }
869 
876  public static function whoIsReal( $id ) {
877  return UserCache::singleton()->getProp( $id, 'real_name' );
878  }
879 
886  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
887  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
888  $name = (string)$name;
889  $nt = Title::makeTitleSafe( NS_USER, $name );
890  if ( $nt === null ) {
891  // Illegal name
892  return null;
893  }
894 
895  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
896  return self::$idCacheByName[$name] === null ? null : (int)self::$idCacheByName[$name];
897  }
898 
899  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
900  $db = wfGetDB( $index );
901 
902  $s = $db->selectRow(
903  'user',
904  [ 'user_id' ],
905  [ 'user_name' => $nt->getText() ],
906  __METHOD__,
907  $options
908  );
909 
910  if ( $s === false ) {
911  $result = null;
912  } else {
913  $result = (int)$s->user_id;
914  }
915 
916  if ( count( self::$idCacheByName ) >= 1000 ) {
917  self::$idCacheByName = [];
918  }
919 
920  self::$idCacheByName[$name] = $result;
921 
922  return $result;
923  }
924 
928  public static function resetIdByNameCache() {
929  self::$idCacheByName = [];
930  }
931 
948  public static function isIP( $name ) {
949  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
950  || IPUtils::isIPv6( $name );
951  }
952 
959  public function isIPRange() {
960  return IPUtils::isValidRange( $this->mName );
961  }
962 
974  public static function isValidUserName( $name ) {
975  global $wgMaxNameChars;
976 
977  if ( $name == ''
978  || self::isIP( $name )
979  || strpos( $name, '/' ) !== false
980  || strlen( $name ) > $wgMaxNameChars
981  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
982  ) {
983  return false;
984  }
985 
986  // Ensure that the name can't be misresolved as a different title,
987  // such as with extra namespace keys at the start.
988  $parsed = Title::newFromText( $name );
989  if ( $parsed === null
990  || $parsed->getNamespace()
991  || strcmp( $name, $parsed->getPrefixedText() ) ) {
992  return false;
993  }
994 
995  // Check an additional blacklist of troublemaker characters.
996  // Should these be merged into the title char list?
997  $unicodeBlacklist = '/[' .
998  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
999  '\x{00a0}' . # non-breaking space
1000  '\x{2000}-\x{200f}' . # various whitespace
1001  '\x{2028}-\x{202f}' . # breaks and control chars
1002  '\x{3000}' . # ideographic space
1003  '\x{e000}-\x{f8ff}' . # private use
1004  ']/u';
1005  if ( preg_match( $unicodeBlacklist, $name ) ) {
1006  return false;
1007  }
1008 
1009  return true;
1010  }
1011 
1023  public static function isUsableName( $name ) {
1024  global $wgReservedUsernames;
1025  // Must be a valid username, obviously ;)
1026  if ( !self::isValidUserName( $name ) ) {
1027  return false;
1028  }
1029 
1030  if ( !self::$reservedUsernames ) {
1031  self::$reservedUsernames = $wgReservedUsernames;
1032  Hooks::run( 'UserGetReservedNames', [ &self::$reservedUsernames ] );
1033  }
1034 
1035  // Certain names may be reserved for batch processes.
1036  foreach ( self::$reservedUsernames as $reserved ) {
1037  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1038  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1039  }
1040  if ( $reserved == $name ) {
1041  return false;
1042  }
1043  }
1044  return true;
1045  }
1046 
1057  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1058  if ( $groups === [] ) {
1059  return UserArrayFromResult::newFromIDs( [] );
1060  }
1061 
1062  $groups = array_unique( (array)$groups );
1063  $limit = min( 5000, $limit );
1064 
1065  $conds = [ 'ug_group' => $groups ];
1066  if ( $after !== null ) {
1067  $conds[] = 'ug_user > ' . (int)$after;
1068  }
1069 
1070  $dbr = wfGetDB( DB_REPLICA );
1071  $ids = $dbr->selectFieldValues(
1072  'user_groups',
1073  'ug_user',
1074  $conds,
1075  __METHOD__,
1076  [
1077  'DISTINCT' => true,
1078  'ORDER BY' => 'ug_user',
1079  'LIMIT' => $limit,
1080  ]
1081  ) ?: [];
1082  return UserArray::newFromIDs( $ids );
1083  }
1084 
1097  public static function isCreatableName( $name ) {
1099 
1100  // Ensure that the username isn't longer than 235 bytes, so that
1101  // (at least for the builtin skins) user javascript and css files
1102  // will work. (T25080)
1103  if ( strlen( $name ) > 235 ) {
1104  wfDebugLog( 'username', __METHOD__ .
1105  ": '$name' invalid due to length" );
1106  return false;
1107  }
1108 
1109  // Preg yells if you try to give it an empty string
1110  if ( $wgInvalidUsernameCharacters !== '' &&
1111  preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name )
1112  ) {
1113  wfDebugLog( 'username', __METHOD__ .
1114  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1115  return false;
1116  }
1117 
1118  return self::isUsableName( $name );
1119  }
1120 
1127  public function isValidPassword( $password ) {
1128  // simple boolean wrapper for checkPasswordValidity
1129  return $this->checkPasswordValidity( $password )->isGood();
1130  }
1131 
1153  public function checkPasswordValidity( $password ) {
1154  global $wgPasswordPolicy;
1155 
1156  $upp = new UserPasswordPolicy(
1157  $wgPasswordPolicy['policies'],
1158  $wgPasswordPolicy['checks']
1159  );
1160 
1161  $status = Status::newGood( [] );
1162  $result = false; // init $result to false for the internal checks
1163 
1164  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1165  $status->error( $result );
1166  return $status;
1167  }
1168 
1169  if ( $result === false ) {
1170  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1171  return $status;
1172  }
1173 
1174  if ( $result === true ) {
1175  return $status;
1176  }
1177 
1178  $status->error( $result );
1179  return $status; // the isValidPassword hook set a string $result and returned true
1180  }
1181 
1195  public static function getCanonicalName( $name, $validate = 'valid' ) {
1196  // Force usernames to capital
1197  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1198 
1199  # Reject names containing '#'; these will be cleaned up
1200  # with title normalisation, but then it's too late to
1201  # check elsewhere
1202  if ( strpos( $name, '#' ) !== false ) {
1203  return false;
1204  }
1205 
1206  // Clean up name according to title rules,
1207  // but only when validation is requested (T14654)
1208  $t = ( $validate !== false ) ?
1209  Title::newFromText( $name, NS_USER ) : Title::makeTitle( NS_USER, $name );
1210  // Check for invalid titles
1211  if ( $t === null || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1212  return false;
1213  }
1214 
1215  $name = $t->getText();
1216 
1217  switch ( $validate ) {
1218  case false:
1219  break;
1220  case 'valid':
1221  if ( !self::isValidUserName( $name ) ) {
1222  $name = false;
1223  }
1224  break;
1225  case 'usable':
1226  if ( !self::isUsableName( $name ) ) {
1227  $name = false;
1228  }
1229  break;
1230  case 'creatable':
1231  if ( !self::isCreatableName( $name ) ) {
1232  $name = false;
1233  }
1234  break;
1235  default:
1236  throw new InvalidArgumentException(
1237  'Invalid parameter value for $validate in ' . __METHOD__ );
1238  }
1239  return $name;
1240  }
1241 
1251  public function loadDefaults( $name = false, $actorId = null ) {
1252  $this->mId = 0;
1253  $this->mName = $name;
1254  $this->mActorId = $actorId;
1255  $this->mRealName = '';
1256  $this->mEmail = '';
1257  $this->mOptionOverrides = null;
1258  $this->mOptionsLoaded = false;
1259 
1260  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1261  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1262  if ( $loggedOut !== 0 ) {
1263  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1264  } else {
1265  $this->mTouched = '1'; # Allow any pages to be cached
1266  }
1267 
1268  $this->mToken = null; // Don't run cryptographic functions till we need a token
1269  $this->mEmailAuthenticated = null;
1270  $this->mEmailToken = '';
1271  $this->mEmailTokenExpires = null;
1272  $this->mRegistration = wfTimestamp( TS_MW );
1273  $this->mGroupMemberships = [];
1274 
1275  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1276  }
1277 
1290  public function isItemLoaded( $item, $all = 'all' ) {
1291  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1292  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1293  }
1294 
1300  protected function setItemLoaded( $item ) {
1301  if ( is_array( $this->mLoadedItems ) ) {
1302  $this->mLoadedItems[$item] = true;
1303  }
1304  }
1305 
1311  private function loadFromSession() {
1312  // MediaWiki\Session\Session already did the necessary authentication of the user
1313  // returned here, so just use it if applicable.
1314  $session = $this->getRequest()->getSession();
1315  $user = $session->getUser();
1316  if ( $user->isLoggedIn() ) {
1317  $this->loadFromUserObject( $user );
1318 
1319  // Other code expects these to be set in the session, so set them.
1320  $session->set( 'wsUserID', $this->getId() );
1321  $session->set( 'wsUserName', $this->getName() );
1322  $session->set( 'wsToken', $this->getToken() );
1323 
1324  return true;
1325  }
1326 
1327  return false;
1328  }
1329 
1335  public function trackBlockWithCookie() {
1336  wfDeprecated( __METHOD__, '1.34' );
1337  // Obsolete.
1338  // MediaWiki::preOutputCommit() handles this whenever possible.
1339  }
1340 
1348  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1349  // Paranoia
1350  $this->mId = intval( $this->mId );
1351 
1352  if ( !$this->mId ) {
1353  // Anonymous users are not in the database
1354  $this->loadDefaults();
1355  return false;
1356  }
1357 
1358  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1359  $db = wfGetDB( $index );
1360 
1361  $userQuery = self::getQueryInfo();
1362  $s = $db->selectRow(
1363  $userQuery['tables'],
1364  $userQuery['fields'],
1365  [ 'user_id' => $this->mId ],
1366  __METHOD__,
1367  $options,
1368  $userQuery['joins']
1369  );
1370 
1371  $this->queryFlagsUsed = $flags;
1372  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1373 
1374  if ( $s !== false ) {
1375  // Initialise user table data
1376  $this->loadFromRow( $s );
1377  $this->mGroupMemberships = null; // deferred
1378  $this->getEditCount(); // revalidation for nulls
1379  return true;
1380  }
1381 
1382  // Invalid user_id
1383  $this->mId = 0;
1384  $this->loadDefaults();
1385 
1386  return false;
1387  }
1388 
1401  protected function loadFromRow( $row, $data = null ) {
1402  if ( !is_object( $row ) ) {
1403  throw new InvalidArgumentException( '$row must be an object' );
1404  }
1405 
1406  $all = true;
1407 
1408  $this->mGroupMemberships = null; // deferred
1409 
1410  if ( isset( $row->actor_id ) ) {
1411  $this->mActorId = (int)$row->actor_id;
1412  if ( $this->mActorId !== 0 ) {
1413  $this->mFrom = 'actor';
1414  }
1415  $this->setItemLoaded( 'actor' );
1416  } else {
1417  $all = false;
1418  }
1419 
1420  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1421  $this->mName = $row->user_name;
1422  $this->mFrom = 'name';
1423  $this->setItemLoaded( 'name' );
1424  } else {
1425  $all = false;
1426  }
1427 
1428  if ( isset( $row->user_real_name ) ) {
1429  $this->mRealName = $row->user_real_name;
1430  $this->setItemLoaded( 'realname' );
1431  } else {
1432  $all = false;
1433  }
1434 
1435  if ( isset( $row->user_id ) ) {
1436  $this->mId = intval( $row->user_id );
1437  if ( $this->mId !== 0 ) {
1438  $this->mFrom = 'id';
1439  }
1440  $this->setItemLoaded( 'id' );
1441  } else {
1442  $all = false;
1443  }
1444 
1445  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1446  self::$idCacheByName[$row->user_name] = $row->user_id;
1447  }
1448 
1449  if ( isset( $row->user_editcount ) ) {
1450  $this->mEditCount = $row->user_editcount;
1451  } else {
1452  $all = false;
1453  }
1454 
1455  if ( isset( $row->user_touched ) ) {
1456  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1457  } else {
1458  $all = false;
1459  }
1460 
1461  if ( isset( $row->user_token ) ) {
1462  // The definition for the column is binary(32), so trim the NULs
1463  // that appends. The previous definition was char(32), so trim
1464  // spaces too.
1465  $this->mToken = rtrim( $row->user_token, " \0" );
1466  if ( $this->mToken === '' ) {
1467  $this->mToken = null;
1468  }
1469  } else {
1470  $all = false;
1471  }
1472 
1473  if ( isset( $row->user_email ) ) {
1474  $this->mEmail = $row->user_email;
1475  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1476  $this->mEmailToken = $row->user_email_token;
1477  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1478  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1479  } else {
1480  $all = false;
1481  }
1482 
1483  if ( $all ) {
1484  $this->mLoadedItems = true;
1485  }
1486 
1487  if ( is_array( $data ) ) {
1488  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1489  if ( $data['user_groups'] === [] ) {
1490  $this->mGroupMemberships = [];
1491  } else {
1492  $firstGroup = reset( $data['user_groups'] );
1493  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1494  $this->mGroupMemberships = [];
1495  foreach ( $data['user_groups'] as $row ) {
1496  $ugm = UserGroupMembership::newFromRow( (object)$row );
1497  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1498  }
1499  }
1500  }
1501  }
1502  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1503  $this->loadOptions( $data['user_properties'] );
1504  }
1505  }
1506  }
1507 
1513  protected function loadFromUserObject( $user ) {
1514  $user->load();
1515  foreach ( self::$mCacheVars as $var ) {
1516  $this->$var = $user->$var;
1517  }
1518  }
1519 
1523  private function loadGroups() {
1524  if ( $this->mGroupMemberships === null ) {
1525  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1526  ? wfGetDB( DB_MASTER )
1527  : wfGetDB( DB_REPLICA );
1528  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1529  $this->mId, $db );
1530  }
1531  }
1532 
1547  public function addAutopromoteOnceGroups( $event ) {
1549 
1550  if ( wfReadOnly() || !$this->getId() ) {
1551  return [];
1552  }
1553 
1554  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1555  if ( $toPromote === [] ) {
1556  return [];
1557  }
1558 
1559  if ( !$this->checkAndSetTouched() ) {
1560  return []; // raced out (bug T48834)
1561  }
1562 
1563  $oldGroups = $this->getGroups(); // previous groups
1564  $oldUGMs = $this->getGroupMemberships();
1565  foreach ( $toPromote as $group ) {
1566  $this->addGroup( $group );
1567  }
1568  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1569  $newUGMs = $this->getGroupMemberships();
1570 
1571  // update groups in external authentication database
1572  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1573 
1574  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1575  $logEntry->setPerformer( $this );
1576  $logEntry->setTarget( $this->getUserPage() );
1577  $logEntry->setParameters( [
1578  '4::oldgroups' => $oldGroups,
1579  '5::newgroups' => $newGroups,
1580  ] );
1581  $logid = $logEntry->insert();
1582  if ( $wgAutopromoteOnceLogInRC ) {
1583  $logEntry->publish( $logid );
1584  }
1585 
1586  return $toPromote;
1587  }
1588 
1598  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1599  if ( $this->mTouched ) {
1600  // CAS check: only update if the row wasn't changed sicne it was loaded.
1601  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1602  }
1603 
1604  return $conditions;
1605  }
1606 
1616  protected function checkAndSetTouched() {
1617  $this->load();
1618 
1619  if ( !$this->mId ) {
1620  return false; // anon
1621  }
1622 
1623  // Get a new user_touched that is higher than the old one
1624  $newTouched = $this->newTouchedTimestamp();
1625 
1626  $dbw = wfGetDB( DB_MASTER );
1627  $dbw->update( 'user',
1628  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1629  $this->makeUpdateConditions( $dbw, [
1630  'user_id' => $this->mId,
1631  ] ),
1632  __METHOD__
1633  );
1634  $success = ( $dbw->affectedRows() > 0 );
1635 
1636  if ( $success ) {
1637  $this->mTouched = $newTouched;
1638  $this->clearSharedCache( 'changed' );
1639  } else {
1640  // Clears on failure too since that is desired if the cache is stale
1641  $this->clearSharedCache( 'refresh' );
1642  }
1643 
1644  return $success;
1645  }
1646 
1654  public function clearInstanceCache( $reloadFrom = false ) {
1655  global $wgFullyInitialised;
1656 
1657  $this->mNewtalk = -1;
1658  $this->mDatePreference = null;
1659  $this->mBlockedby = -1; # Unset
1660  $this->mHash = false;
1661  $this->mEffectiveGroups = null;
1662  $this->mImplicitGroups = null;
1663  $this->mGroupMemberships = null;
1664  $this->mOptions = null;
1665  $this->mOptionsLoaded = false;
1666  $this->mEditCount = null;
1667 
1668  // Replacement of former `$this->mRights = null` line
1669  if ( $wgFullyInitialised && $this->mFrom ) {
1670  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
1671  $this
1672  );
1673  }
1674 
1675  if ( $reloadFrom ) {
1676  $this->mLoadedItems = [];
1677  $this->mFrom = $reloadFrom;
1678  }
1679  }
1680 
1682  private static $defOpt = null;
1684  private static $defOptLang = null;
1685 
1692  public static function resetGetDefaultOptionsForTestsOnly() {
1693  Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
1694  self::$defOpt = null;
1695  self::$defOptLang = null;
1696  }
1697 
1704  public static function getDefaultOptions() {
1708 
1709  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1710  if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
1711  // The content language does not change (and should not change) mid-request, but the
1712  // unit tests change it anyway, and expect this method to return values relevant to the
1713  // current content language.
1714  return self::$defOpt;
1715  }
1716 
1717  self::$defOpt = $wgDefaultUserOptions;
1718  // Default language setting
1719  self::$defOptLang = $contLang->getCode();
1720  self::$defOpt['language'] = self::$defOptLang;
1721  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1722  if ( $langCode === $contLang->getCode() ) {
1723  self::$defOpt['variant'] = $langCode;
1724  } else {
1725  self::$defOpt["variant-$langCode"] = $langCode;
1726  }
1727  }
1728 
1729  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1730  // since extensions may change the set of searchable namespaces depending
1731  // on user groups/permissions.
1732  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1733  self::$defOpt['searchNs' . $nsnum] = (bool)$val;
1734  }
1735  self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1736 
1737  Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
1738 
1739  return self::$defOpt;
1740  }
1741 
1748  public static function getDefaultOption( $opt ) {
1749  $defOpts = self::getDefaultOptions();
1750  return $defOpts[$opt] ?? null;
1751  }
1752 
1762  private function getBlockedStatus( $fromReplica = true ) {
1763  if ( $this->mBlockedby != -1 ) {
1764  return;
1765  }
1766 
1767  wfDebug( __METHOD__ . ": checking...\n" );
1768 
1769  // Initialize data...
1770  // Otherwise something ends up stomping on $this->mBlockedby when
1771  // things get lazy-loaded later, causing false positive block hits
1772  // due to -1 !== 0. Probably session-related... Nothing should be
1773  // overwriting mBlockedby, surely?
1774  $this->load();
1775 
1776  // TODO: Block checking shouldn't really be done from the User object. Block
1777  // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1778  // which need more knowledge of the request context than the User should have.
1779  // Since we do currently check blocks from the User, we have to do the following
1780  // here:
1781  // - Check if this is the user associated with the main request
1782  // - If so, pass the relevant request information to the block manager
1783  $request = null;
1784 
1785  // The session user is set up towards the end of Setup.php. Until then,
1786  // assume it's a logged-out user.
1787  $sessionUser = RequestContext::getMain()->getUser();
1788  $globalUserName = $sessionUser->isSafeToLoad()
1789  ? $sessionUser->getName()
1790  : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1791 
1792  if ( $this->getName() === $globalUserName ) {
1793  // This is the global user, so we need to pass the request
1794  $request = $this->getRequest();
1795  }
1796 
1797  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1798  $this,
1799  $request,
1800  $fromReplica
1801  );
1802 
1803  if ( $block ) {
1804  $this->mBlock = $block;
1805  $this->mBlockedby = $block->getByName();
1806  $this->mBlockreason = $block->getReason();
1807  $this->mHideName = $block->getHideName();
1808  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1809  } else {
1810  $this->mBlock = null;
1811  $this->mBlockedby = '';
1812  $this->mBlockreason = '';
1813  $this->mHideName = 0;
1814  $this->mAllowUsertalk = false;
1815  }
1816  }
1817 
1826  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1827  wfDeprecated( __METHOD__, '1.34' );
1828  return MediaWikiServices::getInstance()->getBlockManager()
1829  ->isDnsBlacklisted( $ip, $checkWhitelist );
1830  }
1831 
1840  public function inDnsBlacklist( $ip, $bases ) {
1841  wfDeprecated( __METHOD__, '1.34' );
1842 
1843  $found = false;
1844  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1845  if ( IPUtils::isIPv4( $ip ) ) {
1846  // Reverse IP, T23255
1847  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1848 
1849  foreach ( (array)$bases as $base ) {
1850  // Make hostname
1851  // If we have an access key, use that too (ProjectHoneypot, etc.)
1852  $basename = $base;
1853  if ( is_array( $base ) ) {
1854  if ( count( $base ) >= 2 ) {
1855  // Access key is 1, base URL is 0
1856  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1857  } else {
1858  $host = "$ipReversed.{$base[0]}";
1859  }
1860  $basename = $base[0];
1861  } else {
1862  $host = "$ipReversed.$base";
1863  }
1864 
1865  // Send query
1866  $ipList = gethostbynamel( $host );
1867 
1868  if ( $ipList ) {
1869  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1870  $found = true;
1871  break;
1872  }
1873 
1874  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1875  }
1876  }
1877 
1878  return $found;
1879  }
1880 
1888  public static function isLocallyBlockedProxy( $ip ) {
1889  wfDeprecated( __METHOD__, '1.34' );
1890 
1891  global $wgProxyList;
1892 
1893  if ( !$wgProxyList ) {
1894  return false;
1895  }
1896 
1897  if ( !is_array( $wgProxyList ) ) {
1898  // Load values from the specified file
1899  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1900  }
1901 
1902  $resultProxyList = [];
1903  $deprecatedIPEntries = [];
1904 
1905  // backward compatibility: move all ip addresses in keys to values
1906  foreach ( $wgProxyList as $key => $value ) {
1907  $keyIsIP = IPUtils::isIPAddress( $key );
1908  $valueIsIP = IPUtils::isIPAddress( $value );
1909  if ( $keyIsIP && !$valueIsIP ) {
1910  $deprecatedIPEntries[] = $key;
1911  $resultProxyList[] = $key;
1912  } elseif ( $keyIsIP && $valueIsIP ) {
1913  $deprecatedIPEntries[] = $key;
1914  $resultProxyList[] = $key;
1915  $resultProxyList[] = $value;
1916  } else {
1917  $resultProxyList[] = $value;
1918  }
1919  }
1920 
1921  if ( $deprecatedIPEntries ) {
1922  wfDeprecated(
1923  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1924  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
1925  }
1926 
1927  $proxyListIPSet = new IPSet( $resultProxyList );
1928  return $proxyListIPSet->match( $ip );
1929  }
1930 
1936  public function isPingLimitable() {
1937  global $wgRateLimitsExcludedIPs;
1938  if ( IPUtils::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1939  // No other good way currently to disable rate limits
1940  // for specific IPs. :P
1941  // But this is a crappy hack and should die.
1942  return false;
1943  }
1944  return !$this->isAllowed( 'noratelimit' );
1945  }
1946 
1961  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1962  // Avoid PHP 7.1 warning of passing $this by reference
1963  $user = $this;
1964  // Call the 'PingLimiter' hook
1965  $result = false;
1966  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1967  return $result;
1968  }
1969 
1970  global $wgRateLimits;
1971  if ( !isset( $wgRateLimits[$action] ) ) {
1972  return false;
1973  }
1974 
1975  $limits = array_merge(
1976  [ '&can-bypass' => true ],
1977  $wgRateLimits[$action]
1978  );
1979 
1980  // Some groups shouldn't trigger the ping limiter, ever
1981  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1982  return false;
1983  }
1984 
1985  $keys = [];
1986  $id = $this->getId();
1987  $userLimit = false;
1988  $isNewbie = $this->isNewbie();
1990 
1991  if ( $id == 0 ) {
1992  // limits for anons
1993  if ( isset( $limits['anon'] ) ) {
1994  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1995  }
1996  } elseif ( isset( $limits['user'] ) ) {
1997  // limits for logged-in users
1998  $userLimit = $limits['user'];
1999  }
2000 
2001  // limits for anons and for newbie logged-in users
2002  if ( $isNewbie ) {
2003  // ip-based limits
2004  if ( isset( $limits['ip'] ) ) {
2005  $ip = $this->getRequest()->getIP();
2006  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
2007  }
2008  // subnet-based limits
2009  if ( isset( $limits['subnet'] ) ) {
2010  $ip = $this->getRequest()->getIP();
2011  $subnet = IPUtils::getSubnet( $ip );
2012  if ( $subnet !== false ) {
2013  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
2014  }
2015  }
2016  }
2017 
2018  // Check for group-specific permissions
2019  // If more than one group applies, use the group with the highest limit ratio (max/period)
2020  foreach ( $this->getGroups() as $group ) {
2021  if ( isset( $limits[$group] ) ) {
2022  if ( $userLimit === false
2023  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2024  ) {
2025  $userLimit = $limits[$group];
2026  }
2027  }
2028  }
2029 
2030  // limits for newbie logged-in users (override all the normal user limits)
2031  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2032  $userLimit = $limits['newbie'];
2033  }
2034 
2035  // Set the user limit key
2036  if ( $userLimit !== false ) {
2037  // phan is confused because &can-bypass's value is a bool, so it assumes
2038  // that $userLimit is also a bool here.
2039  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2040  list( $max, $period ) = $userLimit;
2041  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2042  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2043  }
2044 
2045  // ip-based limits for all ping-limitable users
2046  if ( isset( $limits['ip-all'] ) ) {
2047  $ip = $this->getRequest()->getIP();
2048  // ignore if user limit is more permissive
2049  if ( $isNewbie || $userLimit === false
2050  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2051  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2052  }
2053  }
2054 
2055  // subnet-based limits for all ping-limitable users
2056  if ( isset( $limits['subnet-all'] ) ) {
2057  $ip = $this->getRequest()->getIP();
2058  $subnet = IPUtils::getSubnet( $ip );
2059  if ( $subnet !== false ) {
2060  // ignore if user limit is more permissive
2061  if ( $isNewbie || $userLimit === false
2062  || $limits['ip-all'][0] / $limits['ip-all'][1]
2063  > $userLimit[0] / $userLimit[1] ) {
2064  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2065  }
2066  }
2067  }
2068 
2069  $triggered = false;
2070  foreach ( $keys as $key => $limit ) {
2071  // phan is confused because &can-bypass's value is a bool, so it assumes
2072  // that $userLimit is also a bool here.
2073  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2074  list( $max, $period ) = $limit;
2075  $summary = "(limit $max in {$period}s)";
2076  $count = $cache->get( $key );
2077  // Already pinged?
2078  if ( $count && $count >= $max ) {
2079  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2080  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2081  $triggered = true;
2082  } else {
2083  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2084  if ( $incrBy > 0 ) {
2085  $cache->add( $key, 0, intval( $period ) ); // first ping
2086  }
2087  }
2088  if ( $incrBy > 0 ) {
2089  $cache->incrWithInit( $key, (int)$period, $incrBy, $incrBy );
2090  }
2091  }
2092 
2093  return $triggered;
2094  }
2095 
2107  public function isBlocked( $fromReplica = true ) {
2108  return $this->getBlock( $fromReplica ) instanceof AbstractBlock &&
2109  $this->getBlock()->appliesToRight( 'edit' );
2110  }
2111 
2118  public function getBlock( $fromReplica = true ) {
2119  $this->getBlockedStatus( $fromReplica );
2120  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
2121  }
2122 
2134  public function isBlockedFrom( $title, $fromReplica = false ) {
2135  return MediaWikiServices::getInstance()->getPermissionManager()
2136  ->isBlockedFrom( $this, $title, $fromReplica );
2137  }
2138 
2143  public function blockedBy() {
2144  $this->getBlockedStatus();
2145  return $this->mBlockedby;
2146  }
2147 
2154  public function blockedFor() {
2155  $this->getBlockedStatus();
2156  return $this->mBlockreason;
2157  }
2158 
2163  public function getBlockId() {
2164  $this->getBlockedStatus();
2165  return ( $this->mBlock ? $this->mBlock->getId() : false );
2166  }
2167 
2176  public function isBlockedGlobally( $ip = '' ) {
2177  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
2178  }
2179 
2190  public function getGlobalBlock( $ip = '' ) {
2191  if ( $this->mGlobalBlock !== null ) {
2192  return $this->mGlobalBlock ?: null;
2193  }
2194  // User is already an IP?
2195  if ( IPUtils::isIPAddress( $this->getName() ) ) {
2196  $ip = $this->getName();
2197  } elseif ( !$ip ) {
2198  $ip = $this->getRequest()->getIP();
2199  }
2200  // Avoid PHP 7.1 warning of passing $this by reference
2201  $user = $this;
2202  $blocked = false;
2203  $block = null;
2204  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2205 
2206  if ( $blocked && $block === null ) {
2207  // back-compat: UserIsBlockedGlobally didn't have $block param first
2208  $block = new SystemBlock( [
2209  'address' => $ip,
2210  'systemBlock' => 'global-block'
2211  ] );
2212  }
2213 
2214  $this->mGlobalBlock = $blocked ? $block : false;
2215  return $this->mGlobalBlock ?: null;
2216  }
2217 
2223  public function isLocked() {
2224  if ( $this->mLocked !== null ) {
2225  return $this->mLocked;
2226  }
2227  // Reset for hook
2228  $this->mLocked = false;
2229  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2230  return $this->mLocked;
2231  }
2232 
2238  public function isHidden() {
2239  if ( $this->mHideName !== null ) {
2240  return (bool)$this->mHideName;
2241  }
2242  $this->getBlockedStatus();
2243  if ( !$this->mHideName ) {
2244  // Reset for hook
2245  $this->mHideName = false;
2246  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ], '1.34' );
2247  }
2248  return (bool)$this->mHideName;
2249  }
2250 
2255  public function getId() {
2256  if ( $this->mId === null && $this->mName !== null &&
2257  ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
2258  ) {
2259  // Special case, we know the user is anonymous
2260  return 0;
2261  }
2262 
2263  if ( !$this->isItemLoaded( 'id' ) ) {
2264  // Don't load if this was initialized from an ID
2265  $this->load();
2266  }
2267 
2268  return (int)$this->mId;
2269  }
2270 
2275  public function setId( $v ) {
2276  $this->mId = $v;
2277  $this->clearInstanceCache( 'id' );
2278  }
2279 
2284  public function getName() {
2285  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2286  // Special case optimisation
2287  return $this->mName;
2288  }
2289 
2290  $this->load();
2291  if ( $this->mName === false ) {
2292  // Clean up IPs
2293  $this->mName = IPUtils::sanitizeIP( $this->getRequest()->getIP() );
2294  }
2295 
2296  return $this->mName;
2297  }
2298 
2312  public function setName( $str ) {
2313  $this->load();
2314  $this->mName = $str;
2315  }
2316 
2323  public function getActorId( IDatabase $dbw = null ) {
2324  if ( !$this->isItemLoaded( 'actor' ) ) {
2325  $this->load();
2326  }
2327 
2328  if ( !$this->mActorId && $dbw ) {
2329  $q = [
2330  'actor_user' => $this->getId() ?: null,
2331  'actor_name' => (string)$this->getName(),
2332  ];
2333  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2334  throw new CannotCreateActorException(
2335  'Cannot create an actor for a usable name that is not an existing user: ' .
2336  "user_id={$this->getId()} user_name=\"{$this->getName()}\""
2337  );
2338  }
2339  if ( $q['actor_name'] === '' ) {
2340  throw new CannotCreateActorException(
2341  'Cannot create an actor for a user with no name: ' .
2342  "user_id={$this->getId()} user_name=\"{$this->getName()}\""
2343  );
2344  }
2345  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2346  if ( $dbw->affectedRows() ) {
2347  $this->mActorId = (int)$dbw->insertId();
2348  } else {
2349  // Outdated cache?
2350  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2351  $this->mActorId = (int)$dbw->selectField(
2352  'actor',
2353  'actor_id',
2354  $q,
2355  __METHOD__,
2356  [ 'LOCK IN SHARE MODE' ]
2357  );
2358  if ( !$this->mActorId ) {
2359  throw new CannotCreateActorException(
2360  "Failed to create actor ID for " .
2361  "user_id={$this->getId()} user_name=\"{$this->getName()}\""
2362  );
2363  }
2364  }
2365  $this->invalidateCache();
2366  $this->setItemLoaded( 'actor' );
2367  }
2368 
2369  return (int)$this->mActorId;
2370  }
2371 
2376  public function getTitleKey() {
2377  return str_replace( ' ', '_', $this->getName() );
2378  }
2379 
2384  public function getNewtalk() {
2385  $this->load();
2386 
2387  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2388  if ( $this->mNewtalk === -1 ) {
2389  $this->mNewtalk = false; # reset talk page status
2390 
2391  // Check memcached separately for anons, who have no
2392  // entire User object stored in there.
2393  if ( !$this->mId ) {
2394  global $wgDisableAnonTalk;
2395  if ( $wgDisableAnonTalk ) {
2396  // Anon newtalk disabled by configuration.
2397  $this->mNewtalk = false;
2398  } else {
2399  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2400  }
2401  } else {
2402  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2403  }
2404  }
2405 
2406  return (bool)$this->mNewtalk;
2407  }
2408 
2422  public function getNewMessageLinks() {
2423  // Avoid PHP 7.1 warning of passing $this by reference
2424  $user = $this;
2425  $talks = [];
2426  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2427  return $talks;
2428  }
2429 
2430  if ( !$this->getNewtalk() ) {
2431  return [];
2432  }
2433  $utp = $this->getTalkPage();
2434  $dbr = wfGetDB( DB_REPLICA );
2435  // Get the "last viewed rev" timestamp from the oldest message notification
2436  $timestamp = $dbr->selectField( 'user_newtalk',
2437  'MIN(user_last_timestamp)',
2438  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2439  __METHOD__ );
2440  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2441  return [
2442  [
2444  'link' => $utp->getLocalURL(),
2445  'rev' => $rev
2446  ]
2447  ];
2448  }
2449 
2455  public function getNewMessageRevisionId() {
2456  $newMessageRevisionId = null;
2457  $newMessageLinks = $this->getNewMessageLinks();
2458 
2459  // Note: getNewMessageLinks() never returns more than a single link
2460  // and it is always for the same wiki, but we double-check here in
2461  // case that changes some time in the future.
2462  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2463  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2464  && $newMessageLinks[0]['rev']
2465  ) {
2467  $newMessageRevision = $newMessageLinks[0]['rev'];
2468  $newMessageRevisionId = $newMessageRevision->getId();
2469  }
2470 
2471  return $newMessageRevisionId;
2472  }
2473 
2482  protected function checkNewtalk( $field, $id ) {
2483  $dbr = wfGetDB( DB_REPLICA );
2484 
2485  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2486 
2487  return $ok !== false;
2488  }
2489 
2497  protected function updateNewtalk( $field, $id, $curRev = null ) {
2498  // Get timestamp of the talk page revision prior to the current one
2499  $prevRev = $curRev ? $curRev->getPrevious() : false;
2500  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2501  // Mark the user as having new messages since this revision
2502  $dbw = wfGetDB( DB_MASTER );
2503  $dbw->insert( 'user_newtalk',
2504  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2505  __METHOD__,
2506  [ 'IGNORE' ] );
2507  if ( $dbw->affectedRows() ) {
2508  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2509  return true;
2510  }
2511 
2512  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2513  return false;
2514  }
2515 
2522  protected function deleteNewtalk( $field, $id ) {
2523  $dbw = wfGetDB( DB_MASTER );
2524  $dbw->delete( 'user_newtalk',
2525  [ $field => $id ],
2526  __METHOD__ );
2527  if ( $dbw->affectedRows() ) {
2528  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2529  return true;
2530  }
2531 
2532  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2533  return false;
2534  }
2535 
2542  public function setNewtalk( $val, $curRev = null ) {
2543  if ( wfReadOnly() ) {
2544  return;
2545  }
2546 
2547  $this->load();
2548  $this->mNewtalk = $val;
2549 
2550  if ( $this->isAnon() ) {
2551  $field = 'user_ip';
2552  $id = $this->getName();
2553  } else {
2554  $field = 'user_id';
2555  $id = $this->getId();
2556  }
2557 
2558  if ( $val ) {
2559  $changed = $this->updateNewtalk( $field, $id, $curRev );
2560  } else {
2561  $changed = $this->deleteNewtalk( $field, $id );
2562  }
2563 
2564  if ( $changed ) {
2565  $this->invalidateCache();
2566  }
2567  }
2568 
2575  private function newTouchedTimestamp() {
2576  $time = time();
2577  if ( $this->mTouched ) {
2578  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2579  }
2580 
2581  return wfTimestamp( TS_MW, $time );
2582  }
2583 
2594  public function clearSharedCache( $mode = 'refresh' ) {
2595  if ( !$this->getId() ) {
2596  return;
2597  }
2598 
2599  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2600  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2601  $key = $this->getCacheKey( $cache );
2602 
2603  if ( $mode === 'refresh' ) {
2604  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2605  } else {
2606  $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2607  function () use ( $cache, $key ) {
2608  $cache->delete( $key );
2609  },
2610  __METHOD__
2611  );
2612  }
2613  }
2614 
2620  public function invalidateCache() {
2621  $this->touch();
2622  $this->clearSharedCache( 'changed' );
2623  }
2624 
2637  public function touch() {
2638  $id = $this->getId();
2639  if ( $id ) {
2640  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2641  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2642  $cache->touchCheckKey( $key );
2643  $this->mQuickTouched = null;
2644  }
2645  }
2646 
2652  public function validateCache( $timestamp ) {
2653  return ( $timestamp >= $this->getTouched() );
2654  }
2655 
2664  public function getTouched() {
2665  $this->load();
2666 
2667  if ( $this->mId ) {
2668  if ( $this->mQuickTouched === null ) {
2669  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2670  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2671 
2672  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2673  }
2674 
2675  return max( $this->mTouched, $this->mQuickTouched );
2676  }
2677 
2678  return $this->mTouched;
2679  }
2680 
2686  public function getDBTouched() {
2687  $this->load();
2688 
2689  return $this->mTouched;
2690  }
2691 
2708  public function setPassword( $str ) {
2709  wfDeprecated( __METHOD__, '1.27' );
2710  return $this->setPasswordInternal( $str );
2711  }
2712 
2721  public function setInternalPassword( $str ) {
2722  wfDeprecated( __METHOD__, '1.27' );
2723  $this->setPasswordInternal( $str );
2724  }
2725 
2734  private function setPasswordInternal( $str ) {
2735  $manager = AuthManager::singleton();
2736 
2737  // If the user doesn't exist yet, fail
2738  if ( !$manager->userExists( $this->getName() ) ) {
2739  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2740  }
2741 
2742  $status = $this->changeAuthenticationData( [
2743  'username' => $this->getName(),
2744  'password' => $str,
2745  'retype' => $str,
2746  ] );
2747  if ( !$status->isGood() ) {
2749  ->info( __METHOD__ . ': Password change rejected: '
2750  . $status->getWikiText( null, null, 'en' ) );
2751  return false;
2752  }
2753 
2754  $this->setOption( 'watchlisttoken', false );
2755  SessionManager::singleton()->invalidateSessionsForUser( $this );
2756 
2757  return true;
2758  }
2759 
2772  public function changeAuthenticationData( array $data ) {
2773  $manager = AuthManager::singleton();
2774  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2775  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2776 
2777  $status = Status::newGood( 'ignored' );
2778  foreach ( $reqs as $req ) {
2779  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2780  }
2781  if ( $status->getValue() === 'ignored' ) {
2782  $status->warning( 'authenticationdatachange-ignored' );
2783  }
2784 
2785  if ( $status->isGood() ) {
2786  foreach ( $reqs as $req ) {
2787  $manager->changeAuthenticationData( $req );
2788  }
2789  }
2790  return $status;
2791  }
2792 
2799  public function getToken( $forceCreation = true ) {
2801 
2802  $this->load();
2803  if ( !$this->mToken && $forceCreation ) {
2804  $this->setToken();
2805  }
2806 
2807  if ( !$this->mToken ) {
2808  // The user doesn't have a token, return null to indicate that.
2809  return null;
2810  }
2811 
2812  if ( $this->mToken === self::INVALID_TOKEN ) {
2813  // We return a random value here so existing token checks are very
2814  // likely to fail.
2815  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2816  }
2817 
2818  if ( $wgAuthenticationTokenVersion === null ) {
2819  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2820  return $this->mToken;
2821  }
2822 
2823  // $wgAuthenticationTokenVersion in use, so hmac it.
2824  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2825 
2826  // The raw hash can be overly long. Shorten it up.
2827  $len = max( 32, self::TOKEN_LENGTH );
2828  if ( strlen( $ret ) < $len ) {
2829  // Should never happen, even md5 is 128 bits
2830  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2831  }
2832 
2833  return substr( $ret, -$len );
2834  }
2835 
2842  public function setToken( $token = false ) {
2843  $this->load();
2844  if ( $this->mToken === self::INVALID_TOKEN ) {
2846  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2847  } elseif ( !$token ) {
2848  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2849  } else {
2850  $this->mToken = $token;
2851  }
2852  }
2853 
2858  public function getEmail() {
2859  $this->load();
2860  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2861  return $this->mEmail;
2862  }
2863 
2869  $this->load();
2870  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2872  }
2873 
2878  public function setEmail( $str ) {
2879  $this->load();
2880  if ( $str == $this->mEmail ) {
2881  return;
2882  }
2883  $this->invalidateEmail();
2884  $this->mEmail = $str;
2885  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2886  }
2887 
2895  public function setEmailWithConfirmation( $str ) {
2897 
2898  if ( !$wgEnableEmail ) {
2899  return Status::newFatal( 'emaildisabled' );
2900  }
2901 
2902  $oldaddr = $this->getEmail();
2903  if ( $str === $oldaddr ) {
2904  return Status::newGood( true );
2905  }
2906 
2907  $type = $oldaddr != '' ? 'changed' : 'set';
2908  $notificationResult = null;
2909 
2910  if ( $wgEmailAuthentication && $type === 'changed' ) {
2911  // Send the user an email notifying the user of the change in registered
2912  // email address on their previous email address
2913  $change = $str != '' ? 'changed' : 'removed';
2914  $notificationResult = $this->sendMail(
2915  wfMessage( 'notificationemail_subject_' . $change )->text(),
2916  wfMessage( 'notificationemail_body_' . $change,
2917  $this->getRequest()->getIP(),
2918  $this->getName(),
2919  $str )->text()
2920  );
2921  }
2922 
2923  $this->setEmail( $str );
2924 
2925  if ( $str !== '' && $wgEmailAuthentication ) {
2926  // Send a confirmation request to the new address if needed
2927  $result = $this->sendConfirmationMail( $type );
2928 
2929  if ( $notificationResult !== null ) {
2930  $result->merge( $notificationResult );
2931  }
2932 
2933  if ( $result->isGood() ) {
2934  // Say to the caller that a confirmation and notification mail has been sent
2935  $result->value = 'eauth';
2936  }
2937  } else {
2938  $result = Status::newGood( true );
2939  }
2940 
2941  return $result;
2942  }
2943 
2948  public function getRealName() {
2949  if ( !$this->isItemLoaded( 'realname' ) ) {
2950  $this->load();
2951  }
2952 
2953  return $this->mRealName;
2954  }
2955 
2960  public function setRealName( $str ) {
2961  $this->load();
2962  $this->mRealName = $str;
2963  }
2964 
2975  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2976  global $wgHiddenPrefs;
2977  $this->loadOptions();
2978 
2979  # We want 'disabled' preferences to always behave as the default value for
2980  # users, even if they have set the option explicitly in their settings (ie they
2981  # set it, and then it was disabled removing their ability to change it). But
2982  # we don't want to erase the preferences in the database in case the preference
2983  # is re-enabled again. So don't touch $mOptions, just override the returned value
2984  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2985  return self::getDefaultOption( $oname );
2986  }
2987 
2988  if ( array_key_exists( $oname, $this->mOptions ) ) {
2989  return $this->mOptions[$oname];
2990  }
2991 
2992  return $defaultOverride;
2993  }
2994 
3003  public function getOptions( $flags = 0 ) {
3004  global $wgHiddenPrefs;
3005  $this->loadOptions();
3006  $options = $this->mOptions;
3007 
3008  # We want 'disabled' preferences to always behave as the default value for
3009  # users, even if they have set the option explicitly in their settings (ie they
3010  # set it, and then it was disabled removing their ability to change it). But
3011  # we don't want to erase the preferences in the database in case the preference
3012  # is re-enabled again. So don't touch $mOptions, just override the returned value
3013  foreach ( $wgHiddenPrefs as $pref ) {
3014  $default = self::getDefaultOption( $pref );
3015  if ( $default !== null ) {
3016  $options[$pref] = $default;
3017  }
3018  }
3019 
3020  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3021  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3022  }
3023 
3024  return $options;
3025  }
3026 
3034  public function getBoolOption( $oname ) {
3035  return (bool)$this->getOption( $oname );
3036  }
3037 
3046  public function getIntOption( $oname, $defaultOverride = 0 ) {
3047  $val = $this->getOption( $oname );
3048  if ( $val == '' ) {
3049  $val = $defaultOverride;
3050  }
3051  return intval( $val );
3052  }
3053 
3062  public function setOption( $oname, $val ) {
3063  $this->loadOptions();
3064 
3065  // Explicitly NULL values should refer to defaults
3066  if ( $val === null ) {
3067  $val = self::getDefaultOption( $oname );
3068  }
3069 
3070  $this->mOptions[$oname] = $val;
3071  }
3072 
3083  public function getTokenFromOption( $oname ) {
3084  global $wgHiddenPrefs;
3085 
3086  $id = $this->getId();
3087  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3088  return false;
3089  }
3090 
3091  $token = $this->getOption( $oname );
3092  if ( !$token ) {
3093  // Default to a value based on the user token to avoid space
3094  // wasted on storing tokens for all users. When this option
3095  // is set manually by the user, only then is it stored.
3096  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3097  }
3098 
3099  return $token;
3100  }
3101 
3111  public function resetTokenFromOption( $oname ) {
3112  global $wgHiddenPrefs;
3113  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3114  return false;
3115  }
3116 
3117  $token = MWCryptRand::generateHex( 40 );
3118  $this->setOption( $oname, $token );
3119  return $token;
3120  }
3121 
3145  public static function listOptionKinds() {
3146  return [
3147  'registered',
3148  'registered-multiselect',
3149  'registered-checkmatrix',
3150  'userjs',
3151  'special',
3152  'unused'
3153  ];
3154  }
3155 
3168  public function getOptionKinds( IContextSource $context, $options = null ) {
3169  $this->loadOptions();
3170  if ( $options === null ) {
3171  $options = $this->mOptions;
3172  }
3173 
3174  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3175  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3176  $mapping = [];
3177 
3178  // Pull out the "special" options, so they don't get converted as
3179  // multiselect or checkmatrix.
3180  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3181  foreach ( $specialOptions as $name => $value ) {
3182  unset( $prefs[$name] );
3183  }
3184 
3185  // Multiselect and checkmatrix options are stored in the database with
3186  // one key per option, each having a boolean value. Extract those keys.
3187  $multiselectOptions = [];
3188  foreach ( $prefs as $name => $info ) {
3189  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3190  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3191  $opts = HTMLFormField::flattenOptions( $info['options'] );
3192  $prefix = $info['prefix'] ?? $name;
3193 
3194  foreach ( $opts as $value ) {
3195  $multiselectOptions["$prefix$value"] = true;
3196  }
3197 
3198  unset( $prefs[$name] );
3199  }
3200  }
3201  $checkmatrixOptions = [];
3202  foreach ( $prefs as $name => $info ) {
3203  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3204  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3205  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3206  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3207  $prefix = $info['prefix'] ?? $name;
3208 
3209  foreach ( $columns as $column ) {
3210  foreach ( $rows as $row ) {
3211  $checkmatrixOptions["$prefix$column-$row"] = true;
3212  }
3213  }
3214 
3215  unset( $prefs[$name] );
3216  }
3217  }
3218 
3219  // $value is ignored
3220  foreach ( $options as $key => $value ) {
3221  if ( isset( $prefs[$key] ) ) {
3222  $mapping[$key] = 'registered';
3223  } elseif ( isset( $multiselectOptions[$key] ) ) {
3224  $mapping[$key] = 'registered-multiselect';
3225  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3226  $mapping[$key] = 'registered-checkmatrix';
3227  } elseif ( isset( $specialOptions[$key] ) ) {
3228  $mapping[$key] = 'special';
3229  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3230  $mapping[$key] = 'userjs';
3231  } else {
3232  $mapping[$key] = 'unused';
3233  }
3234  }
3235 
3236  return $mapping;
3237  }
3238 
3253  public function resetOptions(
3254  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3255  IContextSource $context = null
3256  ) {
3257  $this->load();
3258  $defaultOptions = self::getDefaultOptions();
3259 
3260  if ( !is_array( $resetKinds ) ) {
3261  $resetKinds = [ $resetKinds ];
3262  }
3263 
3264  if ( in_array( 'all', $resetKinds ) ) {
3265  $newOptions = $defaultOptions;
3266  } else {
3267  if ( $context === null ) {
3269  }
3270 
3271  $optionKinds = $this->getOptionKinds( $context );
3272  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3273  $newOptions = [];
3274 
3275  // Use default values for the options that should be deleted, and
3276  // copy old values for the ones that shouldn't.
3277  foreach ( $this->mOptions as $key => $value ) {
3278  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3279  if ( array_key_exists( $key, $defaultOptions ) ) {
3280  $newOptions[$key] = $defaultOptions[$key];
3281  }
3282  } else {
3283  $newOptions[$key] = $value;
3284  }
3285  }
3286  }
3287 
3288  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3289 
3290  $this->mOptions = $newOptions;
3291  $this->mOptionsLoaded = true;
3292  }
3293 
3298  public function getDatePreference() {
3299  // Important migration for old data rows
3300  if ( $this->mDatePreference === null ) {
3301  global $wgLang;
3302  $value = $this->getOption( 'date' );
3303  $map = $wgLang->getDatePreferenceMigrationMap();
3304  if ( isset( $map[$value] ) ) {
3305  $value = $map[$value];
3306  }
3307  $this->mDatePreference = $value;
3308  }
3309  return $this->mDatePreference;
3310  }
3311 
3318  public function requiresHTTPS() {
3319  global $wgSecureLogin;
3320  if ( !$wgSecureLogin ) {
3321  return false;
3322  }
3323 
3324  $https = $this->getBoolOption( 'prefershttps' );
3325  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3326  if ( $https ) {
3327  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3328  }
3329 
3330  return $https;
3331  }
3332 
3338  public function getStubThreshold() {
3339  global $wgMaxArticleSize; # Maximum article size, in Kb
3340  $threshold = $this->getIntOption( 'stubthreshold' );
3341  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3342  // If they have set an impossible value, disable the preference
3343  // so we can use the parser cache again.
3344  $threshold = 0;
3345  }
3346  return $threshold;
3347  }
3348 
3357  public function getRights() {
3358  return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
3359  }
3360 
3367  public function getGroups() {
3368  $this->load();
3369  $this->loadGroups();
3370  return array_keys( $this->mGroupMemberships );
3371  }
3372 
3380  public function getGroupMemberships() {
3381  $this->load();
3382  $this->loadGroups();
3383  return $this->mGroupMemberships;
3384  }
3385 
3393  public function getEffectiveGroups( $recache = false ) {
3394  if ( $recache || $this->mEffectiveGroups === null ) {
3395  $this->mEffectiveGroups = array_unique( array_merge(
3396  $this->getGroups(), // explicit groups
3397  $this->getAutomaticGroups( $recache ) // implicit groups
3398  ) );
3399  // Avoid PHP 7.1 warning of passing $this by reference
3400  $user = $this;
3401  // Hook for additional groups
3402  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3403  // Force reindexation of groups when a hook has unset one of them
3404  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3405  }
3406  return $this->mEffectiveGroups;
3407  }
3408 
3416  public function getAutomaticGroups( $recache = false ) {
3417  if ( $recache || $this->mImplicitGroups === null ) {
3418  $this->mImplicitGroups = [ '*' ];
3419  if ( $this->getId() ) {
3420  $this->mImplicitGroups[] = 'user';
3421 
3422  $this->mImplicitGroups = array_unique( array_merge(
3423  $this->mImplicitGroups,
3425  ) );
3426  }
3427  if ( $recache ) {
3428  // Assure data consistency with rights/groups,
3429  // as getEffectiveGroups() depends on this function
3430  $this->mEffectiveGroups = null;
3431  }
3432  }
3433  return $this->mImplicitGroups;
3434  }
3435 
3445  public function getFormerGroups() {
3446  $this->load();
3447 
3448  if ( $this->mFormerGroups === null ) {
3449  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3450  ? wfGetDB( DB_MASTER )
3451  : wfGetDB( DB_REPLICA );
3452  $res = $db->select( 'user_former_groups',
3453  [ 'ufg_group' ],
3454  [ 'ufg_user' => $this->mId ],
3455  __METHOD__ );
3456  $this->mFormerGroups = [];
3457  foreach ( $res as $row ) {
3458  $this->mFormerGroups[] = $row->ufg_group;
3459  }
3460  }
3461 
3462  return $this->mFormerGroups;
3463  }
3464 
3469  public function getEditCount() {
3470  if ( !$this->getId() ) {
3471  return null;
3472  }
3473 
3474  if ( $this->mEditCount === null ) {
3475  /* Populate the count, if it has not been populated yet */
3476  $dbr = wfGetDB( DB_REPLICA );
3477  // check if the user_editcount field has been initialized
3478  $count = $dbr->selectField(
3479  'user', 'user_editcount',
3480  [ 'user_id' => $this->mId ],
3481  __METHOD__
3482  );
3483 
3484  if ( $count === null ) {
3485  // it has not been initialized. do so.
3486  $count = $this->initEditCountInternal( $dbr );
3487  }
3488  $this->mEditCount = $count;
3489  }
3490  return (int)$this->mEditCount;
3491  }
3492 
3504  public function addGroup( $group, $expiry = null ) {
3505  $this->load();
3506  $this->loadGroups();
3507 
3508  if ( $expiry ) {
3509  $expiry = wfTimestamp( TS_MW, $expiry );
3510  }
3511 
3512  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3513  return false;
3514  }
3515 
3516  // create the new UserGroupMembership and put it in the DB
3517  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3518  if ( !$ugm->insert( true ) ) {
3519  return false;
3520  }
3521 
3522  $this->mGroupMemberships[$group] = $ugm;
3523 
3524  // Refresh the groups caches, and clear the rights cache so it will be
3525  // refreshed on the next call to $this->getRights().
3526  $this->getEffectiveGroups( true );
3527  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3528  $this->invalidateCache();
3529 
3530  return true;
3531  }
3532 
3539  public function removeGroup( $group ) {
3540  $this->load();
3541 
3542  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3543  return false;
3544  }
3545 
3546  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3547  // delete the membership entry
3548  if ( !$ugm || !$ugm->delete() ) {
3549  return false;
3550  }
3551 
3552  $this->loadGroups();
3553  unset( $this->mGroupMemberships[$group] );
3554 
3555  // Refresh the groups caches, and clear the rights cache so it will be
3556  // refreshed on the next call to $this->getRights().
3557  $this->getEffectiveGroups( true );
3558  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3559  $this->invalidateCache();
3560 
3561  return true;
3562  }
3563 
3573  public function isRegistered() {
3574  return $this->getId() != 0;
3575  }
3576 
3581  public function isLoggedIn() {
3582  return $this->isRegistered();
3583  }
3584 
3589  public function isAnon() {
3590  return !$this->isRegistered();
3591  }
3592 
3597  public function isBot() {
3598  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3599  return true;
3600  }
3601 
3602  $isBot = false;
3603  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3604 
3605  return $isBot;
3606  }
3607 
3617  public function isSystemUser() {
3618  $this->load();
3619  if ( $this->mEmail || $this->mToken !== self::INVALID_TOKEN ||
3620  AuthManager::singleton()->userCanAuthenticate( $this->mName )
3621  ) {
3622  return false;
3623  }
3624  return true;
3625  }
3626 
3636  public function isAllowedAny( ...$permissions ) {
3637  return MediaWikiServices::getInstance()
3638  ->getPermissionManager()
3639  ->userHasAnyRight( $this, ...$permissions );
3640  }
3641 
3648  public function isAllowedAll( ...$permissions ) {
3649  return MediaWikiServices::getInstance()
3650  ->getPermissionManager()
3651  ->userHasAllRights( $this, ...$permissions );
3652  }
3653 
3664  public function isAllowed( $action = '' ) {
3665  return MediaWikiServices::getInstance()->getPermissionManager()
3666  ->userHasRight( $this, $action );
3667  }
3668 
3673  public function useRCPatrol() {
3674  global $wgUseRCPatrol;
3675  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3676  }
3677 
3682  public function useNPPatrol() {
3684  return (
3686  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3687  );
3688  }
3689 
3694  public function useFilePatrol() {
3696  return (
3698  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3699  );
3700  }
3701 
3707  public function getRequest() {
3708  if ( $this->mRequest ) {
3709  return $this->mRequest;
3710  }
3711 
3712  global $wgRequest;
3713  return $wgRequest;
3714  }
3715 
3724  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3725  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3726  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3727  }
3728  return false;
3729  }
3730 
3738  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3739  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3740  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3741  $this,
3742  [ $title->getSubjectPage(), $title->getTalkPage() ]
3743  );
3744  }
3745  $this->invalidateCache();
3746  }
3747 
3755  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3756  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3757  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3758  $store->removeWatch( $this, $title->getSubjectPage() );
3759  $store->removeWatch( $this, $title->getTalkPage() );
3760  }
3761  $this->invalidateCache();
3762  }
3763 
3772  public function clearNotification( &$title, $oldid = 0 ) {
3774 
3775  // Do nothing if the database is locked to writes
3776  if ( wfReadOnly() ) {
3777  return;
3778  }
3779 
3780  // Do nothing if not allowed to edit the watchlist
3781  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3782  return;
3783  }
3784 
3785  // If we're working on user's talk page, we should update the talk page message indicator
3786  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3787  // Avoid PHP 7.1 warning of passing $this by reference
3788  $user = $this;
3789  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3790  return;
3791  }
3792 
3793  // Try to update the DB post-send and only if needed...
3794  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3795  if ( !$this->getNewtalk() ) {
3796  return; // no notifications to clear
3797  }
3798 
3799  // Delete the last notifications (they stack up)
3800  $this->setNewtalk( false );
3801 
3802  // If there is a new, unseen, revision, use its timestamp
3803  if ( $oldid ) {
3804  $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3805  $oldRev = $rl->getRevisionById( $oldid, Title::READ_LATEST );
3806  if ( $oldRev ) {
3807  $newRev = $rl->getNextRevision( $oldRev );
3808  if ( $newRev ) {
3809  // TODO: actually no need to wrap in a revision,
3810  // setNewtalk really only needs a RevRecord
3811  $this->setNewtalk( true, new Revision( $newRev ) );
3812  }
3813  }
3814  }
3815  } );
3816  }
3817 
3818  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3819  return;
3820  }
3821 
3822  if ( $this->isAnon() ) {
3823  // Nothing else to do...
3824  return;
3825  }
3826 
3827  // Only update the timestamp if the page is being watched.
3828  // The query to find out if it is watched is cached both in memcached and per-invocation,
3829  // and when it does have to be executed, it can be on a replica DB
3830  // If this is the user's newtalk page, we always update the timestamp
3831  $force = '';
3832  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3833  $force = 'force';
3834  }
3835 
3836  MediaWikiServices::getInstance()->getWatchedItemStore()
3837  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3838  }
3839 
3846  public function clearAllNotifications() {
3848  // Do nothing if not allowed to edit the watchlist
3849  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3850  return;
3851  }
3852 
3853  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3854  $this->setNewtalk( false );
3855  return;
3856  }
3857 
3858  $id = $this->getId();
3859  if ( !$id ) {
3860  return;
3861  }
3862 
3863  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
3864  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
3865 
3866  // We also need to clear here the "you have new message" notification for the own
3867  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3868  }
3869 
3875  public function getExperienceLevel() {
3876  global $wgLearnerEdits,
3880 
3881  if ( $this->isAnon() ) {
3882  return false;
3883  }
3884 
3885  $editCount = $this->getEditCount();
3886  $registration = $this->getRegistration();
3887  $now = time();
3888  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3889  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3890 
3891  if ( $editCount < $wgLearnerEdits ||
3892  $registration > $learnerRegistration ) {
3893  return 'newcomer';
3894  }
3895 
3896  if ( $editCount > $wgExperiencedUserEdits &&
3897  $registration <= $experiencedRegistration
3898  ) {
3899  return 'experienced';
3900  }
3901 
3902  return 'learner';
3903  }
3904 
3913  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3914  $this->load();
3915  if ( $this->mId == 0 ) {
3916  return;
3917  }
3918 
3919  $session = $this->getRequest()->getSession();
3920  if ( $request && $session->getRequest() !== $request ) {
3921  $session = $session->sessionWithRequest( $request );
3922  }
3923  $delay = $session->delaySave();
3924 
3925  if ( !$session->getUser()->equals( $this ) ) {
3926  if ( !$session->canSetUser() ) {
3928  ->warning( __METHOD__ .
3929  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3930  );
3931  return;
3932  }
3933  $session->setUser( $this );
3934  }
3935 
3936  $session->setRememberUser( $rememberMe );
3937  if ( $secure !== null ) {
3938  $session->setForceHTTPS( $secure );
3939  }
3940 
3941  $session->persist();
3942 
3943  ScopedCallback::consume( $delay );
3944  }
3945 
3949  public function logout() {
3950  // Avoid PHP 7.1 warning of passing $this by reference
3951  $user = $this;
3952  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
3953  $this->doLogout();
3954  }
3955  }
3956 
3961  public function doLogout() {
3962  $session = $this->getRequest()->getSession();
3963  if ( !$session->canSetUser() ) {
3965  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3966  $error = 'immutable';
3967  } elseif ( !$session->getUser()->equals( $this ) ) {
3969  ->warning( __METHOD__ .
3970  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3971  );
3972  // But we still may as well make this user object anon
3973  $this->clearInstanceCache( 'defaults' );
3974  $error = 'wronguser';
3975  } else {
3976  $this->clearInstanceCache( 'defaults' );
3977  $delay = $session->delaySave();
3978  $session->unpersist(); // Clear cookies (T127436)
3979  $session->setLoggedOutTimestamp( time() );
3980  $session->setUser( new User );
3981  $session->set( 'wsUserID', 0 ); // Other code expects this
3982  $session->resetAllTokens();
3983  ScopedCallback::consume( $delay );
3984  $error = false;
3985  }
3986  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3987  'event' => 'logout',
3988  'successful' => $error === false,
3989  'status' => $error ?: 'success',
3990  ] );
3991  }
3992 
3997  public function saveSettings() {
3998  if ( wfReadOnly() ) {
3999  // @TODO: caller should deal with this instead!
4000  // This should really just be an exception.
4002  null,
4003  "Could not update user with ID '{$this->mId}'; DB is read-only."
4004  ) );
4005  return;
4006  }
4007 
4008  $this->load();
4009  if ( $this->mId == 0 ) {
4010  return; // anon
4011  }
4012 
4013  // Get a new user_touched that is higher than the old one.
4014  // This will be used for a CAS check as a last-resort safety
4015  // check against race conditions and replica DB lag.
4016  $newTouched = $this->newTouchedTimestamp();
4017 
4018  $dbw = wfGetDB( DB_MASTER );
4019  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
4020  $dbw->update( 'user',
4021  [ /* SET */
4022  'user_name' => $this->mName,
4023  'user_real_name' => $this->mRealName,
4024  'user_email' => $this->mEmail,
4025  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4026  'user_touched' => $dbw->timestamp( $newTouched ),
4027  'user_token' => strval( $this->mToken ),
4028  'user_email_token' => $this->mEmailToken,
4029  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4030  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4031  'user_id' => $this->mId,
4032  ] ), $fname
4033  );
4034 
4035  if ( !$dbw->affectedRows() ) {
4036  // Maybe the problem was a missed cache update; clear it to be safe
4037  $this->clearSharedCache( 'refresh' );
4038  // User was changed in the meantime or loaded with stale data
4039  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4040  LoggerFactory::getInstance( 'preferences' )->warning(
4041  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4042  [ 'user_id' => $this->mId, 'db_flag' => $from ]
4043  );
4044  throw new MWException( "CAS update failed on user_touched. " .
4045  "The version of the user to be saved is older than the current version."
4046  );
4047  }
4048 
4049  $dbw->update(
4050  'actor',
4051  [ 'actor_name' => $this->mName ],
4052  [ 'actor_user' => $this->mId ],
4053  $fname
4054  );
4055  } );
4056 
4057  $this->mTouched = $newTouched;
4058  $this->saveOptions();
4059 
4060  Hooks::run( 'UserSaveSettings', [ $this ] );
4061  $this->clearSharedCache( 'changed' );
4062  $this->getUserPage()->purgeSquid();
4063  }
4064 
4071  public function idForName( $flags = 0 ) {
4072  $s = trim( $this->getName() );
4073  if ( $s === '' ) {
4074  return 0;
4075  }
4076 
4077  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4078  ? wfGetDB( DB_MASTER )
4079  : wfGetDB( DB_REPLICA );
4080 
4081  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4082  ? [ 'LOCK IN SHARE MODE' ]
4083  : [];
4084 
4085  $id = $db->selectField( 'user',
4086  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4087 
4088  return (int)$id;
4089  }
4090 
4106  public static function createNew( $name, $params = [] ) {
4107  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4108  if ( isset( $params[$field] ) ) {
4109  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4110  unset( $params[$field] );
4111  }
4112  }
4113 
4114  $user = new User;
4115  $user->load();
4116  $user->setToken(); // init token
4117  if ( isset( $params['options'] ) ) {
4118  $user->mOptions = $params['options'] + (array)$user->mOptions;
4119  unset( $params['options'] );
4120  }
4121  $dbw = wfGetDB( DB_MASTER );
4122 
4123  $noPass = PasswordFactory::newInvalidPassword()->toString();
4124 
4125  $fields = [
4126  'user_name' => $name,
4127  'user_password' => $noPass,
4128  'user_newpassword' => $noPass,
4129  'user_email' => $user->mEmail,
4130  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4131  'user_real_name' => $user->mRealName,
4132  'user_token' => strval( $user->mToken ),
4133  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4134  'user_editcount' => 0,
4135  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4136  ];
4137  foreach ( $params as $name => $value ) {
4138  $fields["user_$name"] = $value;
4139  }
4140 
4141  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
4142  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4143  if ( $dbw->affectedRows() ) {
4144  $newUser = self::newFromId( $dbw->insertId() );
4145  $newUser->mName = $fields['user_name'];
4146  $newUser->updateActorId( $dbw );
4147  // Load the user from master to avoid replica lag
4148  $newUser->load( self::READ_LATEST );
4149  } else {
4150  $newUser = null;
4151  }
4152  return $newUser;
4153  } );
4154  }
4155 
4182  public function addToDatabase() {
4183  $this->load();
4184  if ( !$this->mToken ) {
4185  $this->setToken(); // init token
4186  }
4187 
4188  if ( !is_string( $this->mName ) ) {
4189  throw new RuntimeException( "User name field is not set." );
4190  }
4191 
4192  $this->mTouched = $this->newTouchedTimestamp();
4193 
4194  $dbw = wfGetDB( DB_MASTER );
4195  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
4196  $noPass = PasswordFactory::newInvalidPassword()->toString();
4197  $dbw->insert( 'user',
4198  [
4199  'user_name' => $this->mName,
4200  'user_password' => $noPass,
4201  'user_newpassword' => $noPass,
4202  'user_email' => $this->mEmail,
4203  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4204  'user_real_name' => $this->mRealName,
4205  'user_token' => strval( $this->mToken ),
4206  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4207  'user_editcount' => 0,
4208  'user_touched' => $dbw->timestamp( $this->mTouched ),
4209  ], $fname,
4210  [ 'IGNORE' ]
4211  );
4212  if ( !$dbw->affectedRows() ) {
4213  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4214  $this->mId = $dbw->selectField(
4215  'user',
4216  'user_id',
4217  [ 'user_name' => $this->mName ],
4218  $fname,
4219  [ 'LOCK IN SHARE MODE' ]
4220  );
4221  $loaded = false;
4222  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
4223  $loaded = true;
4224  }
4225  if ( !$loaded ) {
4226  throw new MWException( $fname . ": hit a key conflict attempting " .
4227  "to insert user '{$this->mName}' row, but it was not present in select!" );
4228  }
4229  return Status::newFatal( 'userexists' );
4230  }
4231  $this->mId = $dbw->insertId();
4232  self::$idCacheByName[$this->mName] = $this->mId;
4233  $this->updateActorId( $dbw );
4234 
4235  return Status::newGood();
4236  } );
4237  if ( !$status->isGood() ) {
4238  return $status;
4239  }
4240 
4241  // Clear instance cache other than user table data and actor, which is already accurate
4242  $this->clearInstanceCache();
4243 
4244  $this->saveOptions();
4245  return Status::newGood();
4246  }
4247 
4252  private function updateActorId( IDatabase $dbw ) {
4253  $dbw->insert(
4254  'actor',
4255  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4256  __METHOD__
4257  );
4258  $this->mActorId = (int)$dbw->insertId();
4259  }
4260 
4266  public function spreadAnyEditBlock() {
4267  if ( $this->isLoggedIn() && $this->getBlock() ) {
4268  return $this->spreadBlock();
4269  }
4270 
4271  return false;
4272  }
4273 
4279  protected function spreadBlock() {
4280  wfDebug( __METHOD__ . "()\n" );
4281  $this->load();
4282  if ( $this->mId == 0 ) {
4283  return false;
4284  }
4285 
4286  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
4287  if ( !$userblock ) {
4288  return false;
4289  }
4290 
4291  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4292  }
4293 
4298  public function isBlockedFromCreateAccount() {
4299  $this->getBlockedStatus();
4300  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4301  return $this->mBlock;
4302  }
4303 
4304  # T15611: if the IP address the user is trying to create an account from is
4305  # blocked with createaccount disabled, prevent new account creation there even
4306  # when the user is logged in
4307  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4308  $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
4309  null, $this->getRequest()->getIP()
4310  );
4311  }
4312  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
4313  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4314  ? $this->mBlockedFromCreateAccount
4315  : false;
4316  }
4317 
4322  public function isBlockedFromEmailuser() {
4323  $this->getBlockedStatus();
4324  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4325  }
4326 
4333  public function isBlockedFromUpload() {
4334  $this->getBlockedStatus();
4335  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4336  }
4337 
4342  public function isAllowedToCreateAccount() {
4343  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4344  }
4345 
4351  public function getUserPage() {
4352  return Title::makeTitle( NS_USER, $this->getName() );
4353  }
4354 
4360  public function getTalkPage() {
4361  $title = $this->getUserPage();
4362  return $title->getTalkPage();
4363  }
4364 
4370  public function isNewbie() {
4371  return !$this->isAllowed( 'autoconfirmed' );
4372  }
4373 
4380  public function checkPassword( $password ) {
4381  wfDeprecated( __METHOD__, '1.27' );
4382 
4383  $manager = AuthManager::singleton();
4384  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4385  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4386  [
4387  'username' => $this->getName(),
4388  'password' => $password,
4389  ]
4390  );
4391  $res = $manager->beginAuthentication( $reqs, 'null:' );
4392  switch ( $res->status ) {
4393  case AuthenticationResponse::PASS:
4394  return true;
4395  case AuthenticationResponse::FAIL:
4396  // Hope it's not a PreAuthenticationProvider that failed...
4397  LoggerFactory::getInstance( 'authentication' )
4398  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4399  return false;
4400  default:
4401  throw new BadMethodCallException(
4402  'AuthManager returned a response unsupported by ' . __METHOD__
4403  );
4404  }
4405  }
4406 
4415  public function checkTemporaryPassword( $plaintext ) {
4416  wfDeprecated( __METHOD__, '1.27' );
4417  // Can't check the temporary password individually.
4418  return $this->checkPassword( $plaintext );
4419  }
4420 
4432  public function getEditTokenObject( $salt = '', $request = null ) {
4433  if ( $this->isAnon() ) {
4434  return new LoggedOutEditToken();
4435  }
4436 
4437  if ( !$request ) {
4438  $request = $this->getRequest();
4439  }
4440  return $request->getSession()->getToken( $salt );
4441  }
4442 
4456  public function getEditToken( $salt = '', $request = null ) {
4457  return $this->getEditTokenObject( $salt, $request )->toString();
4458  }
4459 
4472  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4473  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4474  }
4475 
4486  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4487  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4488  return $this->matchEditToken( $val, $salt, $request, $maxage );
4489  }
4490 
4498  public function sendConfirmationMail( $type = 'created' ) {
4499  global $wgLang;
4500  $expiration = null; // gets passed-by-ref and defined in next line.
4501  $token = $this->confirmationToken( $expiration );
4502  $url = $this->confirmationTokenUrl( $token );
4503  $invalidateURL = $this->invalidationTokenUrl( $token );
4504  $this->saveSettings();
4505 
4506  if ( $type == 'created' || $type === false ) {
4507  $message = 'confirmemail_body';
4508  $type = 'created';
4509  } elseif ( $type === true ) {
4510  $message = 'confirmemail_body_changed';
4511  $type = 'changed';
4512  } else {
4513  // Messages: confirmemail_body_changed, confirmemail_body_set
4514  $message = 'confirmemail_body_' . $type;
4515  }
4516 
4517  $mail = [
4518  'subject' => wfMessage( 'confirmemail_subject' )->text(),
4519  'body' => wfMessage( $message,
4520  $this->getRequest()->getIP(),
4521  $this->getName(),
4522  $url,
4523  $wgLang->userTimeAndDate( $expiration, $this ),
4524  $invalidateURL,
4525  $wgLang->userDate( $expiration, $this ),
4526  $wgLang->userTime( $expiration, $this ) )->text(),
4527  'from' => null,
4528  'replyTo' => null,
4529  ];
4530  $info = [
4531  'type' => $type,
4532  'ip' => $this->getRequest()->getIP(),
4533  'confirmURL' => $url,
4534  'invalidateURL' => $invalidateURL,
4535  'expiration' => $expiration
4536  ];
4537 
4538  Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4539  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4540  }
4541 
4553  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4554  global $wgPasswordSender;
4555 
4556  if ( $from instanceof User ) {
4557  $sender = MailAddress::newFromUser( $from );
4558  } else {
4559  $sender = new MailAddress( $wgPasswordSender,
4560  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4561  }
4562  $to = MailAddress::newFromUser( $this );
4563 
4564  return UserMailer::send( $to, $sender, $subject, $body, [
4565  'replyTo' => $replyto,
4566  ] );
4567  }
4568 
4579  protected function confirmationToken( &$expiration ) {
4581  $now = time();
4582  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4583  $expiration = wfTimestamp( TS_MW, $expires );
4584  $this->load();
4585  $token = MWCryptRand::generateHex( 32 );
4586  $hash = md5( $token );
4587  $this->mEmailToken = $hash;
4588  $this->mEmailTokenExpires = $expiration;
4589  return $token;
4590  }
4591 
4597  protected function confirmationTokenUrl( $token ) {
4598  return $this->getTokenUrl( 'ConfirmEmail', $token );
4599  }
4600 
4606  protected function invalidationTokenUrl( $token ) {
4607  return $this->getTokenUrl( 'InvalidateEmail', $token );
4608  }
4609 
4624  protected function getTokenUrl( $page, $token ) {
4625  // Hack to bypass localization of 'Special:'
4626  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4627  return $title->getCanonicalURL();
4628  }
4629 
4637  public function confirmEmail() {
4638  // Check if it's already confirmed, so we don't touch the database
4639  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4640  if ( !$this->isEmailConfirmed() ) {
4642  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4643  }
4644  return true;
4645  }
4646 
4654  public function invalidateEmail() {
4655  $this->load();
4656  $this->mEmailToken = null;
4657  $this->mEmailTokenExpires = null;
4658  $this->setEmailAuthenticationTimestamp( null );
4659  $this->mEmail = '';
4660  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4661  return true;
4662  }
4663 
4668  public function setEmailAuthenticationTimestamp( $timestamp ) {
4669  $this->load();
4670  $this->mEmailAuthenticated = $timestamp;
4671  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4672  }
4673 
4679  public function canSendEmail() {
4681  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4682  return false;
4683  }
4684  $canSend = $this->isEmailConfirmed();
4685  // Avoid PHP 7.1 warning of passing $this by reference
4686  $user = $this;
4687  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4688  return $canSend;
4689  }
4690 
4696  public function canReceiveEmail() {
4697  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4698  }
4699 
4710  public function isEmailConfirmed() {
4711  global $wgEmailAuthentication;
4712  $this->load();
4713  // Avoid PHP 7.1 warning of passing $this by reference
4714  $user = $this;
4715  $confirmed = true;
4716  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4717  if ( $this->isAnon() ) {
4718  return false;
4719  }
4720  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4721  return false;
4722  }
4724  return false;
4725  }
4726  return true;
4727  }
4728 
4729  return $confirmed;
4730  }
4731 
4736  public function isEmailConfirmationPending() {
4737  global $wgEmailAuthentication;
4738  return $wgEmailAuthentication &&
4739  !$this->isEmailConfirmed() &&
4740  $this->mEmailToken &&
4741  $this->mEmailTokenExpires > wfTimestamp();
4742  }
4743 
4751  public function getRegistration() {
4752  if ( $this->isAnon() ) {
4753  return false;
4754  }
4755  $this->load();
4756  return $this->mRegistration;
4757  }
4758 
4765  public function getFirstEditTimestamp() {
4766  return $this->getEditTimestamp( true );
4767  }
4768 
4776  public function getLatestEditTimestamp() {
4777  return $this->getEditTimestamp( false );
4778  }
4779 
4787  private function getEditTimestamp( $first ) {
4788  if ( $this->getId() == 0 ) {
4789  return false; // anons
4790  }
4791  $dbr = wfGetDB( DB_REPLICA );
4792  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4793  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4794  ? 'revactor_timestamp' : 'rev_timestamp';
4795  $sortOrder = $first ? 'ASC' : 'DESC';
4796  $time = $dbr->selectField(
4797  [ 'revision' ] + $actorWhere['tables'],
4798  $tsField,
4799  [ $actorWhere['conds'] ],
4800  __METHOD__,
4801  [ 'ORDER BY' => "$tsField $sortOrder" ],
4802  $actorWhere['joins']
4803  );
4804  if ( !$time ) {
4805  return false; // no edits
4806  }
4807  return wfTimestamp( TS_MW, $time );
4808  }
4809 
4819  public static function getGroupPermissions( $groups ) {
4820  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4821  }
4822 
4832  public static function getGroupsWithPermission( $role ) {
4833  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4834  }
4835 
4851  public static function groupHasPermission( $group, $role ) {
4852  return MediaWikiServices::getInstance()->getPermissionManager()
4853  ->groupHasPermission( $group, $role );
4854  }
4855 
4873  public static function isEveryoneAllowed( $right ) {
4874  wfDeprecated( __METHOD__, '1.34' );
4875  return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4876  }
4877 
4884  public static function getAllGroups() {
4886  return array_values( array_diff(
4887  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4888  self::getImplicitGroups()
4889  ) );
4890  }
4891 
4899  public static function getAllRights() {
4900  wfDeprecated( __METHOD__, '1.34' );
4901  return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4902  }
4903 
4910  public static function getImplicitGroups() {
4911  global $wgImplicitGroups;
4912  return $wgImplicitGroups;
4913  }
4914 
4924  public static function changeableByGroup( $group ) {
4926 
4927  $groups = [
4928  'add' => [],
4929  'remove' => [],
4930  'add-self' => [],
4931  'remove-self' => []
4932  ];
4933 
4934  if ( empty( $wgAddGroups[$group] ) ) {
4935  // Don't add anything to $groups
4936  } elseif ( $wgAddGroups[$group] === true ) {
4937  // You get everything
4938  $groups['add'] = self::getAllGroups();
4939  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4940  $groups['add'] = $wgAddGroups[$group];
4941  }
4942 
4943  // Same thing for remove
4944  if ( empty( $wgRemoveGroups[$group] ) ) {
4945  // Do nothing
4946  } elseif ( $wgRemoveGroups[$group] === true ) {
4947  $groups['remove'] = self::getAllGroups();
4948  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4949  $groups['remove'] = $wgRemoveGroups[$group];
4950  }
4951 
4952  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4953  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4954  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4955  if ( is_int( $key ) ) {
4956  $wgGroupsAddToSelf['user'][] = $value;
4957  }
4958  }
4959  }
4960 
4961  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4962  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4963  if ( is_int( $key ) ) {
4964  $wgGroupsRemoveFromSelf['user'][] = $value;
4965  }
4966  }
4967  }
4968 
4969  // Now figure out what groups the user can add to him/herself
4970  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4971  // Do nothing
4972  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4973  // No idea WHY this would be used, but it's there
4974  $groups['add-self'] = self::getAllGroups();
4975  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4976  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4977  }
4978 
4979  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4980  // Do nothing
4981  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4982  $groups['remove-self'] = self::getAllGroups();
4983  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4984  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4985  }
4986 
4987  return $groups;
4988  }
4989 
4997  public function changeableGroups() {
4998  if ( $this->isAllowed( 'userrights' ) ) {
4999  // This group gives the right to modify everything (reverse-
5000  // compatibility with old "userrights lets you change
5001  // everything")
5002  // Using array_merge to make the groups reindexed
5003  $all = array_merge( self::getAllGroups() );
5004  return [
5005  'add' => $all,
5006  'remove' => $all,
5007  'add-self' => [],
5008  'remove-self' => []
5009  ];
5010  }
5011 
5012  // Okay, it's not so simple, we will have to go through the arrays
5013  $groups = [
5014  'add' => [],
5015  'remove' => [],
5016  'add-self' => [],
5017  'remove-self' => []
5018  ];
5019  $addergroups = $this->getEffectiveGroups();
5020 
5021  foreach ( $addergroups as $addergroup ) {
5022  $groups = array_merge_recursive(
5023  $groups, $this->changeableByGroup( $addergroup )
5024  );
5025  $groups['add'] = array_unique( $groups['add'] );
5026  $groups['remove'] = array_unique( $groups['remove'] );
5027  $groups['add-self'] = array_unique( $groups['add-self'] );
5028  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5029  }
5030  return $groups;
5031  }
5032 
5036  public function incEditCount() {
5037  if ( $this->isAnon() ) {
5038  return; // sanity
5039  }
5040 
5042  new UserEditCountUpdate( $this, 1 ),
5044  );
5045  }
5046 
5052  public function setEditCountInternal( $count ) {
5053  $this->mEditCount = $count;
5054  }
5055 
5063  public function initEditCountInternal( IDatabase $dbr ) {
5064  // Pull from a replica DB to be less cruel to servers
5065  // Accuracy isn't the point anyway here
5066  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5067  $count = (int)$dbr->selectField(
5068  [ 'revision' ] + $actorWhere['tables'],
5069  'COUNT(*)',
5070  [ $actorWhere['conds'] ],
5071  __METHOD__,
5072  [],
5073  $actorWhere['joins']
5074  );
5075 
5076  $dbw = wfGetDB( DB_MASTER );
5077  $dbw->update(
5078  'user',
5079  [ 'user_editcount' => $count ],
5080  [
5081  'user_id' => $this->getId(),
5082  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5083  ],
5084  __METHOD__
5085  );
5086 
5087  return $count;
5088  }
5089 
5097  public static function getRightDescription( $right ) {
5098  $key = "right-$right";
5099  $msg = wfMessage( $key );
5100  return $msg->isDisabled() ? $right : $msg->text();
5101  }
5102 
5110  public static function getGrantName( $grant ) {
5111  $key = "grant-$grant";
5112  $msg = wfMessage( $key );
5113  return $msg->isDisabled() ? $grant : $msg->text();
5114  }
5115 
5136  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5137  return true; // disabled
5138  }
5139 
5145  protected function loadOptions( $data = null ) {
5146  $this->load();
5147 
5148  if ( $this->mOptionsLoaded ) {
5149  return;
5150  }
5151 
5152  $this->mOptions = self::getDefaultOptions();
5153 
5154  if ( !$this->getId() ) {
5155  // For unlogged-in users, load language/variant options from request.
5156  // There's no need to do it for logged-in users: they can set preferences,
5157  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5158  // so don't override user's choice (especially when the user chooses site default).
5159  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5160  $this->mOptions['variant'] = $variant;
5161  $this->mOptions['language'] = $variant;
5162  $this->mOptionsLoaded = true;
5163  return;
5164  }
5165 
5166  // Maybe load from the object
5167  if ( $this->mOptionOverrides !== null ) {
5168  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5169  foreach ( $this->mOptionOverrides as $key => $value ) {
5170  $this->mOptions[$key] = $value;
5171  }
5172  } else {
5173  if ( !is_array( $data ) ) {
5174  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5175  // Load from database
5176  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5177  ? wfGetDB( DB_MASTER )
5178  : wfGetDB( DB_REPLICA );
5179 
5180  $res = $dbr->select(
5181  'user_properties',
5182  [ 'up_property', 'up_value' ],
5183  [ 'up_user' => $this->getId() ],
5184  __METHOD__
5185  );
5186 
5187  $this->mOptionOverrides = [];
5188  $data = [];
5189  foreach ( $res as $row ) {
5190  // Convert '0' to 0. PHP's boolean conversion considers them both
5191  // false, but e.g. JavaScript considers the former as true.
5192  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5193  // and convert all values here.
5194  if ( $row->up_value === '0' ) {
5195  $row->up_value = 0;
5196  }
5197  $data[$row->up_property] = $row->up_value;
5198  }
5199  }
5200 
5201  foreach ( $data as $property => $value ) {
5202  $this->mOptionOverrides[$property] = $value;
5203  $this->mOptions[$property] = $value;
5204  }
5205  }
5206 
5207  // Replace deprecated language codes
5208  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5209  $this->mOptions['language']
5210  );
5211 
5212  $this->mOptionsLoaded = true;
5213 
5214  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5215  }
5216 
5222  protected function saveOptions() {
5223  $this->loadOptions();
5224 
5225  // Not using getOptions(), to keep hidden preferences in database
5226  $saveOptions = $this->mOptions;
5227 
5228  // Allow hooks to abort, for instance to save to a global profile.
5229  // Reset options to default state before saving.
5230  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5231  return;
5232  }
5233 
5234  $userId = $this->getId();
5235 
5236  $insert_rows = []; // all the new preference rows
5237  foreach ( $saveOptions as $key => $value ) {
5238  // Don't bother storing default values
5239  $defaultOption = self::getDefaultOption( $key );
5240  if ( ( $defaultOption === null && $value !== false && $value !== null )
5241  || $value != $defaultOption
5242  ) {
5243  $insert_rows[] = [
5244  'up_user' => $userId,
5245  'up_property' => $key,
5246  'up_value' => $value,
5247  ];
5248  }
5249  }
5250 
5251  $dbw = wfGetDB( DB_MASTER );
5252 
5253  $res = $dbw->select( 'user_properties',
5254  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5255 
5256  // Find prior rows that need to be removed or updated. These rows will
5257  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5258  $keysDelete = [];
5259  foreach ( $res as $row ) {
5260  if ( !isset( $saveOptions[$row->up_property] )
5261  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5262  ) {
5263  $keysDelete[] = $row->up_property;
5264  }
5265  }
5266 
5267  if ( count( $keysDelete ) ) {
5268  // Do the DELETE by PRIMARY KEY for prior rows.
5269  // In the past a very large portion of calls to this function are for setting
5270  // 'rememberpassword' for new accounts (a preference that has since been removed).
5271  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5272  // caused gap locks on [max user ID,+infinity) which caused high contention since
5273  // updates would pile up on each other as they are for higher (newer) user IDs.
5274  // It might not be necessary these days, but it shouldn't hurt either.
5275  $dbw->delete( 'user_properties',
5276  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5277  }
5278  // Insert the new preference rows
5279  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5280  }
5281 
5288  public static function selectFields() {
5289  wfDeprecated( __METHOD__, '1.31' );
5290  return [
5291  'user_id',
5292  'user_name',
5293  'user_real_name',
5294  'user_email',
5295  'user_touched',
5296  'user_token',
5297  'user_email_authenticated',
5298  'user_email_token',
5299  'user_email_token_expires',
5300  'user_registration',
5301  'user_editcount',
5302  ];
5303  }
5304 
5314  public static function getQueryInfo() {
5315  $ret = [
5316  'tables' => [ 'user', 'user_actor' => 'actor' ],
5317  'fields' => [
5318  'user_id',
5319  'user_name',
5320  'user_real_name',
5321  'user_email',
5322  'user_touched',
5323  'user_token',
5324  'user_email_authenticated',
5325  'user_email_token',
5326  'user_email_token_expires',
5327  'user_registration',
5328  'user_editcount',
5329  'user_actor.actor_id',
5330  ],
5331  'joins' => [
5332  'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
5333  ],
5334  ];
5335 
5336  return $ret;
5337  }
5338 
5346  static function newFatalPermissionDeniedStatus( $permission ) {
5347  global $wgLang;
5348 
5349  $groups = [];
5350  foreach ( MediaWikiServices::getInstance()
5352  ->getGroupsWithPermission( $permission ) as $group ) {
5353  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5354  }
5355 
5356  if ( $groups ) {
5357  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5358  }
5359 
5360  return Status::newFatal( 'badaccess-group0' );
5361  }
5362 
5372  public function getInstanceForUpdate() {
5373  if ( !$this->getId() ) {
5374  return null; // anon
5375  }
5376 
5377  $user = self::newFromId( $this->getId() );
5378  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5379  return null;
5380  }
5381 
5382  return $user;
5383  }
5384 
5392  public function equals( UserIdentity $user ) {
5393  // XXX it's not clear whether central ID providers are supposed to obey this
5394  return $this->getName() === $user->getName();
5395  }
5396 
5402  public function isAllowUsertalk() {
5403  return $this->mAllowUsertalk;
5404  }
5405 
5406 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1748
User\saveOptions
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5222
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:4908
User\$mNewtalk
int bool $mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:180
LanguageCode\replaceDeprecatedCodes
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date,...
Definition: LanguageCode.php:162
User\updateNewtalk
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2497
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:410
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:325
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2384
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:49
User\inDnsBlacklist
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1840
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:561
Title\newFromText
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
Wikimedia\Rdbms\IDatabase\affectedRows
affectedRows()
Get the number of rows affected by the last write query.
User\confirmationTokenUrl
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4597
User\__set
__set( $name, $value)
Definition: User.php:279
$wgProxyList
$wgProxyList
Big list of banned IP addresses.
Definition: DefaultSettings.php:6000
WikiMap\getCurrentWikiDbDomain
static getCurrentWikiDbDomain()
Definition: WikiMap.php:293
wfCanIPUseHTTPS
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
Definition: GlobalFunctions.php:2935
User\$mToken
string $mToken
Definition: User.php:135
User\$mBlockedby
string int $mBlockedby
Definition: User.php:189
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:267
User\resetGetDefaultOptionsForTestsOnly
static resetGetDefaultOptionsForTestsOnly()
Reset the process cache of default user options.
Definition: User.php:1692
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1127
User\getId
getId()
Get the user's ID.
Definition: User.php:2255
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3694
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2290
MWCryptHash\hmac
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
Definition: MWCryptHash.php:106
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:3589
User\$mBlockedFromCreateAccount
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:232
User\$mBlockreason
string $mBlockreason
TODO: This should be removed when User::BlockedFor and AbstractBlock::getReason are hard deprecated.
Definition: User.php:197
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4624
User\isRegistered
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3573
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2323
User\$mImplicitGroups
array $mImplicitGroups
Definition: User.php:201
User\isLocallyBlockedProxy
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1888
User\$mCacheVars
static string[] $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:93
$wgRevokePermissions
$wgRevokePermissions
Permission keys revoked from users in each group.
Definition: DefaultSettings.php:5268
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3111
User\isBlockedFrom
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2134
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1513
WikiMap\isCurrentWikiId
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:313
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5346
User\getEditTokenObject
getEditTokenObject( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4432
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:7016
User\isBot
isBot()
Definition: User.php:3597
User\newTouchedTimestamp
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2575
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:8930
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3469
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:4279
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:58
User\incEditCount
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition: User.php:5036
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:696
UserMailer\send
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Definition: UserMailer.php:115
User\getOptionKinds
getOptionKinds(IContextSource $context, $options=null)
Return an associative array mapping preferences keys to the kind of a preference they're used for.
Definition: User.php:3168
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4736
MediaWiki\Logger\LoggerFactory\getInstance
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
Definition: LoggerFactory.php:92
User\getBlockId
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2163
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:3046
true
return true
Definition: router.php:92
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:3003
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:131
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:251
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1871
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2799
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:4266
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2455
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:229
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1805
User\$mOptions
array $mOptions
Definition: User.php:216
User\loadOptions
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5145
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:4839
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:85
Autopromote\getAutopromoteGroups
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:38
User\setEmailWithConfirmation
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:2895
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1693
User\$mHideName
bool $mHideName
Definition: User.php:214
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3338
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1170
User\$mLocked
bool $mLocked
Definition: User.php:207
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:537
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1401
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1263
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4668
User\$mEmail
string $mEmail
Definition: User.php:129
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:4351
User\$mOptionOverrides
array $mOptionOverrides
Definition: User.php:149
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3367
$s
$s
Definition: mergeMessageFileList.php:185
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:593
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1078
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3682
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
User\getDatePreference
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3298
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:308
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:2878
User\idForName
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:4071
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:974
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
User\sendConfirmationMail
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4498
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4851
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2868
User\pingLimiter
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1961
UserEditCountUpdate
Handles increment the edit count for a given set of users.
Definition: UserEditCountUpdate.php:29
$res
$res
Definition: testCompression.php:54
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3673
$base
$base
Definition: generateLocalAutoload.php:11
User\invalidateEmail
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4654
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:718
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6909
User\loadGroups
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1523
User\$mHash
string $mHash
Definition: User.php:191
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:6925
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1007
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
User\isIPRange
isIPRange()
Is the user an IP range?
Definition: User.php:959
User\deleteNewtalk
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2522
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:139
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
MediaWiki\User\UserIdentity\getActorId
getActorId()
User\getRights
getRights()
Get the permissions this user has.
Definition: User.php:3357
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4106
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3707
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
User\getAutomaticGroups
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3416
User\INVALID_TOKEN
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:62
User\setPassword
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2708
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1704
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5372
$dbr
$dbr
Definition: testCompression.php:52
$wgMaxNameChars
$wgMaxNameChars
Maximum number of bytes in username.
Definition: DefaultSettings.php:4812
IDBAccessObject\READ_LOCKING
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:64
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Specify the difference engine to use.
Definition: DefaultSettings.php:8929
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:759
UserGroupMembership\getMembershipsForUser
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
Definition: UserGroupMembership.php:311
Revision
Definition: Revision.php:40
NS_MAIN
const NS_MAIN
Definition: Defines.php:60
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3504
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:32
LoggedOutEditToken
Value object representing a logged-out user's edit token.
Definition: LoggedOutEditToken.php:35
User\matchEditToken
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4472
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2858
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:54
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:4360
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4182
Wikimedia\Rdbms\IDatabase\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
User\invalidateCache
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2620
User\$reservedUsernames
static string[] false $reservedUsernames
Cache for self::isUsableName()
Definition: User.php:115
User\setInternalPassword
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2721
MWException
MediaWiki exception.
Definition: MWException.php:26
User\invalidationTokenUrl
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4606
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2223
User\$mDatePreference
string $mDatePreference
Definition: User.php:182
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:4946
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1044
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
User\checkPasswordValidity
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1153
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:104
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4637
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1300
UserGroupMembership\getLink
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
Definition: UserGroupMembership.php:376
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2154
getPermissionManager
getPermissionManager()
WikiMap\getWikiIdFromDbDomain
static getWikiIdFromDbDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:269
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2542
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2562
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4342
MediaWiki\Auth\AuthenticationResponse
This is a value object to hold authentication response data.
Definition: AuthenticationResponse.php:37
User\logout
logout()
Log this user out.
Definition: User.php:3949
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4579
User\initEditCountInternal
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition: User.php:5063
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:451
$wgLang
$wgLang
Definition: Setup.php:842
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1887
User\getImplicitGroups
static getImplicitGroups()
Get a list of implicit groups TODO: Should we deprecate this? It's trivial, but we don't want to enco...
Definition: User.php:4910
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2238
UserGroupMembership\newFromRow
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
Definition: UserGroupMembership.php:93
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4322
User\$mBlock
AbstractBlock null $mBlock
Definition: User.php:226
User\loadDefaults
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition: User.php:1251
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:948
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2652
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3393
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1936
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:70
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3539
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4696
$wgImplicitGroups
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
Definition: DefaultSettings.php:5273
User\$mOptionsLoaded
bool $mOptionsLoaded
Whether the cache variables have been loaded.
Definition: User.php:156
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4370
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2637
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:36
User\clearInstanceCache
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1654
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1687
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:79
User\TOKEN_LENGTH
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:57
$wgDefaultSkin
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
Definition: DefaultSettings.php:3328
$wgReservedUsernames
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
Definition: DefaultSettings.php:4818
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:584
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1900
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3738
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:133
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:618
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2118
User\setName
setName( $str)
Set the user name.
Definition: User.php:2312
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:126
User\resetOptions
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3253
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:42
User\$mGroupMemberships
UserGroupMembership[] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:147
UserPasswordPolicy
Check if a user's password complies with any password policies that apply to that user,...
Definition: UserPasswordPolicy.php:28
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:6936
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:913
User\__get
& __get( $name)
Definition: User.php:262
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5522
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3772
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:3997
User\addNewUserLogEntry
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5136
User\$defOpt
static array null $defOpt
Is the user an IP range?
Definition: User.php:1682
User\isAllowedAll
isAllowedAll(... $permissions)
Definition: User.php:3648
User\$defOptLang
static string null $defOptLang
Is the user an IP range?
Definition: User.php:1684
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:893
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4765
$wgAutopromoteOnceLogInRC
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
Definition: DefaultSettings.php:5493
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:74
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:2960
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2422
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:610
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:63
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3445
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:866
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\isAllowedAny
isAllowedAny(... $permissions)
Check if user is allowed to access a feature / make an action.
Definition: User.php:3636
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1311
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:5684
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2176
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2975
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
User\isDnsBlacklisted
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1826
Wikimedia\Rdbms\IDatabase\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
User\getTouched
getTouched()
Get the user touched timestamp.
Definition: User.php:2664
User\$mId
int $mId
Cache variables.
Definition: User.php:120
User\isSystemUser
isSystemUser()
Get whether the user is a system user.
Definition: User.php:3617
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2190
User\__toString
__toString()
Definition: User.php:258
MediaWiki\Block\SystemBlock
System blocks are temporary blocks that are created on enforcement (e.g.
Definition: SystemBlock.php:33
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
CannotCreateActorException
Exception thrown when an actor can't be created.
Definition: CannotCreateActorException.php:28
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1616
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:116
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:2948
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4252
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:3913
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4819
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4997
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3846
User\VERSION
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:68
User\matchEditTokenNoSuffix
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4486
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5517
User\checkPassword
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4380
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4884
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3755
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4899
Wikimedia\Rdbms\IDatabase\update
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:36
User\isBlockedFromUpload
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition: User.php:4333
$wgInvalidUsernameCharacters
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
Definition: DefaultSettings.php:4915
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:447
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:5314
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2143
User\$mLoadedItems
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:161
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:8927
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2686
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $rows, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:85
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:43
User\getEditTimestamp
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition: User.php:4787
$context
$context
Definition: load.php:40
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4924
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:429
Autopromote\getAutopromoteOnceGroups
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:66
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2275
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3875
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1737
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4751
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:242
User\isEveryoneAllowed
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4873
MediaWiki\User\UserIdentity\getId
getId()
MediaWiki\Block\AbstractBlock\appliesToRight
appliesToRight( $right)
Determine whether the block prevents a given right.
Definition: AbstractBlock.php:267
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5097
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2772
$cache
$cache
Definition: mcc.php:33
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:5766
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3581
User\checkTemporaryPassword
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4415
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:32
User\$mActorId
int null $mActorId
Definition: User.php:124
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2376
User\purge
static purge( $dbDomain, $userId)
Definition: User.php:440
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2842
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:886
$wgGroupPermissions
$wgGroupPermissions
Permission keys given to users in each group.
Definition: DefaultSettings.php:5116
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:928
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:141
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1057
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1195
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4710
User\$mGlobalBlock
AbstractBlock $mGlobalBlock
Definition: User.php:205
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5402
User\getBlockedStatus
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1762
User\$idCacheByName
static int[] $idCacheByName
Definition: User.php:238
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5110
UserCache\singleton
static singleton()
Definition: UserCache.php:34
User\clearSharedCache
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2594
User\isBlocked
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:2107
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:296
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:576
$keys
$keys
Definition: testCompression.php:69
NS_USER
const NS_USER
Definition: Defines.php:62
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:474
$wgLearnerMemberSince
$wgLearnerMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:8928
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1547
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4553
User\getTokenFromOption
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:3083
User\$mEmailToken
string $mEmailToken
Definition: User.php:139
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:38
Wikimedia\Rdbms\IDatabase\timestampOrNull
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
$wgGroupsRemoveFromSelf
$wgGroupsRemoveFromSelf
Definition: DefaultSettings.php:5301
User\equals
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5392
User\checkNewtalk
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2482
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1348
HTMLFormField\flattenOptions
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
Definition: HTMLFormField.php:1094
User\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5288
User\$mName
string $mName
Definition: User.php:122
User\$mRegistration
string $mRegistration
Definition: User.php:143
MediaWiki\Block\AbstractBlock
Definition: AbstractBlock.php:36
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for the wiki.
Definition: DefaultSettings.php:4477
$t
$t
Definition: testCompression.php:71
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:672
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:84
User\$mRequest
WebRequest $mRequest
Definition: User.php:219
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:713
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3724
UserGroupMembership\getMembership
static getMembership( $userId, $group, IDatabase $db=null)
Returns a UserGroupMembership object that pertains to the given user and group, or false if the user ...
Definition: UserGroupMembership.php:343
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:3034
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
$wgGroupsAddToSelf
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
Definition: DefaultSettings.php:5296
User\isUsableName
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:1023
User\$mFormerGroups
array $mFormerGroups
Definition: User.php:203
User\makeUpdateConditions
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1598
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1673
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4298
User\getEditToken
getEditToken( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4456
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3318
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1290
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:52
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:125
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3062
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:235
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:876
ExternalUserNames\isExternal
static isExternal( $username)
Tells whether the username is external or not.
Definition: ExternalUserNames.php:137
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2284
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:4934
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3961
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:3380
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4679
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1097
Wikimedia\Rdbms\IDatabase\delete
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
$wgNamespacesToBeSearchedDefault
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
Definition: DefaultSettings.php:6582
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:462
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
User\getLatestEditTimestamp
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4776
User\$mEffectiveGroups
array $mEffectiveGroups
Definition: User.php:199
User\setEditCountInternal
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:5052
User\setPasswordInternal
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2734
$wgDisableAnonTalk
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
Definition: DefaultSettings.php:7022
UserGroupMembership
Represents a "user group membership" – a specific instance of a user belonging to a group.
Definition: UserGroupMembership.php:37
User\$mEditCount
int $mEditCount
Definition: User.php:145
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:137
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4832
User\$mFrom
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:174
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3664
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3145
$type
$type
Definition: testCompression.php:50
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:629
User\trackBlockWithCookie
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition: User.php:1335