MediaWiki  1.34.0
User.php
Go to the documentation of this file.
1 <?php
34 use Wikimedia\Assert\Assert;
35 use Wikimedia\IPSet;
36 use Wikimedia\ScopedCallback;
40 
51 class User implements IDBAccessObject, UserIdentity {
52 
56  const TOKEN_LENGTH = 32;
57 
61  const INVALID_TOKEN = '*** INVALID ***';
62 
67  const VERSION = 14;
68 
74 
78  const CHECK_USER_RIGHTS = true;
79 
83  const IGNORE_USER_RIGHTS = false;
84 
92  protected static $mCacheVars = [
93  // user table
94  'mId',
95  'mName',
96  'mRealName',
97  'mEmail',
98  'mTouched',
99  'mToken',
100  'mEmailAuthenticated',
101  'mEmailToken',
102  'mEmailTokenExpires',
103  'mRegistration',
104  'mEditCount',
105  // user_groups table
106  'mGroupMemberships',
107  // user_properties table
108  'mOptionOverrides',
109  // actor table
110  'mActorId',
111  ];
112 
114  // @{
116  public $mId;
118  public $mName;
120  protected $mActorId;
122  public $mRealName;
123 
125  public $mEmail;
127  public $mTouched;
129  protected $mQuickTouched;
131  protected $mToken;
135  protected $mEmailToken;
139  protected $mRegistration;
141  protected $mEditCount;
145  protected $mOptionOverrides;
146  // @}
147 
148  // @{
153 
157  protected $mLoadedItems = [];
158  // @}
159 
170  public $mFrom;
171 
176  protected $mNewtalk;
178  protected $mDatePreference;
180  public $mBlockedby;
182  protected $mHash;
184  protected $mBlockreason;
186  protected $mEffectiveGroups;
188  protected $mImplicitGroups;
190  protected $mFormerGroups;
192  protected $mGlobalBlock;
194  protected $mLocked;
196  public $mHideName;
198  public $mOptions;
199 
201  private $mRequest;
202 
204  public $mBlock;
205 
207  protected $mAllowUsertalk;
208 
210  private $mBlockedFromCreateAccount = false;
211 
213  protected $queryFlagsUsed = self::READ_NORMAL;
214 
216  public static $idCacheByName = [];
217 
229  public function __construct() {
230  $this->clearInstanceCache( 'defaults' );
231  }
232 
236  public function __toString() {
237  return (string)$this->getName();
238  }
239 
240  public function &__get( $name ) {
241  // A shortcut for $mRights deprecation phase
242  if ( $name === 'mRights' ) {
243  $copy = $this->getRights();
244  return $copy;
245  } elseif ( !property_exists( $this, $name ) ) {
246  // T227688 - do not break $u->foo['bar'] = 1
247  wfLogWarning( 'tried to get non-existent property' );
248  $this->$name = null;
249  return $this->$name;
250  } else {
251  wfLogWarning( 'tried to get non-visible property' );
252  $null = null;
253  return $null;
254  }
255  }
256 
257  public function __set( $name, $value ) {
258  // A shortcut for $mRights deprecation phase, only known legitimate use was for
259  // testing purposes, other uses seem bad in principle
260  if ( $name === 'mRights' ) {
261  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
262  $this,
263  is_null( $value ) ? [] : $value
264  );
265  } elseif ( !property_exists( $this, $name ) ) {
266  $this->$name = $value;
267  } else {
268  wfLogWarning( 'tried to set non-visible property' );
269  }
270  }
271 
286  public function isSafeToLoad() {
287  global $wgFullyInitialised;
288 
289  // The user is safe to load if:
290  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
291  // * mLoadedItems === true (already loaded)
292  // * mFrom !== 'session' (sessions not involved at all)
293 
294  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
295  $this->mLoadedItems === true || $this->mFrom !== 'session';
296  }
297 
303  public function load( $flags = self::READ_NORMAL ) {
304  global $wgFullyInitialised;
305 
306  if ( $this->mLoadedItems === true ) {
307  return;
308  }
309 
310  // Set it now to avoid infinite recursion in accessors
311  $oldLoadedItems = $this->mLoadedItems;
312  $this->mLoadedItems = true;
313  $this->queryFlagsUsed = $flags;
314 
315  // If this is called too early, things are likely to break.
316  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
318  ->warning( 'User::loadFromSession called before the end of Setup.php', [
319  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
320  ] );
321  $this->loadDefaults();
322  $this->mLoadedItems = $oldLoadedItems;
323  return;
324  }
325 
326  switch ( $this->mFrom ) {
327  case 'defaults':
328  $this->loadDefaults();
329  break;
330  case 'id':
331  // Make sure this thread sees its own changes, if the ID isn't 0
332  if ( $this->mId != 0 ) {
333  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
334  if ( $lb->hasOrMadeRecentMasterChanges() ) {
335  $flags |= self::READ_LATEST;
336  $this->queryFlagsUsed = $flags;
337  }
338  }
339 
340  $this->loadFromId( $flags );
341  break;
342  case 'actor':
343  case 'name':
344  // Make sure this thread sees its own changes
345  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
346  if ( $lb->hasOrMadeRecentMasterChanges() ) {
347  $flags |= self::READ_LATEST;
348  $this->queryFlagsUsed = $flags;
349  }
350 
351  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
352  $row = wfGetDB( $index )->selectRow(
353  'actor',
354  [ 'actor_id', 'actor_user', 'actor_name' ],
355  $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
356  __METHOD__,
357  $options
358  );
359 
360  if ( !$row ) {
361  // Ugh.
362  $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
363  } elseif ( $row->actor_user ) {
364  $this->mId = $row->actor_user;
365  $this->loadFromId( $flags );
366  } else {
367  $this->loadDefaults( $row->actor_name, $row->actor_id );
368  }
369  break;
370  case 'session':
371  if ( !$this->loadFromSession() ) {
372  // Loading from session failed. Load defaults.
373  $this->loadDefaults();
374  }
375  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
376  break;
377  default:
378  throw new UnexpectedValueException(
379  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
380  }
381  }
382 
388  public function loadFromId( $flags = self::READ_NORMAL ) {
389  if ( $this->mId == 0 ) {
390  // Anonymous users are not in the database (don't need cache)
391  $this->loadDefaults();
392  return false;
393  }
394 
395  // Try cache (unless this needs data from the master DB).
396  // NOTE: if this thread called saveSettings(), the cache was cleared.
397  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
398  if ( $latest ) {
399  if ( !$this->loadFromDatabase( $flags ) ) {
400  // Can't load from ID
401  return false;
402  }
403  } else {
404  $this->loadFromCache();
405  }
406 
407  $this->mLoadedItems = true;
408  $this->queryFlagsUsed = $flags;
409 
410  return true;
411  }
412 
418  public static function purge( $dbDomain, $userId ) {
419  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
420  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
421  $cache->delete( $key );
422  }
423 
429  protected function getCacheKey( WANObjectCache $cache ) {
430  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
431 
432  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
433  }
434 
441  $id = $this->getId();
442 
443  return $id ? [ $this->getCacheKey( $cache ) ] : [];
444  }
445 
452  protected function loadFromCache() {
453  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
454  $data = $cache->getWithSetCallback(
455  $this->getCacheKey( $cache ),
456  $cache::TTL_HOUR,
457  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
458  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
459  wfDebug( "User: cache miss for user {$this->mId}\n" );
460 
461  $this->loadFromDatabase( self::READ_NORMAL );
462  $this->loadGroups();
463  $this->loadOptions();
464 
465  $data = [];
466  foreach ( self::$mCacheVars as $name ) {
467  $data[$name] = $this->$name;
468  }
469 
470  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
471 
472  // if a user group membership is about to expire, the cache needs to
473  // expire at that time (T163691)
474  foreach ( $this->mGroupMemberships as $ugm ) {
475  if ( $ugm->getExpiry() ) {
476  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
477  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
478  $ttl = $secondsUntilExpiry;
479  }
480  }
481  }
482 
483  return $data;
484  },
485  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
486  );
487 
488  // Restore from cache
489  foreach ( self::$mCacheVars as $name ) {
490  $this->$name = $data[$name];
491  }
492 
493  return true;
494  }
495 
497  // @{
498 
515  public static function newFromName( $name, $validate = 'valid' ) {
516  if ( $validate === true ) {
517  $validate = 'valid';
518  }
519  $name = self::getCanonicalName( $name, $validate );
520  if ( $name === false ) {
521  return false;
522  }
523 
524  // Create unloaded user object
525  $u = new User;
526  $u->mName = $name;
527  $u->mFrom = 'name';
528  $u->setItemLoaded( 'name' );
529 
530  return $u;
531  }
532 
539  public static function newFromId( $id ) {
540  $u = new User;
541  $u->mId = $id;
542  $u->mFrom = 'id';
543  $u->setItemLoaded( 'id' );
544  return $u;
545  }
546 
554  public static function newFromActorId( $id ) {
555  $u = new User;
556  $u->mActorId = $id;
557  $u->mFrom = 'actor';
558  $u->setItemLoaded( 'actor' );
559  return $u;
560  }
561 
571  public static function newFromIdentity( UserIdentity $identity ) {
572  if ( $identity instanceof User ) {
573  return $identity;
574  }
575 
576  return self::newFromAnyId(
577  $identity->getId() === 0 ? null : $identity->getId(),
578  $identity->getName() === '' ? null : $identity->getName(),
579  $identity->getActorId() === 0 ? null : $identity->getActorId()
580  );
581  }
582 
596  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
597  // Stop-gap solution for the problem described in T222212.
598  // Force the User ID and Actor ID to zero for users loaded from the database
599  // of another wiki, to prevent subtle data corruption and confusing failure modes.
600  if ( $dbDomain !== false ) {
601  $userId = 0;
602  $actorId = 0;
603  }
604 
605  $user = new User;
606  $user->mFrom = 'defaults';
607 
608  if ( $actorId !== null ) {
609  $user->mActorId = (int)$actorId;
610  if ( $user->mActorId !== 0 ) {
611  $user->mFrom = 'actor';
612  }
613  $user->setItemLoaded( 'actor' );
614  }
615 
616  if ( $userName !== null && $userName !== '' ) {
617  $user->mName = $userName;
618  $user->mFrom = 'name';
619  $user->setItemLoaded( 'name' );
620  }
621 
622  if ( $userId !== null ) {
623  $user->mId = (int)$userId;
624  if ( $user->mId !== 0 ) {
625  $user->mFrom = 'id';
626  }
627  $user->setItemLoaded( 'id' );
628  }
629 
630  if ( $user->mFrom === 'defaults' ) {
631  throw new InvalidArgumentException(
632  'Cannot create a user with no name, no ID, and no actor ID'
633  );
634  }
635 
636  return $user;
637  }
638 
650  public static function newFromConfirmationCode( $code, $flags = 0 ) {
651  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
652  ? wfGetDB( DB_MASTER )
653  : wfGetDB( DB_REPLICA );
654 
655  $id = $db->selectField(
656  'user',
657  'user_id',
658  [
659  'user_email_token' => md5( $code ),
660  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
661  ]
662  );
663 
664  return $id ? self::newFromId( $id ) : null;
665  }
666 
674  public static function newFromSession( WebRequest $request = null ) {
675  $user = new User;
676  $user->mFrom = 'session';
677  $user->mRequest = $request;
678  return $user;
679  }
680 
696  public static function newFromRow( $row, $data = null ) {
697  $user = new User;
698  $user->loadFromRow( $row, $data );
699  return $user;
700  }
701 
737  public static function newSystemUser( $name, $options = [] ) {
738  $options += [
739  'validate' => 'valid',
740  'create' => true,
741  'steal' => false,
742  ];
743 
744  $name = self::getCanonicalName( $name, $options['validate'] );
745  if ( $name === false ) {
746  return null;
747  }
748 
749  $dbr = wfGetDB( DB_REPLICA );
750  $userQuery = self::getQueryInfo();
751  $row = $dbr->selectRow(
752  $userQuery['tables'],
753  $userQuery['fields'],
754  [ 'user_name' => $name ],
755  __METHOD__,
756  [],
757  $userQuery['joins']
758  );
759  if ( !$row ) {
760  // Try the master database...
761  $dbw = wfGetDB( DB_MASTER );
762  $row = $dbw->selectRow(
763  $userQuery['tables'],
764  $userQuery['fields'],
765  [ 'user_name' => $name ],
766  __METHOD__,
767  [],
768  $userQuery['joins']
769  );
770  }
771 
772  if ( !$row ) {
773  // No user. Create it?
774  return $options['create']
775  ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
776  : null;
777  }
778 
779  $user = self::newFromRow( $row );
780 
781  // A user is considered to exist as a non-system user if it can
782  // authenticate, or has an email set, or has a non-invalid token.
783  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
784  AuthManager::singleton()->userCanAuthenticate( $name )
785  ) {
786  // User exists. Steal it?
787  if ( !$options['steal'] ) {
788  return null;
789  }
790 
791  AuthManager::singleton()->revokeAccessForUser( $name );
792 
793  $user->invalidateEmail();
794  $user->mToken = self::INVALID_TOKEN;
795  $user->saveSettings();
796  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
797  }
798 
799  return $user;
800  }
801 
802  // @}
803 
809  public static function whoIs( $id ) {
810  return UserCache::singleton()->getProp( $id, 'name' );
811  }
812 
819  public static function whoIsReal( $id ) {
820  return UserCache::singleton()->getProp( $id, 'real_name' );
821  }
822 
829  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
830  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
831  $name = (string)$name;
832  $nt = Title::makeTitleSafe( NS_USER, $name );
833  if ( is_null( $nt ) ) {
834  // Illegal name
835  return null;
836  }
837 
838  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
839  return is_null( self::$idCacheByName[$name] ) ? null : (int)self::$idCacheByName[$name];
840  }
841 
842  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
843  $db = wfGetDB( $index );
844 
845  $s = $db->selectRow(
846  'user',
847  [ 'user_id' ],
848  [ 'user_name' => $nt->getText() ],
849  __METHOD__,
850  $options
851  );
852 
853  if ( $s === false ) {
854  $result = null;
855  } else {
856  $result = (int)$s->user_id;
857  }
858 
859  if ( count( self::$idCacheByName ) >= 1000 ) {
860  self::$idCacheByName = [];
861  }
862 
863  self::$idCacheByName[$name] = $result;
864 
865  return $result;
866  }
867 
871  public static function resetIdByNameCache() {
872  self::$idCacheByName = [];
873  }
874 
891  public static function isIP( $name ) {
892  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
893  || IP::isIPv6( $name );
894  }
895 
902  public function isIPRange() {
903  return IP::isValidRange( $this->mName );
904  }
905 
917  public static function isValidUserName( $name ) {
918  global $wgMaxNameChars;
919 
920  if ( $name == ''
921  || self::isIP( $name )
922  || strpos( $name, '/' ) !== false
923  || strlen( $name ) > $wgMaxNameChars
924  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
925  ) {
926  return false;
927  }
928 
929  // Ensure that the name can't be misresolved as a different title,
930  // such as with extra namespace keys at the start.
931  $parsed = Title::newFromText( $name );
932  if ( is_null( $parsed )
933  || $parsed->getNamespace()
934  || strcmp( $name, $parsed->getPrefixedText() ) ) {
935  return false;
936  }
937 
938  // Check an additional blacklist of troublemaker characters.
939  // Should these be merged into the title char list?
940  $unicodeBlacklist = '/[' .
941  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
942  '\x{00a0}' . # non-breaking space
943  '\x{2000}-\x{200f}' . # various whitespace
944  '\x{2028}-\x{202f}' . # breaks and control chars
945  '\x{3000}' . # ideographic space
946  '\x{e000}-\x{f8ff}' . # private use
947  ']/u';
948  if ( preg_match( $unicodeBlacklist, $name ) ) {
949  return false;
950  }
951 
952  return true;
953  }
954 
966  public static function isUsableName( $name ) {
967  global $wgReservedUsernames;
968  // Must be a valid username, obviously ;)
969  if ( !self::isValidUserName( $name ) ) {
970  return false;
971  }
972 
973  static $reservedUsernames = false;
974  if ( !$reservedUsernames ) {
975  $reservedUsernames = $wgReservedUsernames;
976  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
977  }
978 
979  // Certain names may be reserved for batch processes.
980  foreach ( $reservedUsernames as $reserved ) {
981  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
982  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
983  }
984  if ( $reserved == $name ) {
985  return false;
986  }
987  }
988  return true;
989  }
990 
1001  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1002  if ( $groups === [] ) {
1003  return UserArrayFromResult::newFromIDs( [] );
1004  }
1005 
1006  $groups = array_unique( (array)$groups );
1007  $limit = min( 5000, $limit );
1008 
1009  $conds = [ 'ug_group' => $groups ];
1010  if ( $after !== null ) {
1011  $conds[] = 'ug_user > ' . (int)$after;
1012  }
1013 
1014  $dbr = wfGetDB( DB_REPLICA );
1015  $ids = $dbr->selectFieldValues(
1016  'user_groups',
1017  'ug_user',
1018  $conds,
1019  __METHOD__,
1020  [
1021  'DISTINCT' => true,
1022  'ORDER BY' => 'ug_user',
1023  'LIMIT' => $limit,
1024  ]
1025  ) ?: [];
1026  return UserArray::newFromIDs( $ids );
1027  }
1028 
1041  public static function isCreatableName( $name ) {
1043 
1044  // Ensure that the username isn't longer than 235 bytes, so that
1045  // (at least for the builtin skins) user javascript and css files
1046  // will work. (T25080)
1047  if ( strlen( $name ) > 235 ) {
1048  wfDebugLog( 'username', __METHOD__ .
1049  ": '$name' invalid due to length" );
1050  return false;
1051  }
1052 
1053  // Preg yells if you try to give it an empty string
1054  if ( $wgInvalidUsernameCharacters !== '' &&
1055  preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name )
1056  ) {
1057  wfDebugLog( 'username', __METHOD__ .
1058  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1059  return false;
1060  }
1061 
1062  return self::isUsableName( $name );
1063  }
1064 
1071  public function isValidPassword( $password ) {
1072  // simple boolean wrapper for checkPasswordValidity
1073  return $this->checkPasswordValidity( $password )->isGood();
1074  }
1075 
1097  public function checkPasswordValidity( $password ) {
1098  global $wgPasswordPolicy;
1099 
1100  $upp = new UserPasswordPolicy(
1101  $wgPasswordPolicy['policies'],
1102  $wgPasswordPolicy['checks']
1103  );
1104 
1105  $status = Status::newGood( [] );
1106  $result = false; // init $result to false for the internal checks
1107 
1108  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1109  $status->error( $result );
1110  return $status;
1111  }
1112 
1113  if ( $result === false ) {
1114  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1115  return $status;
1116  }
1117 
1118  if ( $result === true ) {
1119  return $status;
1120  }
1121 
1122  $status->error( $result );
1123  return $status; // the isValidPassword hook set a string $result and returned true
1124  }
1125 
1139  public static function getCanonicalName( $name, $validate = 'valid' ) {
1140  // Force usernames to capital
1141  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1142 
1143  # Reject names containing '#'; these will be cleaned up
1144  # with title normalisation, but then it's too late to
1145  # check elsewhere
1146  if ( strpos( $name, '#' ) !== false ) {
1147  return false;
1148  }
1149 
1150  // Clean up name according to title rules,
1151  // but only when validation is requested (T14654)
1152  $t = ( $validate !== false ) ?
1153  Title::newFromText( $name, NS_USER ) : Title::makeTitle( NS_USER, $name );
1154  // Check for invalid titles
1155  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1156  return false;
1157  }
1158 
1159  $name = $t->getText();
1160 
1161  switch ( $validate ) {
1162  case false:
1163  break;
1164  case 'valid':
1165  if ( !self::isValidUserName( $name ) ) {
1166  $name = false;
1167  }
1168  break;
1169  case 'usable':
1170  if ( !self::isUsableName( $name ) ) {
1171  $name = false;
1172  }
1173  break;
1174  case 'creatable':
1175  if ( !self::isCreatableName( $name ) ) {
1176  $name = false;
1177  }
1178  break;
1179  default:
1180  throw new InvalidArgumentException(
1181  'Invalid parameter value for $validate in ' . __METHOD__ );
1182  }
1183  return $name;
1184  }
1185 
1195  public function loadDefaults( $name = false, $actorId = null ) {
1196  $this->mId = 0;
1197  $this->mName = $name;
1198  $this->mActorId = $actorId;
1199  $this->mRealName = '';
1200  $this->mEmail = '';
1201  $this->mOptionOverrides = null;
1202  $this->mOptionsLoaded = false;
1203 
1204  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1205  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1206  if ( $loggedOut !== 0 ) {
1207  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1208  } else {
1209  $this->mTouched = '1'; # Allow any pages to be cached
1210  }
1211 
1212  $this->mToken = null; // Don't run cryptographic functions till we need a token
1213  $this->mEmailAuthenticated = null;
1214  $this->mEmailToken = '';
1215  $this->mEmailTokenExpires = null;
1216  $this->mRegistration = wfTimestamp( TS_MW );
1217  $this->mGroupMemberships = [];
1218 
1219  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1220  }
1221 
1234  public function isItemLoaded( $item, $all = 'all' ) {
1235  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1236  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1237  }
1238 
1244  protected function setItemLoaded( $item ) {
1245  if ( is_array( $this->mLoadedItems ) ) {
1246  $this->mLoadedItems[$item] = true;
1247  }
1248  }
1249 
1255  private function loadFromSession() {
1256  // MediaWiki\Session\Session already did the necessary authentication of the user
1257  // returned here, so just use it if applicable.
1258  $session = $this->getRequest()->getSession();
1259  $user = $session->getUser();
1260  if ( $user->isLoggedIn() ) {
1261  $this->loadFromUserObject( $user );
1262 
1263  // If this user is autoblocked, set a cookie to track the block. This has to be done on
1264  // every session load, because an autoblocked editor might not edit again from the same
1265  // IP address after being blocked.
1266  MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1267 
1268  // Other code expects these to be set in the session, so set them.
1269  $session->set( 'wsUserID', $this->getId() );
1270  $session->set( 'wsUserName', $this->getName() );
1271  $session->set( 'wsToken', $this->getToken() );
1272 
1273  return true;
1274  }
1275 
1276  return false;
1277  }
1278 
1284  public function trackBlockWithCookie() {
1285  MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1286  }
1287 
1295  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1296  // Paranoia
1297  $this->mId = intval( $this->mId );
1298 
1299  if ( !$this->mId ) {
1300  // Anonymous users are not in the database
1301  $this->loadDefaults();
1302  return false;
1303  }
1304 
1305  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1306  $db = wfGetDB( $index );
1307 
1308  $userQuery = self::getQueryInfo();
1309  $s = $db->selectRow(
1310  $userQuery['tables'],
1311  $userQuery['fields'],
1312  [ 'user_id' => $this->mId ],
1313  __METHOD__,
1314  $options,
1315  $userQuery['joins']
1316  );
1317 
1318  $this->queryFlagsUsed = $flags;
1319  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1320 
1321  if ( $s !== false ) {
1322  // Initialise user table data
1323  $this->loadFromRow( $s );
1324  $this->mGroupMemberships = null; // deferred
1325  $this->getEditCount(); // revalidation for nulls
1326  return true;
1327  }
1328 
1329  // Invalid user_id
1330  $this->mId = 0;
1331  $this->loadDefaults();
1332 
1333  return false;
1334  }
1335 
1348  protected function loadFromRow( $row, $data = null ) {
1349  if ( !is_object( $row ) ) {
1350  throw new InvalidArgumentException( '$row must be an object' );
1351  }
1352 
1353  $all = true;
1354 
1355  $this->mGroupMemberships = null; // deferred
1356 
1357  if ( isset( $row->actor_id ) ) {
1358  $this->mActorId = (int)$row->actor_id;
1359  if ( $this->mActorId !== 0 ) {
1360  $this->mFrom = 'actor';
1361  }
1362  $this->setItemLoaded( 'actor' );
1363  } else {
1364  $all = false;
1365  }
1366 
1367  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1368  $this->mName = $row->user_name;
1369  $this->mFrom = 'name';
1370  $this->setItemLoaded( 'name' );
1371  } else {
1372  $all = false;
1373  }
1374 
1375  if ( isset( $row->user_real_name ) ) {
1376  $this->mRealName = $row->user_real_name;
1377  $this->setItemLoaded( 'realname' );
1378  } else {
1379  $all = false;
1380  }
1381 
1382  if ( isset( $row->user_id ) ) {
1383  $this->mId = intval( $row->user_id );
1384  if ( $this->mId !== 0 ) {
1385  $this->mFrom = 'id';
1386  }
1387  $this->setItemLoaded( 'id' );
1388  } else {
1389  $all = false;
1390  }
1391 
1392  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1393  self::$idCacheByName[$row->user_name] = $row->user_id;
1394  }
1395 
1396  if ( isset( $row->user_editcount ) ) {
1397  $this->mEditCount = $row->user_editcount;
1398  } else {
1399  $all = false;
1400  }
1401 
1402  if ( isset( $row->user_touched ) ) {
1403  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1404  } else {
1405  $all = false;
1406  }
1407 
1408  if ( isset( $row->user_token ) ) {
1409  // The definition for the column is binary(32), so trim the NULs
1410  // that appends. The previous definition was char(32), so trim
1411  // spaces too.
1412  $this->mToken = rtrim( $row->user_token, " \0" );
1413  if ( $this->mToken === '' ) {
1414  $this->mToken = null;
1415  }
1416  } else {
1417  $all = false;
1418  }
1419 
1420  if ( isset( $row->user_email ) ) {
1421  $this->mEmail = $row->user_email;
1422  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1423  $this->mEmailToken = $row->user_email_token;
1424  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1425  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1426  } else {
1427  $all = false;
1428  }
1429 
1430  if ( $all ) {
1431  $this->mLoadedItems = true;
1432  }
1433 
1434  if ( is_array( $data ) ) {
1435  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1436  if ( $data['user_groups'] === [] ) {
1437  $this->mGroupMemberships = [];
1438  } else {
1439  $firstGroup = reset( $data['user_groups'] );
1440  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1441  $this->mGroupMemberships = [];
1442  foreach ( $data['user_groups'] as $row ) {
1443  $ugm = UserGroupMembership::newFromRow( (object)$row );
1444  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1445  }
1446  }
1447  }
1448  }
1449  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1450  $this->loadOptions( $data['user_properties'] );
1451  }
1452  }
1453  }
1454 
1460  protected function loadFromUserObject( $user ) {
1461  $user->load();
1462  foreach ( self::$mCacheVars as $var ) {
1463  $this->$var = $user->$var;
1464  }
1465  }
1466 
1470  private function loadGroups() {
1471  if ( is_null( $this->mGroupMemberships ) ) {
1472  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1473  ? wfGetDB( DB_MASTER )
1474  : wfGetDB( DB_REPLICA );
1475  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1476  $this->mId, $db );
1477  }
1478  }
1479 
1494  public function addAutopromoteOnceGroups( $event ) {
1496 
1497  if ( wfReadOnly() || !$this->getId() ) {
1498  return [];
1499  }
1500 
1501  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1502  if ( $toPromote === [] ) {
1503  return [];
1504  }
1505 
1506  if ( !$this->checkAndSetTouched() ) {
1507  return []; // raced out (bug T48834)
1508  }
1509 
1510  $oldGroups = $this->getGroups(); // previous groups
1511  $oldUGMs = $this->getGroupMemberships();
1512  foreach ( $toPromote as $group ) {
1513  $this->addGroup( $group );
1514  }
1515  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1516  $newUGMs = $this->getGroupMemberships();
1517 
1518  // update groups in external authentication database
1519  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1520 
1521  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1522  $logEntry->setPerformer( $this );
1523  $logEntry->setTarget( $this->getUserPage() );
1524  $logEntry->setParameters( [
1525  '4::oldgroups' => $oldGroups,
1526  '5::newgroups' => $newGroups,
1527  ] );
1528  $logid = $logEntry->insert();
1529  if ( $wgAutopromoteOnceLogInRC ) {
1530  $logEntry->publish( $logid );
1531  }
1532 
1533  return $toPromote;
1534  }
1535 
1545  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1546  if ( $this->mTouched ) {
1547  // CAS check: only update if the row wasn't changed sicne it was loaded.
1548  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1549  }
1550 
1551  return $conditions;
1552  }
1553 
1563  protected function checkAndSetTouched() {
1564  $this->load();
1565 
1566  if ( !$this->mId ) {
1567  return false; // anon
1568  }
1569 
1570  // Get a new user_touched that is higher than the old one
1571  $newTouched = $this->newTouchedTimestamp();
1572 
1573  $dbw = wfGetDB( DB_MASTER );
1574  $dbw->update( 'user',
1575  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1576  $this->makeUpdateConditions( $dbw, [
1577  'user_id' => $this->mId,
1578  ] ),
1579  __METHOD__
1580  );
1581  $success = ( $dbw->affectedRows() > 0 );
1582 
1583  if ( $success ) {
1584  $this->mTouched = $newTouched;
1585  $this->clearSharedCache( 'changed' );
1586  } else {
1587  // Clears on failure too since that is desired if the cache is stale
1588  $this->clearSharedCache( 'refresh' );
1589  }
1590 
1591  return $success;
1592  }
1593 
1601  public function clearInstanceCache( $reloadFrom = false ) {
1602  global $wgFullyInitialised;
1603 
1604  $this->mNewtalk = -1;
1605  $this->mDatePreference = null;
1606  $this->mBlockedby = -1; # Unset
1607  $this->mHash = false;
1608  $this->mEffectiveGroups = null;
1609  $this->mImplicitGroups = null;
1610  $this->mGroupMemberships = null;
1611  $this->mOptions = null;
1612  $this->mOptionsLoaded = false;
1613  $this->mEditCount = null;
1614 
1615  // Replacement of former `$this->mRights = null` line
1616  if ( $wgFullyInitialised && $this->mFrom ) {
1617  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
1618  $this
1619  );
1620  }
1621 
1622  if ( $reloadFrom ) {
1623  $this->mLoadedItems = [];
1624  $this->mFrom = $reloadFrom;
1625  }
1626  }
1627 
1629  private static $defOpt = null;
1631  private static $defOptLang = null;
1632 
1639  public static function resetGetDefaultOptionsForTestsOnly() {
1640  Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
1641  self::$defOpt = null;
1642  self::$defOptLang = null;
1643  }
1644 
1651  public static function getDefaultOptions() {
1653 
1654  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1655  if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
1656  // The content language does not change (and should not change) mid-request, but the
1657  // unit tests change it anyway, and expect this method to return values relevant to the
1658  // current content language.
1659  return self::$defOpt;
1660  }
1661 
1662  self::$defOpt = $wgDefaultUserOptions;
1663  // Default language setting
1664  self::$defOptLang = $contLang->getCode();
1665  self::$defOpt['language'] = self::$defOptLang;
1666  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1667  if ( $langCode === $contLang->getCode() ) {
1668  self::$defOpt['variant'] = $langCode;
1669  } else {
1670  self::$defOpt["variant-$langCode"] = $langCode;
1671  }
1672  }
1673 
1674  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1675  // since extensions may change the set of searchable namespaces depending
1676  // on user groups/permissions.
1677  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1678  self::$defOpt['searchNs' . $nsnum] = (bool)$val;
1679  }
1680  self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1681 
1682  Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
1683 
1684  return self::$defOpt;
1685  }
1686 
1693  public static function getDefaultOption( $opt ) {
1694  $defOpts = self::getDefaultOptions();
1695  return $defOpts[$opt] ?? null;
1696  }
1697 
1707  private function getBlockedStatus( $fromReplica = true ) {
1708  if ( $this->mBlockedby != -1 ) {
1709  return;
1710  }
1711 
1712  wfDebug( __METHOD__ . ": checking...\n" );
1713 
1714  // Initialize data...
1715  // Otherwise something ends up stomping on $this->mBlockedby when
1716  // things get lazy-loaded later, causing false positive block hits
1717  // due to -1 !== 0. Probably session-related... Nothing should be
1718  // overwriting mBlockedby, surely?
1719  $this->load();
1720 
1721  // TODO: Block checking shouldn't really be done from the User object. Block
1722  // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1723  // which need more knowledge of the request context than the User should have.
1724  // Since we do currently check blocks from the User, we have to do the following
1725  // here:
1726  // - Check if this is the user associated with the main request
1727  // - If so, pass the relevant request information to the block manager
1728  $request = null;
1729 
1730  // The session user is set up towards the end of Setup.php. Until then,
1731  // assume it's a logged-out user.
1732  $sessionUser = RequestContext::getMain()->getUser();
1733  $globalUserName = $sessionUser->isSafeToLoad()
1734  ? $sessionUser->getName()
1735  : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1736 
1737  if ( $this->getName() === $globalUserName ) {
1738  // This is the global user, so we need to pass the request
1739  $request = $this->getRequest();
1740  }
1741 
1742  // @phan-suppress-next-line PhanAccessMethodInternal It's the only allowed use
1743  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1744  $this,
1745  $request,
1746  $fromReplica
1747  );
1748 
1749  if ( $block ) {
1750  $this->mBlock = $block;
1751  $this->mBlockedby = $block->getByName();
1752  $this->mBlockreason = $block->getReason();
1753  $this->mHideName = $block->getHideName();
1754  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1755  } else {
1756  $this->mBlock = null;
1757  $this->mBlockedby = '';
1758  $this->mBlockreason = '';
1759  $this->mHideName = 0;
1760  $this->mAllowUsertalk = false;
1761  }
1762 
1763  // Avoid PHP 7.1 warning of passing $this by reference
1764  $thisUser = $this;
1765  // Extensions
1766  Hooks::run( 'GetBlockedStatus', [ &$thisUser ], '1.34' );
1767  }
1768 
1777  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1778  return MediaWikiServices::getInstance()->getBlockManager()
1779  ->isDnsBlacklisted( $ip, $checkWhitelist );
1780  }
1781 
1790  public function inDnsBlacklist( $ip, $bases ) {
1791  wfDeprecated( __METHOD__, '1.34' );
1792 
1793  $found = false;
1794  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1795  if ( IP::isIPv4( $ip ) ) {
1796  // Reverse IP, T23255
1797  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1798 
1799  foreach ( (array)$bases as $base ) {
1800  // Make hostname
1801  // If we have an access key, use that too (ProjectHoneypot, etc.)
1802  $basename = $base;
1803  if ( is_array( $base ) ) {
1804  if ( count( $base ) >= 2 ) {
1805  // Access key is 1, base URL is 0
1806  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1807  } else {
1808  $host = "$ipReversed.{$base[0]}";
1809  }
1810  $basename = $base[0];
1811  } else {
1812  $host = "$ipReversed.$base";
1813  }
1814 
1815  // Send query
1816  $ipList = gethostbynamel( $host );
1817 
1818  if ( $ipList ) {
1819  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1820  $found = true;
1821  break;
1822  }
1823 
1824  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1825  }
1826  }
1827 
1828  return $found;
1829  }
1830 
1838  public static function isLocallyBlockedProxy( $ip ) {
1839  wfDeprecated( __METHOD__, '1.34' );
1840 
1841  global $wgProxyList;
1842 
1843  if ( !$wgProxyList ) {
1844  return false;
1845  }
1846 
1847  if ( !is_array( $wgProxyList ) ) {
1848  // Load values from the specified file
1849  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1850  }
1851 
1852  $resultProxyList = [];
1853  $deprecatedIPEntries = [];
1854 
1855  // backward compatibility: move all ip addresses in keys to values
1856  foreach ( $wgProxyList as $key => $value ) {
1857  $keyIsIP = IP::isIPAddress( $key );
1858  $valueIsIP = IP::isIPAddress( $value );
1859  if ( $keyIsIP && !$valueIsIP ) {
1860  $deprecatedIPEntries[] = $key;
1861  $resultProxyList[] = $key;
1862  } elseif ( $keyIsIP && $valueIsIP ) {
1863  $deprecatedIPEntries[] = $key;
1864  $resultProxyList[] = $key;
1865  $resultProxyList[] = $value;
1866  } else {
1867  $resultProxyList[] = $value;
1868  }
1869  }
1870 
1871  if ( $deprecatedIPEntries ) {
1872  wfDeprecated(
1873  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1874  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
1875  }
1876 
1877  $proxyListIPSet = new IPSet( $resultProxyList );
1878  return $proxyListIPSet->match( $ip );
1879  }
1880 
1886  public function isPingLimitable() {
1887  global $wgRateLimitsExcludedIPs;
1888  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1889  // No other good way currently to disable rate limits
1890  // for specific IPs. :P
1891  // But this is a crappy hack and should die.
1892  return false;
1893  }
1894  return !$this->isAllowed( 'noratelimit' );
1895  }
1896 
1911  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1912  // Avoid PHP 7.1 warning of passing $this by reference
1913  $user = $this;
1914  // Call the 'PingLimiter' hook
1915  $result = false;
1916  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1917  return $result;
1918  }
1919 
1920  global $wgRateLimits;
1921  if ( !isset( $wgRateLimits[$action] ) ) {
1922  return false;
1923  }
1924 
1925  $limits = array_merge(
1926  [ '&can-bypass' => true ],
1927  $wgRateLimits[$action]
1928  );
1929 
1930  // Some groups shouldn't trigger the ping limiter, ever
1931  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1932  return false;
1933  }
1934 
1935  $keys = [];
1936  $id = $this->getId();
1937  $userLimit = false;
1938  $isNewbie = $this->isNewbie();
1940 
1941  if ( $id == 0 ) {
1942  // limits for anons
1943  if ( isset( $limits['anon'] ) ) {
1944  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1945  }
1946  } elseif ( isset( $limits['user'] ) ) {
1947  // limits for logged-in users
1948  $userLimit = $limits['user'];
1949  }
1950 
1951  // limits for anons and for newbie logged-in users
1952  if ( $isNewbie ) {
1953  // ip-based limits
1954  if ( isset( $limits['ip'] ) ) {
1955  $ip = $this->getRequest()->getIP();
1956  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1957  }
1958  // subnet-based limits
1959  if ( isset( $limits['subnet'] ) ) {
1960  $ip = $this->getRequest()->getIP();
1961  $subnet = IP::getSubnet( $ip );
1962  if ( $subnet !== false ) {
1963  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1964  }
1965  }
1966  }
1967 
1968  // Check for group-specific permissions
1969  // If more than one group applies, use the group with the highest limit ratio (max/period)
1970  foreach ( $this->getGroups() as $group ) {
1971  if ( isset( $limits[$group] ) ) {
1972  if ( $userLimit === false
1973  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1974  ) {
1975  $userLimit = $limits[$group];
1976  }
1977  }
1978  }
1979 
1980  // limits for newbie logged-in users (override all the normal user limits)
1981  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1982  $userLimit = $limits['newbie'];
1983  }
1984 
1985  // Set the user limit key
1986  if ( $userLimit !== false ) {
1987  // phan is confused because &can-bypass's value is a bool, so it assumes
1988  // that $userLimit is also a bool here.
1989  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1990  list( $max, $period ) = $userLimit;
1991  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
1992  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
1993  }
1994 
1995  // ip-based limits for all ping-limitable users
1996  if ( isset( $limits['ip-all'] ) ) {
1997  $ip = $this->getRequest()->getIP();
1998  // ignore if user limit is more permissive
1999  if ( $isNewbie || $userLimit === false
2000  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2001  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2002  }
2003  }
2004 
2005  // subnet-based limits for all ping-limitable users
2006  if ( isset( $limits['subnet-all'] ) ) {
2007  $ip = $this->getRequest()->getIP();
2008  $subnet = IP::getSubnet( $ip );
2009  if ( $subnet !== false ) {
2010  // ignore if user limit is more permissive
2011  if ( $isNewbie || $userLimit === false
2012  || $limits['ip-all'][0] / $limits['ip-all'][1]
2013  > $userLimit[0] / $userLimit[1] ) {
2014  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2015  }
2016  }
2017  }
2018 
2019  $triggered = false;
2020  foreach ( $keys as $key => $limit ) {
2021  // phan is confused because &can-bypass's value is a bool, so it assumes
2022  // that $userLimit is also a bool here.
2023  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2024  list( $max, $period ) = $limit;
2025  $summary = "(limit $max in {$period}s)";
2026  $count = $cache->get( $key );
2027  // Already pinged?
2028  if ( $count && $count >= $max ) {
2029  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2030  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2031  $triggered = true;
2032  } else {
2033  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2034  if ( $incrBy > 0 ) {
2035  $cache->add( $key, 0, intval( $period ) ); // first ping
2036  }
2037  }
2038  if ( $incrBy > 0 ) {
2039  $cache->incrWithInit( $key, (int)$period, $incrBy, $incrBy );
2040  }
2041  }
2042 
2043  return $triggered;
2044  }
2045 
2057  public function isBlocked( $fromReplica = true ) {
2058  return $this->getBlock( $fromReplica ) instanceof AbstractBlock &&
2059  $this->getBlock()->appliesToRight( 'edit' );
2060  }
2061 
2068  public function getBlock( $fromReplica = true ) {
2069  $this->getBlockedStatus( $fromReplica );
2070  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
2071  }
2072 
2084  public function isBlockedFrom( $title, $fromReplica = false ) {
2085  return MediaWikiServices::getInstance()->getPermissionManager()
2086  ->isBlockedFrom( $this, $title, $fromReplica );
2087  }
2088 
2093  public function blockedBy() {
2094  $this->getBlockedStatus();
2095  return $this->mBlockedby;
2096  }
2097 
2102  public function blockedFor() {
2103  $this->getBlockedStatus();
2104  return $this->mBlockreason;
2105  }
2106 
2111  public function getBlockId() {
2112  $this->getBlockedStatus();
2113  return ( $this->mBlock ? $this->mBlock->getId() : false );
2114  }
2115 
2124  public function isBlockedGlobally( $ip = '' ) {
2125  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
2126  }
2127 
2138  public function getGlobalBlock( $ip = '' ) {
2139  if ( $this->mGlobalBlock !== null ) {
2140  return $this->mGlobalBlock ?: null;
2141  }
2142  // User is already an IP?
2143  if ( IP::isIPAddress( $this->getName() ) ) {
2144  $ip = $this->getName();
2145  } elseif ( !$ip ) {
2146  $ip = $this->getRequest()->getIP();
2147  }
2148  // Avoid PHP 7.1 warning of passing $this by reference
2149  $user = $this;
2150  $blocked = false;
2151  $block = null;
2152  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2153 
2154  if ( $blocked && $block === null ) {
2155  // back-compat: UserIsBlockedGlobally didn't have $block param first
2156  $block = new SystemBlock( [
2157  'address' => $ip,
2158  'systemBlock' => 'global-block'
2159  ] );
2160  }
2161 
2162  $this->mGlobalBlock = $blocked ? $block : false;
2163  return $this->mGlobalBlock ?: null;
2164  }
2165 
2171  public function isLocked() {
2172  if ( $this->mLocked !== null ) {
2173  return $this->mLocked;
2174  }
2175  // Reset for hook
2176  $this->mLocked = false;
2177  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2178  return $this->mLocked;
2179  }
2180 
2186  public function isHidden() {
2187  if ( $this->mHideName !== null ) {
2188  return (bool)$this->mHideName;
2189  }
2190  $this->getBlockedStatus();
2191  if ( !$this->mHideName ) {
2192  // Reset for hook
2193  $this->mHideName = false;
2194  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ], '1.34' );
2195  }
2196  return (bool)$this->mHideName;
2197  }
2198 
2203  public function getId() {
2204  if ( $this->mId === null && $this->mName !== null &&
2205  ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
2206  ) {
2207  // Special case, we know the user is anonymous
2208  return 0;
2209  }
2210 
2211  if ( !$this->isItemLoaded( 'id' ) ) {
2212  // Don't load if this was initialized from an ID
2213  $this->load();
2214  }
2215 
2216  return (int)$this->mId;
2217  }
2218 
2223  public function setId( $v ) {
2224  $this->mId = $v;
2225  $this->clearInstanceCache( 'id' );
2226  }
2227 
2232  public function getName() {
2233  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2234  // Special case optimisation
2235  return $this->mName;
2236  }
2237 
2238  $this->load();
2239  if ( $this->mName === false ) {
2240  // Clean up IPs
2241  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2242  }
2243 
2244  return $this->mName;
2245  }
2246 
2260  public function setName( $str ) {
2261  $this->load();
2262  $this->mName = $str;
2263  }
2264 
2271  public function getActorId( IDatabase $dbw = null ) {
2272  if ( !$this->isItemLoaded( 'actor' ) ) {
2273  $this->load();
2274  }
2275 
2276  if ( !$this->mActorId && $dbw ) {
2277  $q = [
2278  'actor_user' => $this->getId() ?: null,
2279  'actor_name' => (string)$this->getName(),
2280  ];
2281  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2282  throw new CannotCreateActorException(
2283  'Cannot create an actor for a usable name that is not an existing user'
2284  );
2285  }
2286  if ( $q['actor_name'] === '' ) {
2287  throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2288  }
2289  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2290  if ( $dbw->affectedRows() ) {
2291  $this->mActorId = (int)$dbw->insertId();
2292  } else {
2293  // Outdated cache?
2294  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2295  $this->mActorId = (int)$dbw->selectField(
2296  'actor',
2297  'actor_id',
2298  $q,
2299  __METHOD__,
2300  [ 'LOCK IN SHARE MODE' ]
2301  );
2302  if ( !$this->mActorId ) {
2303  throw new CannotCreateActorException(
2304  "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2305  );
2306  }
2307  }
2308  $this->invalidateCache();
2309  $this->setItemLoaded( 'actor' );
2310  }
2311 
2312  return (int)$this->mActorId;
2313  }
2314 
2319  public function getTitleKey() {
2320  return str_replace( ' ', '_', $this->getName() );
2321  }
2322 
2327  public function getNewtalk() {
2328  $this->load();
2329 
2330  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2331  if ( $this->mNewtalk === -1 ) {
2332  $this->mNewtalk = false; # reset talk page status
2333 
2334  // Check memcached separately for anons, who have no
2335  // entire User object stored in there.
2336  if ( !$this->mId ) {
2337  global $wgDisableAnonTalk;
2338  if ( $wgDisableAnonTalk ) {
2339  // Anon newtalk disabled by configuration.
2340  $this->mNewtalk = false;
2341  } else {
2342  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2343  }
2344  } else {
2345  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2346  }
2347  }
2348 
2349  return (bool)$this->mNewtalk;
2350  }
2351 
2365  public function getNewMessageLinks() {
2366  // Avoid PHP 7.1 warning of passing $this by reference
2367  $user = $this;
2368  $talks = [];
2369  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2370  return $talks;
2371  }
2372 
2373  if ( !$this->getNewtalk() ) {
2374  return [];
2375  }
2376  $utp = $this->getTalkPage();
2377  $dbr = wfGetDB( DB_REPLICA );
2378  // Get the "last viewed rev" timestamp from the oldest message notification
2379  $timestamp = $dbr->selectField( 'user_newtalk',
2380  'MIN(user_last_timestamp)',
2381  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2382  __METHOD__ );
2383  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2384  return [
2385  [
2387  'link' => $utp->getLocalURL(),
2388  'rev' => $rev
2389  ]
2390  ];
2391  }
2392 
2398  public function getNewMessageRevisionId() {
2399  $newMessageRevisionId = null;
2400  $newMessageLinks = $this->getNewMessageLinks();
2401 
2402  // Note: getNewMessageLinks() never returns more than a single link
2403  // and it is always for the same wiki, but we double-check here in
2404  // case that changes some time in the future.
2405  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2406  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2407  && $newMessageLinks[0]['rev']
2408  ) {
2410  $newMessageRevision = $newMessageLinks[0]['rev'];
2411  $newMessageRevisionId = $newMessageRevision->getId();
2412  }
2413 
2414  return $newMessageRevisionId;
2415  }
2416 
2425  protected function checkNewtalk( $field, $id ) {
2426  $dbr = wfGetDB( DB_REPLICA );
2427 
2428  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2429 
2430  return $ok !== false;
2431  }
2432 
2440  protected function updateNewtalk( $field, $id, $curRev = null ) {
2441  // Get timestamp of the talk page revision prior to the current one
2442  $prevRev = $curRev ? $curRev->getPrevious() : false;
2443  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2444  // Mark the user as having new messages since this revision
2445  $dbw = wfGetDB( DB_MASTER );
2446  $dbw->insert( 'user_newtalk',
2447  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2448  __METHOD__,
2449  [ 'IGNORE' ] );
2450  if ( $dbw->affectedRows() ) {
2451  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2452  return true;
2453  }
2454 
2455  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2456  return false;
2457  }
2458 
2465  protected function deleteNewtalk( $field, $id ) {
2466  $dbw = wfGetDB( DB_MASTER );
2467  $dbw->delete( 'user_newtalk',
2468  [ $field => $id ],
2469  __METHOD__ );
2470  if ( $dbw->affectedRows() ) {
2471  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2472  return true;
2473  }
2474 
2475  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2476  return false;
2477  }
2478 
2485  public function setNewtalk( $val, $curRev = null ) {
2486  if ( wfReadOnly() ) {
2487  return;
2488  }
2489 
2490  $this->load();
2491  $this->mNewtalk = $val;
2492 
2493  if ( $this->isAnon() ) {
2494  $field = 'user_ip';
2495  $id = $this->getName();
2496  } else {
2497  $field = 'user_id';
2498  $id = $this->getId();
2499  }
2500 
2501  if ( $val ) {
2502  $changed = $this->updateNewtalk( $field, $id, $curRev );
2503  } else {
2504  $changed = $this->deleteNewtalk( $field, $id );
2505  }
2506 
2507  if ( $changed ) {
2508  $this->invalidateCache();
2509  }
2510  }
2511 
2518  private function newTouchedTimestamp() {
2519  $time = time();
2520  if ( $this->mTouched ) {
2521  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2522  }
2523 
2524  return wfTimestamp( TS_MW, $time );
2525  }
2526 
2537  public function clearSharedCache( $mode = 'refresh' ) {
2538  if ( !$this->getId() ) {
2539  return;
2540  }
2541 
2542  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2543  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2544  $key = $this->getCacheKey( $cache );
2545 
2546  if ( $mode === 'refresh' ) {
2547  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2548  } else {
2549  $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2550  function () use ( $cache, $key ) {
2551  $cache->delete( $key );
2552  },
2553  __METHOD__
2554  );
2555  }
2556  }
2557 
2563  public function invalidateCache() {
2564  $this->touch();
2565  $this->clearSharedCache( 'changed' );
2566  }
2567 
2580  public function touch() {
2581  $id = $this->getId();
2582  if ( $id ) {
2583  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2584  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2585  $cache->touchCheckKey( $key );
2586  $this->mQuickTouched = null;
2587  }
2588  }
2589 
2595  public function validateCache( $timestamp ) {
2596  return ( $timestamp >= $this->getTouched() );
2597  }
2598 
2607  public function getTouched() {
2608  $this->load();
2609 
2610  if ( $this->mId ) {
2611  if ( $this->mQuickTouched === null ) {
2612  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2613  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2614 
2615  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2616  }
2617 
2618  return max( $this->mTouched, $this->mQuickTouched );
2619  }
2620 
2621  return $this->mTouched;
2622  }
2623 
2629  public function getDBTouched() {
2630  $this->load();
2631 
2632  return $this->mTouched;
2633  }
2634 
2651  public function setPassword( $str ) {
2652  wfDeprecated( __METHOD__, '1.27' );
2653  return $this->setPasswordInternal( $str );
2654  }
2655 
2664  public function setInternalPassword( $str ) {
2665  wfDeprecated( __METHOD__, '1.27' );
2666  $this->setPasswordInternal( $str );
2667  }
2668 
2677  private function setPasswordInternal( $str ) {
2678  $manager = AuthManager::singleton();
2679 
2680  // If the user doesn't exist yet, fail
2681  if ( !$manager->userExists( $this->getName() ) ) {
2682  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2683  }
2684 
2685  $status = $this->changeAuthenticationData( [
2686  'username' => $this->getName(),
2687  'password' => $str,
2688  'retype' => $str,
2689  ] );
2690  if ( !$status->isGood() ) {
2692  ->info( __METHOD__ . ': Password change rejected: '
2693  . $status->getWikiText( null, null, 'en' ) );
2694  return false;
2695  }
2696 
2697  $this->setOption( 'watchlisttoken', false );
2698  SessionManager::singleton()->invalidateSessionsForUser( $this );
2699 
2700  return true;
2701  }
2702 
2715  public function changeAuthenticationData( array $data ) {
2716  $manager = AuthManager::singleton();
2717  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2718  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2719 
2720  $status = Status::newGood( 'ignored' );
2721  foreach ( $reqs as $req ) {
2722  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2723  }
2724  if ( $status->getValue() === 'ignored' ) {
2725  $status->warning( 'authenticationdatachange-ignored' );
2726  }
2727 
2728  if ( $status->isGood() ) {
2729  foreach ( $reqs as $req ) {
2730  $manager->changeAuthenticationData( $req );
2731  }
2732  }
2733  return $status;
2734  }
2735 
2742  public function getToken( $forceCreation = true ) {
2744 
2745  $this->load();
2746  if ( !$this->mToken && $forceCreation ) {
2747  $this->setToken();
2748  }
2749 
2750  if ( !$this->mToken ) {
2751  // The user doesn't have a token, return null to indicate that.
2752  return null;
2753  }
2754 
2755  if ( $this->mToken === self::INVALID_TOKEN ) {
2756  // We return a random value here so existing token checks are very
2757  // likely to fail.
2758  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2759  }
2760 
2761  if ( $wgAuthenticationTokenVersion === null ) {
2762  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2763  return $this->mToken;
2764  }
2765 
2766  // $wgAuthenticationTokenVersion in use, so hmac it.
2767  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2768 
2769  // The raw hash can be overly long. Shorten it up.
2770  $len = max( 32, self::TOKEN_LENGTH );
2771  if ( strlen( $ret ) < $len ) {
2772  // Should never happen, even md5 is 128 bits
2773  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2774  }
2775 
2776  return substr( $ret, -$len );
2777  }
2778 
2785  public function setToken( $token = false ) {
2786  $this->load();
2787  if ( $this->mToken === self::INVALID_TOKEN ) {
2789  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2790  } elseif ( !$token ) {
2791  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2792  } else {
2793  $this->mToken = $token;
2794  }
2795  }
2796 
2801  public function getEmail() {
2802  $this->load();
2803  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2804  return $this->mEmail;
2805  }
2806 
2812  $this->load();
2813  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2815  }
2816 
2821  public function setEmail( $str ) {
2822  $this->load();
2823  if ( $str == $this->mEmail ) {
2824  return;
2825  }
2826  $this->invalidateEmail();
2827  $this->mEmail = $str;
2828  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2829  }
2830 
2838  public function setEmailWithConfirmation( $str ) {
2840 
2841  if ( !$wgEnableEmail ) {
2842  return Status::newFatal( 'emaildisabled' );
2843  }
2844 
2845  $oldaddr = $this->getEmail();
2846  if ( $str === $oldaddr ) {
2847  return Status::newGood( true );
2848  }
2849 
2850  $type = $oldaddr != '' ? 'changed' : 'set';
2851  $notificationResult = null;
2852 
2853  if ( $wgEmailAuthentication && $type === 'changed' ) {
2854  // Send the user an email notifying the user of the change in registered
2855  // email address on their previous email address
2856  $change = $str != '' ? 'changed' : 'removed';
2857  $notificationResult = $this->sendMail(
2858  wfMessage( 'notificationemail_subject_' . $change )->text(),
2859  wfMessage( 'notificationemail_body_' . $change,
2860  $this->getRequest()->getIP(),
2861  $this->getName(),
2862  $str )->text()
2863  );
2864  }
2865 
2866  $this->setEmail( $str );
2867 
2868  if ( $str !== '' && $wgEmailAuthentication ) {
2869  // Send a confirmation request to the new address if needed
2870  $result = $this->sendConfirmationMail( $type );
2871 
2872  if ( $notificationResult !== null ) {
2873  $result->merge( $notificationResult );
2874  }
2875 
2876  if ( $result->isGood() ) {
2877  // Say to the caller that a confirmation and notification mail has been sent
2878  $result->value = 'eauth';
2879  }
2880  } else {
2881  $result = Status::newGood( true );
2882  }
2883 
2884  return $result;
2885  }
2886 
2891  public function getRealName() {
2892  if ( !$this->isItemLoaded( 'realname' ) ) {
2893  $this->load();
2894  }
2895 
2896  return $this->mRealName;
2897  }
2898 
2903  public function setRealName( $str ) {
2904  $this->load();
2905  $this->mRealName = $str;
2906  }
2907 
2918  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2919  global $wgHiddenPrefs;
2920  $this->loadOptions();
2921 
2922  # We want 'disabled' preferences to always behave as the default value for
2923  # users, even if they have set the option explicitly in their settings (ie they
2924  # set it, and then it was disabled removing their ability to change it). But
2925  # we don't want to erase the preferences in the database in case the preference
2926  # is re-enabled again. So don't touch $mOptions, just override the returned value
2927  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2928  return self::getDefaultOption( $oname );
2929  }
2930 
2931  if ( array_key_exists( $oname, $this->mOptions ) ) {
2932  return $this->mOptions[$oname];
2933  }
2934 
2935  return $defaultOverride;
2936  }
2937 
2946  public function getOptions( $flags = 0 ) {
2947  global $wgHiddenPrefs;
2948  $this->loadOptions();
2949  $options = $this->mOptions;
2950 
2951  # We want 'disabled' preferences to always behave as the default value for
2952  # users, even if they have set the option explicitly in their settings (ie they
2953  # set it, and then it was disabled removing their ability to change it). But
2954  # we don't want to erase the preferences in the database in case the preference
2955  # is re-enabled again. So don't touch $mOptions, just override the returned value
2956  foreach ( $wgHiddenPrefs as $pref ) {
2957  $default = self::getDefaultOption( $pref );
2958  if ( $default !== null ) {
2959  $options[$pref] = $default;
2960  }
2961  }
2962 
2963  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2964  $options = array_diff_assoc( $options, self::getDefaultOptions() );
2965  }
2966 
2967  return $options;
2968  }
2969 
2977  public function getBoolOption( $oname ) {
2978  return (bool)$this->getOption( $oname );
2979  }
2980 
2989  public function getIntOption( $oname, $defaultOverride = 0 ) {
2990  $val = $this->getOption( $oname );
2991  if ( $val == '' ) {
2992  $val = $defaultOverride;
2993  }
2994  return intval( $val );
2995  }
2996 
3005  public function setOption( $oname, $val ) {
3006  $this->loadOptions();
3007 
3008  // Explicitly NULL values should refer to defaults
3009  if ( is_null( $val ) ) {
3010  $val = self::getDefaultOption( $oname );
3011  }
3012 
3013  $this->mOptions[$oname] = $val;
3014  }
3015 
3026  public function getTokenFromOption( $oname ) {
3027  global $wgHiddenPrefs;
3028 
3029  $id = $this->getId();
3030  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3031  return false;
3032  }
3033 
3034  $token = $this->getOption( $oname );
3035  if ( !$token ) {
3036  // Default to a value based on the user token to avoid space
3037  // wasted on storing tokens for all users. When this option
3038  // is set manually by the user, only then is it stored.
3039  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3040  }
3041 
3042  return $token;
3043  }
3044 
3054  public function resetTokenFromOption( $oname ) {
3055  global $wgHiddenPrefs;
3056  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3057  return false;
3058  }
3059 
3060  $token = MWCryptRand::generateHex( 40 );
3061  $this->setOption( $oname, $token );
3062  return $token;
3063  }
3064 
3088  public static function listOptionKinds() {
3089  return [
3090  'registered',
3091  'registered-multiselect',
3092  'registered-checkmatrix',
3093  'userjs',
3094  'special',
3095  'unused'
3096  ];
3097  }
3098 
3111  public function getOptionKinds( IContextSource $context, $options = null ) {
3112  $this->loadOptions();
3113  if ( $options === null ) {
3114  $options = $this->mOptions;
3115  }
3116 
3117  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3118  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3119  $mapping = [];
3120 
3121  // Pull out the "special" options, so they don't get converted as
3122  // multiselect or checkmatrix.
3123  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3124  foreach ( $specialOptions as $name => $value ) {
3125  unset( $prefs[$name] );
3126  }
3127 
3128  // Multiselect and checkmatrix options are stored in the database with
3129  // one key per option, each having a boolean value. Extract those keys.
3130  $multiselectOptions = [];
3131  foreach ( $prefs as $name => $info ) {
3132  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3133  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3134  $opts = HTMLFormField::flattenOptions( $info['options'] );
3135  $prefix = $info['prefix'] ?? $name;
3136 
3137  foreach ( $opts as $value ) {
3138  $multiselectOptions["$prefix$value"] = true;
3139  }
3140 
3141  unset( $prefs[$name] );
3142  }
3143  }
3144  $checkmatrixOptions = [];
3145  foreach ( $prefs as $name => $info ) {
3146  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3147  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3148  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3149  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3150  $prefix = $info['prefix'] ?? $name;
3151 
3152  foreach ( $columns as $column ) {
3153  foreach ( $rows as $row ) {
3154  $checkmatrixOptions["$prefix$column-$row"] = true;
3155  }
3156  }
3157 
3158  unset( $prefs[$name] );
3159  }
3160  }
3161 
3162  // $value is ignored
3163  foreach ( $options as $key => $value ) {
3164  if ( isset( $prefs[$key] ) ) {
3165  $mapping[$key] = 'registered';
3166  } elseif ( isset( $multiselectOptions[$key] ) ) {
3167  $mapping[$key] = 'registered-multiselect';
3168  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3169  $mapping[$key] = 'registered-checkmatrix';
3170  } elseif ( isset( $specialOptions[$key] ) ) {
3171  $mapping[$key] = 'special';
3172  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3173  $mapping[$key] = 'userjs';
3174  } else {
3175  $mapping[$key] = 'unused';
3176  }
3177  }
3178 
3179  return $mapping;
3180  }
3181 
3196  public function resetOptions(
3197  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3198  IContextSource $context = null
3199  ) {
3200  $this->load();
3201  $defaultOptions = self::getDefaultOptions();
3202 
3203  if ( !is_array( $resetKinds ) ) {
3204  $resetKinds = [ $resetKinds ];
3205  }
3206 
3207  if ( in_array( 'all', $resetKinds ) ) {
3208  $newOptions = $defaultOptions;
3209  } else {
3210  if ( $context === null ) {
3212  }
3213 
3214  $optionKinds = $this->getOptionKinds( $context );
3215  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3216  $newOptions = [];
3217 
3218  // Use default values for the options that should be deleted, and
3219  // copy old values for the ones that shouldn't.
3220  foreach ( $this->mOptions as $key => $value ) {
3221  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3222  if ( array_key_exists( $key, $defaultOptions ) ) {
3223  $newOptions[$key] = $defaultOptions[$key];
3224  }
3225  } else {
3226  $newOptions[$key] = $value;
3227  }
3228  }
3229  }
3230 
3231  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3232 
3233  $this->mOptions = $newOptions;
3234  $this->mOptionsLoaded = true;
3235  }
3236 
3241  public function getDatePreference() {
3242  // Important migration for old data rows
3243  if ( is_null( $this->mDatePreference ) ) {
3244  global $wgLang;
3245  $value = $this->getOption( 'date' );
3246  $map = $wgLang->getDatePreferenceMigrationMap();
3247  if ( isset( $map[$value] ) ) {
3248  $value = $map[$value];
3249  }
3250  $this->mDatePreference = $value;
3251  }
3252  return $this->mDatePreference;
3253  }
3254 
3261  public function requiresHTTPS() {
3262  global $wgSecureLogin;
3263  if ( !$wgSecureLogin ) {
3264  return false;
3265  }
3266 
3267  $https = $this->getBoolOption( 'prefershttps' );
3268  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3269  if ( $https ) {
3270  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3271  }
3272 
3273  return $https;
3274  }
3275 
3281  public function getStubThreshold() {
3282  global $wgMaxArticleSize; # Maximum article size, in Kb
3283  $threshold = $this->getIntOption( 'stubthreshold' );
3284  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3285  // If they have set an impossible value, disable the preference
3286  // so we can use the parser cache again.
3287  $threshold = 0;
3288  }
3289  return $threshold;
3290  }
3291 
3300  public function getRights() {
3301  return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
3302  }
3303 
3310  public function getGroups() {
3311  $this->load();
3312  $this->loadGroups();
3313  return array_keys( $this->mGroupMemberships );
3314  }
3315 
3323  public function getGroupMemberships() {
3324  $this->load();
3325  $this->loadGroups();
3326  return $this->mGroupMemberships;
3327  }
3328 
3336  public function getEffectiveGroups( $recache = false ) {
3337  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3338  $this->mEffectiveGroups = array_unique( array_merge(
3339  $this->getGroups(), // explicit groups
3340  $this->getAutomaticGroups( $recache ) // implicit groups
3341  ) );
3342  // Avoid PHP 7.1 warning of passing $this by reference
3343  $user = $this;
3344  // Hook for additional groups
3345  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3346  // Force reindexation of groups when a hook has unset one of them
3347  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3348  }
3349  return $this->mEffectiveGroups;
3350  }
3351 
3359  public function getAutomaticGroups( $recache = false ) {
3360  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3361  $this->mImplicitGroups = [ '*' ];
3362  if ( $this->getId() ) {
3363  $this->mImplicitGroups[] = 'user';
3364 
3365  $this->mImplicitGroups = array_unique( array_merge(
3366  $this->mImplicitGroups,
3368  ) );
3369  }
3370  if ( $recache ) {
3371  // Assure data consistency with rights/groups,
3372  // as getEffectiveGroups() depends on this function
3373  $this->mEffectiveGroups = null;
3374  }
3375  }
3376  return $this->mImplicitGroups;
3377  }
3378 
3388  public function getFormerGroups() {
3389  $this->load();
3390 
3391  if ( is_null( $this->mFormerGroups ) ) {
3392  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3393  ? wfGetDB( DB_MASTER )
3394  : wfGetDB( DB_REPLICA );
3395  $res = $db->select( 'user_former_groups',
3396  [ 'ufg_group' ],
3397  [ 'ufg_user' => $this->mId ],
3398  __METHOD__ );
3399  $this->mFormerGroups = [];
3400  foreach ( $res as $row ) {
3401  $this->mFormerGroups[] = $row->ufg_group;
3402  }
3403  }
3404 
3405  return $this->mFormerGroups;
3406  }
3407 
3412  public function getEditCount() {
3413  if ( !$this->getId() ) {
3414  return null;
3415  }
3416 
3417  if ( $this->mEditCount === null ) {
3418  /* Populate the count, if it has not been populated yet */
3419  $dbr = wfGetDB( DB_REPLICA );
3420  // check if the user_editcount field has been initialized
3421  $count = $dbr->selectField(
3422  'user', 'user_editcount',
3423  [ 'user_id' => $this->mId ],
3424  __METHOD__
3425  );
3426 
3427  if ( $count === null ) {
3428  // it has not been initialized. do so.
3429  $count = $this->initEditCountInternal( $dbr );
3430  }
3431  $this->mEditCount = $count;
3432  }
3433  return (int)$this->mEditCount;
3434  }
3435 
3447  public function addGroup( $group, $expiry = null ) {
3448  $this->load();
3449  $this->loadGroups();
3450 
3451  if ( $expiry ) {
3452  $expiry = wfTimestamp( TS_MW, $expiry );
3453  }
3454 
3455  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3456  return false;
3457  }
3458 
3459  // create the new UserGroupMembership and put it in the DB
3460  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3461  if ( !$ugm->insert( true ) ) {
3462  return false;
3463  }
3464 
3465  $this->mGroupMemberships[$group] = $ugm;
3466 
3467  // Refresh the groups caches, and clear the rights cache so it will be
3468  // refreshed on the next call to $this->getRights().
3469  $this->getEffectiveGroups( true );
3470  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3471  $this->invalidateCache();
3472 
3473  return true;
3474  }
3475 
3482  public function removeGroup( $group ) {
3483  $this->load();
3484 
3485  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3486  return false;
3487  }
3488 
3489  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3490  // delete the membership entry
3491  if ( !$ugm || !$ugm->delete() ) {
3492  return false;
3493  }
3494 
3495  $this->loadGroups();
3496  unset( $this->mGroupMemberships[$group] );
3497 
3498  // Refresh the groups caches, and clear the rights cache so it will be
3499  // refreshed on the next call to $this->getRights().
3500  $this->getEffectiveGroups( true );
3501  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3502  $this->invalidateCache();
3503 
3504  return true;
3505  }
3506 
3516  public function isRegistered() {
3517  return $this->getId() != 0;
3518  }
3519 
3524  public function isLoggedIn() {
3525  return $this->isRegistered();
3526  }
3527 
3532  public function isAnon() {
3533  return !$this->isRegistered();
3534  }
3535 
3540  public function isBot() {
3541  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3542  return true;
3543  }
3544 
3545  $isBot = false;
3546  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3547 
3548  return $isBot;
3549  }
3550 
3560  public function isAllowedAny( ...$permissions ) {
3561  return MediaWikiServices::getInstance()
3562  ->getPermissionManager()
3563  ->userHasAnyRight( $this, ...$permissions );
3564  }
3565 
3572  public function isAllowedAll( ...$permissions ) {
3573  return MediaWikiServices::getInstance()
3574  ->getPermissionManager()
3575  ->userHasAllRights( $this, ...$permissions );
3576  }
3577 
3588  public function isAllowed( $action = '' ) {
3589  return MediaWikiServices::getInstance()->getPermissionManager()
3590  ->userHasRight( $this, $action );
3591  }
3592 
3597  public function useRCPatrol() {
3598  global $wgUseRCPatrol;
3599  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3600  }
3601 
3606  public function useNPPatrol() {
3608  return (
3610  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3611  );
3612  }
3613 
3618  public function useFilePatrol() {
3620  return (
3622  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3623  );
3624  }
3625 
3631  public function getRequest() {
3632  if ( $this->mRequest ) {
3633  return $this->mRequest;
3634  }
3635 
3636  global $wgRequest;
3637  return $wgRequest;
3638  }
3639 
3648  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3649  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3650  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3651  }
3652  return false;
3653  }
3654 
3662  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3663  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3664  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3665  $this,
3666  [ $title->getSubjectPage(), $title->getTalkPage() ]
3667  );
3668  }
3669  $this->invalidateCache();
3670  }
3671 
3679  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3680  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3681  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3682  $store->removeWatch( $this, $title->getSubjectPage() );
3683  $store->removeWatch( $this, $title->getTalkPage() );
3684  }
3685  $this->invalidateCache();
3686  }
3687 
3696  public function clearNotification( &$title, $oldid = 0 ) {
3698 
3699  // Do nothing if the database is locked to writes
3700  if ( wfReadOnly() ) {
3701  return;
3702  }
3703 
3704  // Do nothing if not allowed to edit the watchlist
3705  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3706  return;
3707  }
3708 
3709  // If we're working on user's talk page, we should update the talk page message indicator
3710  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3711  // Avoid PHP 7.1 warning of passing $this by reference
3712  $user = $this;
3713  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3714  return;
3715  }
3716 
3717  // Try to update the DB post-send and only if needed...
3718  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3719  if ( !$this->getNewtalk() ) {
3720  return; // no notifications to clear
3721  }
3722 
3723  // Delete the last notifications (they stack up)
3724  $this->setNewtalk( false );
3725 
3726  // If there is a new, unseen, revision, use its timestamp
3727  if ( $oldid ) {
3728  $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3729  $oldRev = $rl->getRevisionById( $oldid, Title::READ_LATEST );
3730  if ( $oldRev ) {
3731  $newRev = $rl->getNextRevision( $oldRev );
3732  if ( $newRev ) {
3733  // TODO: actually no need to wrap in a revision,
3734  // setNewtalk really only needs a RevRecord
3735  $this->setNewtalk( true, new Revision( $newRev ) );
3736  }
3737  }
3738  }
3739  } );
3740  }
3741 
3742  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3743  return;
3744  }
3745 
3746  if ( $this->isAnon() ) {
3747  // Nothing else to do...
3748  return;
3749  }
3750 
3751  // Only update the timestamp if the page is being watched.
3752  // The query to find out if it is watched is cached both in memcached and per-invocation,
3753  // and when it does have to be executed, it can be on a replica DB
3754  // If this is the user's newtalk page, we always update the timestamp
3755  $force = '';
3756  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3757  $force = 'force';
3758  }
3759 
3760  MediaWikiServices::getInstance()->getWatchedItemStore()
3761  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3762  }
3763 
3770  public function clearAllNotifications() {
3772  // Do nothing if not allowed to edit the watchlist
3773  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3774  return;
3775  }
3776 
3777  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3778  $this->setNewtalk( false );
3779  return;
3780  }
3781 
3782  $id = $this->getId();
3783  if ( !$id ) {
3784  return;
3785  }
3786 
3787  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
3788  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
3789 
3790  // We also need to clear here the "you have new message" notification for the own
3791  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3792  }
3793 
3799  public function getExperienceLevel() {
3800  global $wgLearnerEdits,
3804 
3805  if ( $this->isAnon() ) {
3806  return false;
3807  }
3808 
3809  $editCount = $this->getEditCount();
3810  $registration = $this->getRegistration();
3811  $now = time();
3812  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3813  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3814 
3815  if ( $editCount < $wgLearnerEdits ||
3816  $registration > $learnerRegistration ) {
3817  return 'newcomer';
3818  }
3819 
3820  if ( $editCount > $wgExperiencedUserEdits &&
3821  $registration <= $experiencedRegistration
3822  ) {
3823  return 'experienced';
3824  }
3825 
3826  return 'learner';
3827  }
3828 
3837  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3838  $this->load();
3839  if ( $this->mId == 0 ) {
3840  return;
3841  }
3842 
3843  $session = $this->getRequest()->getSession();
3844  if ( $request && $session->getRequest() !== $request ) {
3845  $session = $session->sessionWithRequest( $request );
3846  }
3847  $delay = $session->delaySave();
3848 
3849  if ( !$session->getUser()->equals( $this ) ) {
3850  if ( !$session->canSetUser() ) {
3852  ->warning( __METHOD__ .
3853  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3854  );
3855  return;
3856  }
3857  $session->setUser( $this );
3858  }
3859 
3860  $session->setRememberUser( $rememberMe );
3861  if ( $secure !== null ) {
3862  $session->setForceHTTPS( $secure );
3863  }
3864 
3865  $session->persist();
3866 
3867  ScopedCallback::consume( $delay );
3868  }
3869 
3873  public function logout() {
3874  // Avoid PHP 7.1 warning of passing $this by reference
3875  $user = $this;
3876  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
3877  $this->doLogout();
3878  }
3879  }
3880 
3885  public function doLogout() {
3886  $session = $this->getRequest()->getSession();
3887  if ( !$session->canSetUser() ) {
3889  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3890  $error = 'immutable';
3891  } elseif ( !$session->getUser()->equals( $this ) ) {
3893  ->warning( __METHOD__ .
3894  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3895  );
3896  // But we still may as well make this user object anon
3897  $this->clearInstanceCache( 'defaults' );
3898  $error = 'wronguser';
3899  } else {
3900  $this->clearInstanceCache( 'defaults' );
3901  $delay = $session->delaySave();
3902  $session->unpersist(); // Clear cookies (T127436)
3903  $session->setLoggedOutTimestamp( time() );
3904  $session->setUser( new User );
3905  $session->set( 'wsUserID', 0 ); // Other code expects this
3906  $session->resetAllTokens();
3907  ScopedCallback::consume( $delay );
3908  $error = false;
3909  }
3910  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3911  'event' => 'logout',
3912  'successful' => $error === false,
3913  'status' => $error ?: 'success',
3914  ] );
3915  }
3916 
3921  public function saveSettings() {
3922  if ( wfReadOnly() ) {
3923  // @TODO: caller should deal with this instead!
3924  // This should really just be an exception.
3926  null,
3927  "Could not update user with ID '{$this->mId}'; DB is read-only."
3928  ) );
3929  return;
3930  }
3931 
3932  $this->load();
3933  if ( $this->mId == 0 ) {
3934  return; // anon
3935  }
3936 
3937  // Get a new user_touched that is higher than the old one.
3938  // This will be used for a CAS check as a last-resort safety
3939  // check against race conditions and replica DB lag.
3940  $newTouched = $this->newTouchedTimestamp();
3941 
3942  $dbw = wfGetDB( DB_MASTER );
3943  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
3944  $dbw->update( 'user',
3945  [ /* SET */
3946  'user_name' => $this->mName,
3947  'user_real_name' => $this->mRealName,
3948  'user_email' => $this->mEmail,
3949  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3950  'user_touched' => $dbw->timestamp( $newTouched ),
3951  'user_token' => strval( $this->mToken ),
3952  'user_email_token' => $this->mEmailToken,
3953  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3954  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
3955  'user_id' => $this->mId,
3956  ] ), $fname
3957  );
3958 
3959  if ( !$dbw->affectedRows() ) {
3960  // Maybe the problem was a missed cache update; clear it to be safe
3961  $this->clearSharedCache( 'refresh' );
3962  // User was changed in the meantime or loaded with stale data
3963  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
3964  LoggerFactory::getInstance( 'preferences' )->warning(
3965  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
3966  [ 'user_id' => $this->mId, 'db_flag' => $from ]
3967  );
3968  throw new MWException( "CAS update failed on user_touched. " .
3969  "The version of the user to be saved is older than the current version."
3970  );
3971  }
3972 
3973  $dbw->update(
3974  'actor',
3975  [ 'actor_name' => $this->mName ],
3976  [ 'actor_user' => $this->mId ],
3977  $fname
3978  );
3979  } );
3980 
3981  $this->mTouched = $newTouched;
3982  $this->saveOptions();
3983 
3984  Hooks::run( 'UserSaveSettings', [ $this ] );
3985  $this->clearSharedCache( 'changed' );
3986  $this->getUserPage()->purgeSquid();
3987  }
3988 
3995  public function idForName( $flags = 0 ) {
3996  $s = trim( $this->getName() );
3997  if ( $s === '' ) {
3998  return 0;
3999  }
4000 
4001  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4002  ? wfGetDB( DB_MASTER )
4003  : wfGetDB( DB_REPLICA );
4004 
4005  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4006  ? [ 'LOCK IN SHARE MODE' ]
4007  : [];
4008 
4009  $id = $db->selectField( 'user',
4010  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4011 
4012  return (int)$id;
4013  }
4014 
4030  public static function createNew( $name, $params = [] ) {
4031  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4032  if ( isset( $params[$field] ) ) {
4033  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4034  unset( $params[$field] );
4035  }
4036  }
4037 
4038  $user = new User;
4039  $user->load();
4040  $user->setToken(); // init token
4041  if ( isset( $params['options'] ) ) {
4042  $user->mOptions = $params['options'] + (array)$user->mOptions;
4043  unset( $params['options'] );
4044  }
4045  $dbw = wfGetDB( DB_MASTER );
4046 
4047  $noPass = PasswordFactory::newInvalidPassword()->toString();
4048 
4049  $fields = [
4050  'user_name' => $name,
4051  'user_password' => $noPass,
4052  'user_newpassword' => $noPass,
4053  'user_email' => $user->mEmail,
4054  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4055  'user_real_name' => $user->mRealName,
4056  'user_token' => strval( $user->mToken ),
4057  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4058  'user_editcount' => 0,
4059  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4060  ];
4061  foreach ( $params as $name => $value ) {
4062  $fields["user_$name"] = $value;
4063  }
4064 
4065  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
4066  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4067  if ( $dbw->affectedRows() ) {
4068  $newUser = self::newFromId( $dbw->insertId() );
4069  $newUser->mName = $fields['user_name'];
4070  $newUser->updateActorId( $dbw );
4071  // Load the user from master to avoid replica lag
4072  $newUser->load( self::READ_LATEST );
4073  } else {
4074  $newUser = null;
4075  }
4076  return $newUser;
4077  } );
4078  }
4079 
4106  public function addToDatabase() {
4107  $this->load();
4108  if ( !$this->mToken ) {
4109  $this->setToken(); // init token
4110  }
4111 
4112  if ( !is_string( $this->mName ) ) {
4113  throw new RuntimeException( "User name field is not set." );
4114  }
4115 
4116  $this->mTouched = $this->newTouchedTimestamp();
4117 
4118  $dbw = wfGetDB( DB_MASTER );
4119  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
4120  $noPass = PasswordFactory::newInvalidPassword()->toString();
4121  $dbw->insert( 'user',
4122  [
4123  'user_name' => $this->mName,
4124  'user_password' => $noPass,
4125  'user_newpassword' => $noPass,
4126  'user_email' => $this->mEmail,
4127  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4128  'user_real_name' => $this->mRealName,
4129  'user_token' => strval( $this->mToken ),
4130  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4131  'user_editcount' => 0,
4132  'user_touched' => $dbw->timestamp( $this->mTouched ),
4133  ], $fname,
4134  [ 'IGNORE' ]
4135  );
4136  if ( !$dbw->affectedRows() ) {
4137  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4138  $this->mId = $dbw->selectField(
4139  'user',
4140  'user_id',
4141  [ 'user_name' => $this->mName ],
4142  $fname,
4143  [ 'LOCK IN SHARE MODE' ]
4144  );
4145  $loaded = false;
4146  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
4147  $loaded = true;
4148  }
4149  if ( !$loaded ) {
4150  throw new MWException( $fname . ": hit a key conflict attempting " .
4151  "to insert user '{$this->mName}' row, but it was not present in select!" );
4152  }
4153  return Status::newFatal( 'userexists' );
4154  }
4155  $this->mId = $dbw->insertId();
4156  self::$idCacheByName[$this->mName] = $this->mId;
4157  $this->updateActorId( $dbw );
4158 
4159  return Status::newGood();
4160  } );
4161  if ( !$status->isGood() ) {
4162  return $status;
4163  }
4164 
4165  // Clear instance cache other than user table data and actor, which is already accurate
4166  $this->clearInstanceCache();
4167 
4168  $this->saveOptions();
4169  return Status::newGood();
4170  }
4171 
4176  private function updateActorId( IDatabase $dbw ) {
4177  $dbw->insert(
4178  'actor',
4179  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4180  __METHOD__
4181  );
4182  $this->mActorId = (int)$dbw->insertId();
4183  }
4184 
4190  public function spreadAnyEditBlock() {
4191  if ( $this->isLoggedIn() && $this->getBlock() ) {
4192  return $this->spreadBlock();
4193  }
4194 
4195  return false;
4196  }
4197 
4203  protected function spreadBlock() {
4204  wfDebug( __METHOD__ . "()\n" );
4205  $this->load();
4206  if ( $this->mId == 0 ) {
4207  return false;
4208  }
4209 
4210  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
4211  if ( !$userblock ) {
4212  return false;
4213  }
4214 
4215  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4216  }
4217 
4222  public function isBlockedFromCreateAccount() {
4223  $this->getBlockedStatus();
4224  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4225  return $this->mBlock;
4226  }
4227 
4228  # T15611: if the IP address the user is trying to create an account from is
4229  # blocked with createaccount disabled, prevent new account creation there even
4230  # when the user is logged in
4231  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4232  $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
4233  null, $this->getRequest()->getIP()
4234  );
4235  }
4236  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
4237  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4238  ? $this->mBlockedFromCreateAccount
4239  : false;
4240  }
4241 
4246  public function isBlockedFromEmailuser() {
4247  $this->getBlockedStatus();
4248  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4249  }
4250 
4257  public function isBlockedFromUpload() {
4258  $this->getBlockedStatus();
4259  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4260  }
4261 
4266  public function isAllowedToCreateAccount() {
4267  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4268  }
4269 
4275  public function getUserPage() {
4276  return Title::makeTitle( NS_USER, $this->getName() );
4277  }
4278 
4284  public function getTalkPage() {
4285  $title = $this->getUserPage();
4286  return $title->getTalkPage();
4287  }
4288 
4294  public function isNewbie() {
4295  return !$this->isAllowed( 'autoconfirmed' );
4296  }
4297 
4304  public function checkPassword( $password ) {
4305  wfDeprecated( __METHOD__, '1.27' );
4306 
4307  $manager = AuthManager::singleton();
4308  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4309  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4310  [
4311  'username' => $this->getName(),
4312  'password' => $password,
4313  ]
4314  );
4315  $res = $manager->beginAuthentication( $reqs, 'null:' );
4316  switch ( $res->status ) {
4317  case AuthenticationResponse::PASS:
4318  return true;
4319  case AuthenticationResponse::FAIL:
4320  // Hope it's not a PreAuthenticationProvider that failed...
4321  LoggerFactory::getInstance( 'authentication' )
4322  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4323  return false;
4324  default:
4325  throw new BadMethodCallException(
4326  'AuthManager returned a response unsupported by ' . __METHOD__
4327  );
4328  }
4329  }
4330 
4339  public function checkTemporaryPassword( $plaintext ) {
4340  wfDeprecated( __METHOD__, '1.27' );
4341  // Can't check the temporary password individually.
4342  return $this->checkPassword( $plaintext );
4343  }
4344 
4356  public function getEditTokenObject( $salt = '', $request = null ) {
4357  if ( $this->isAnon() ) {
4358  return new LoggedOutEditToken();
4359  }
4360 
4361  if ( !$request ) {
4362  $request = $this->getRequest();
4363  }
4364  return $request->getSession()->getToken( $salt );
4365  }
4366 
4380  public function getEditToken( $salt = '', $request = null ) {
4381  return $this->getEditTokenObject( $salt, $request )->toString();
4382  }
4383 
4396  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4397  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4398  }
4399 
4410  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4411  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4412  return $this->matchEditToken( $val, $salt, $request, $maxage );
4413  }
4414 
4422  public function sendConfirmationMail( $type = 'created' ) {
4423  global $wgLang;
4424  $expiration = null; // gets passed-by-ref and defined in next line.
4425  $token = $this->confirmationToken( $expiration );
4426  $url = $this->confirmationTokenUrl( $token );
4427  $invalidateURL = $this->invalidationTokenUrl( $token );
4428  $this->saveSettings();
4429 
4430  if ( $type == 'created' || $type === false ) {
4431  $message = 'confirmemail_body';
4432  $type = 'created';
4433  } elseif ( $type === true ) {
4434  $message = 'confirmemail_body_changed';
4435  $type = 'changed';
4436  } else {
4437  // Messages: confirmemail_body_changed, confirmemail_body_set
4438  $message = 'confirmemail_body_' . $type;
4439  }
4440 
4441  $mail = [
4442  'subject' => wfMessage( 'confirmemail_subject' )->text(),
4443  'body' => wfMessage( $message,
4444  $this->getRequest()->getIP(),
4445  $this->getName(),
4446  $url,
4447  $wgLang->userTimeAndDate( $expiration, $this ),
4448  $invalidateURL,
4449  $wgLang->userDate( $expiration, $this ),
4450  $wgLang->userTime( $expiration, $this ) )->text(),
4451  'from' => null,
4452  'replyTo' => null,
4453  ];
4454  $info = [
4455  'type' => $type,
4456  'ip' => $this->getRequest()->getIP(),
4457  'confirmURL' => $url,
4458  'invalidateURL' => $invalidateURL,
4459  'expiration' => $expiration
4460  ];
4461 
4462  Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4463  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4464  }
4465 
4477  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4478  global $wgPasswordSender;
4479 
4480  if ( $from instanceof User ) {
4481  $sender = MailAddress::newFromUser( $from );
4482  } else {
4483  $sender = new MailAddress( $wgPasswordSender,
4484  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4485  }
4486  $to = MailAddress::newFromUser( $this );
4487 
4488  return UserMailer::send( $to, $sender, $subject, $body, [
4489  'replyTo' => $replyto,
4490  ] );
4491  }
4492 
4503  protected function confirmationToken( &$expiration ) {
4505  $now = time();
4506  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4507  $expiration = wfTimestamp( TS_MW, $expires );
4508  $this->load();
4509  $token = MWCryptRand::generateHex( 32 );
4510  $hash = md5( $token );
4511  $this->mEmailToken = $hash;
4512  $this->mEmailTokenExpires = $expiration;
4513  return $token;
4514  }
4515 
4521  protected function confirmationTokenUrl( $token ) {
4522  return $this->getTokenUrl( 'ConfirmEmail', $token );
4523  }
4524 
4530  protected function invalidationTokenUrl( $token ) {
4531  return $this->getTokenUrl( 'InvalidateEmail', $token );
4532  }
4533 
4548  protected function getTokenUrl( $page, $token ) {
4549  // Hack to bypass localization of 'Special:'
4550  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4551  return $title->getCanonicalURL();
4552  }
4553 
4561  public function confirmEmail() {
4562  // Check if it's already confirmed, so we don't touch the database
4563  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4564  if ( !$this->isEmailConfirmed() ) {
4566  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4567  }
4568  return true;
4569  }
4570 
4578  public function invalidateEmail() {
4579  $this->load();
4580  $this->mEmailToken = null;
4581  $this->mEmailTokenExpires = null;
4582  $this->setEmailAuthenticationTimestamp( null );
4583  $this->mEmail = '';
4584  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4585  return true;
4586  }
4587 
4592  public function setEmailAuthenticationTimestamp( $timestamp ) {
4593  $this->load();
4594  $this->mEmailAuthenticated = $timestamp;
4595  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4596  }
4597 
4603  public function canSendEmail() {
4605  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4606  return false;
4607  }
4608  $canSend = $this->isEmailConfirmed();
4609  // Avoid PHP 7.1 warning of passing $this by reference
4610  $user = $this;
4611  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4612  return $canSend;
4613  }
4614 
4620  public function canReceiveEmail() {
4621  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4622  }
4623 
4634  public function isEmailConfirmed() {
4635  global $wgEmailAuthentication;
4636  $this->load();
4637  // Avoid PHP 7.1 warning of passing $this by reference
4638  $user = $this;
4639  $confirmed = true;
4640  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4641  if ( $this->isAnon() ) {
4642  return false;
4643  }
4644  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4645  return false;
4646  }
4648  return false;
4649  }
4650  return true;
4651  }
4652 
4653  return $confirmed;
4654  }
4655 
4660  public function isEmailConfirmationPending() {
4661  global $wgEmailAuthentication;
4662  return $wgEmailAuthentication &&
4663  !$this->isEmailConfirmed() &&
4664  $this->mEmailToken &&
4665  $this->mEmailTokenExpires > wfTimestamp();
4666  }
4667 
4675  public function getRegistration() {
4676  if ( $this->isAnon() ) {
4677  return false;
4678  }
4679  $this->load();
4680  return $this->mRegistration;
4681  }
4682 
4689  public function getFirstEditTimestamp() {
4690  return $this->getEditTimestamp( true );
4691  }
4692 
4700  public function getLatestEditTimestamp() {
4701  return $this->getEditTimestamp( false );
4702  }
4703 
4711  private function getEditTimestamp( $first ) {
4712  if ( $this->getId() == 0 ) {
4713  return false; // anons
4714  }
4715  $dbr = wfGetDB( DB_REPLICA );
4716  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4717  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4718  ? 'revactor_timestamp' : 'rev_timestamp';
4719  $sortOrder = $first ? 'ASC' : 'DESC';
4720  $time = $dbr->selectField(
4721  [ 'revision' ] + $actorWhere['tables'],
4722  $tsField,
4723  [ $actorWhere['conds'] ],
4724  __METHOD__,
4725  [ 'ORDER BY' => "$tsField $sortOrder" ],
4726  $actorWhere['joins']
4727  );
4728  if ( !$time ) {
4729  return false; // no edits
4730  }
4731  return wfTimestamp( TS_MW, $time );
4732  }
4733 
4743  public static function getGroupPermissions( $groups ) {
4744  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4745  }
4746 
4756  public static function getGroupsWithPermission( $role ) {
4757  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4758  }
4759 
4775  public static function groupHasPermission( $group, $role ) {
4776  return MediaWikiServices::getInstance()->getPermissionManager()
4777  ->groupHasPermission( $group, $role );
4778  }
4779 
4798  public static function isEveryoneAllowed( $right ) {
4799  return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4800  }
4801 
4808  public static function getAllGroups() {
4810  return array_values( array_diff(
4811  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4812  self::getImplicitGroups()
4813  ) );
4814  }
4815 
4823  public static function getAllRights() {
4824  return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4825  }
4826 
4833  public static function getImplicitGroups() {
4834  global $wgImplicitGroups;
4835  return $wgImplicitGroups;
4836  }
4837 
4847  public static function changeableByGroup( $group ) {
4849 
4850  $groups = [
4851  'add' => [],
4852  'remove' => [],
4853  'add-self' => [],
4854  'remove-self' => []
4855  ];
4856 
4857  if ( empty( $wgAddGroups[$group] ) ) {
4858  // Don't add anything to $groups
4859  } elseif ( $wgAddGroups[$group] === true ) {
4860  // You get everything
4861  $groups['add'] = self::getAllGroups();
4862  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4863  $groups['add'] = $wgAddGroups[$group];
4864  }
4865 
4866  // Same thing for remove
4867  if ( empty( $wgRemoveGroups[$group] ) ) {
4868  // Do nothing
4869  } elseif ( $wgRemoveGroups[$group] === true ) {
4870  $groups['remove'] = self::getAllGroups();
4871  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4872  $groups['remove'] = $wgRemoveGroups[$group];
4873  }
4874 
4875  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4876  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4877  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4878  if ( is_int( $key ) ) {
4879  $wgGroupsAddToSelf['user'][] = $value;
4880  }
4881  }
4882  }
4883 
4884  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4885  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4886  if ( is_int( $key ) ) {
4887  $wgGroupsRemoveFromSelf['user'][] = $value;
4888  }
4889  }
4890  }
4891 
4892  // Now figure out what groups the user can add to him/herself
4893  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4894  // Do nothing
4895  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4896  // No idea WHY this would be used, but it's there
4897  $groups['add-self'] = self::getAllGroups();
4898  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4899  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4900  }
4901 
4902  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4903  // Do nothing
4904  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4905  $groups['remove-self'] = self::getAllGroups();
4906  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4907  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4908  }
4909 
4910  return $groups;
4911  }
4912 
4920  public function changeableGroups() {
4921  if ( $this->isAllowed( 'userrights' ) ) {
4922  // This group gives the right to modify everything (reverse-
4923  // compatibility with old "userrights lets you change
4924  // everything")
4925  // Using array_merge to make the groups reindexed
4926  $all = array_merge( self::getAllGroups() );
4927  return [
4928  'add' => $all,
4929  'remove' => $all,
4930  'add-self' => [],
4931  'remove-self' => []
4932  ];
4933  }
4934 
4935  // Okay, it's not so simple, we will have to go through the arrays
4936  $groups = [
4937  'add' => [],
4938  'remove' => [],
4939  'add-self' => [],
4940  'remove-self' => []
4941  ];
4942  $addergroups = $this->getEffectiveGroups();
4943 
4944  foreach ( $addergroups as $addergroup ) {
4945  $groups = array_merge_recursive(
4946  $groups, $this->changeableByGroup( $addergroup )
4947  );
4948  $groups['add'] = array_unique( $groups['add'] );
4949  $groups['remove'] = array_unique( $groups['remove'] );
4950  $groups['add-self'] = array_unique( $groups['add-self'] );
4951  $groups['remove-self'] = array_unique( $groups['remove-self'] );
4952  }
4953  return $groups;
4954  }
4955 
4959  public function incEditCount() {
4960  if ( $this->isAnon() ) {
4961  return; // sanity
4962  }
4963 
4965  new UserEditCountUpdate( $this, 1 ),
4967  );
4968  }
4969 
4975  public function setEditCountInternal( $count ) {
4976  $this->mEditCount = $count;
4977  }
4978 
4986  public function initEditCountInternal( IDatabase $dbr ) {
4987  // Pull from a replica DB to be less cruel to servers
4988  // Accuracy isn't the point anyway here
4989  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4990  $count = (int)$dbr->selectField(
4991  [ 'revision' ] + $actorWhere['tables'],
4992  'COUNT(*)',
4993  [ $actorWhere['conds'] ],
4994  __METHOD__,
4995  [],
4996  $actorWhere['joins']
4997  );
4998 
4999  $dbw = wfGetDB( DB_MASTER );
5000  $dbw->update(
5001  'user',
5002  [ 'user_editcount' => $count ],
5003  [
5004  'user_id' => $this->getId(),
5005  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5006  ],
5007  __METHOD__
5008  );
5009 
5010  return $count;
5011  }
5012 
5020  public static function getRightDescription( $right ) {
5021  $key = "right-$right";
5022  $msg = wfMessage( $key );
5023  return $msg->isDisabled() ? $right : $msg->text();
5024  }
5025 
5033  public static function getGrantName( $grant ) {
5034  $key = "grant-$grant";
5035  $msg = wfMessage( $key );
5036  return $msg->isDisabled() ? $grant : $msg->text();
5037  }
5038 
5059  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5060  return true; // disabled
5061  }
5062 
5071  public function addNewUserLogEntryAutoCreate() {
5072  wfDeprecated( __METHOD__, '1.27' );
5073  $this->addNewUserLogEntry( 'autocreate' );
5074 
5075  return true;
5076  }
5077 
5083  protected function loadOptions( $data = null ) {
5084  $this->load();
5085 
5086  if ( $this->mOptionsLoaded ) {
5087  return;
5088  }
5089 
5090  $this->mOptions = self::getDefaultOptions();
5091 
5092  if ( !$this->getId() ) {
5093  // For unlogged-in users, load language/variant options from request.
5094  // There's no need to do it for logged-in users: they can set preferences,
5095  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5096  // so don't override user's choice (especially when the user chooses site default).
5097  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5098  $this->mOptions['variant'] = $variant;
5099  $this->mOptions['language'] = $variant;
5100  $this->mOptionsLoaded = true;
5101  return;
5102  }
5103 
5104  // Maybe load from the object
5105  if ( !is_null( $this->mOptionOverrides ) ) {
5106  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5107  foreach ( $this->mOptionOverrides as $key => $value ) {
5108  $this->mOptions[$key] = $value;
5109  }
5110  } else {
5111  if ( !is_array( $data ) ) {
5112  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5113  // Load from database
5114  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5115  ? wfGetDB( DB_MASTER )
5116  : wfGetDB( DB_REPLICA );
5117 
5118  $res = $dbr->select(
5119  'user_properties',
5120  [ 'up_property', 'up_value' ],
5121  [ 'up_user' => $this->getId() ],
5122  __METHOD__
5123  );
5124 
5125  $this->mOptionOverrides = [];
5126  $data = [];
5127  foreach ( $res as $row ) {
5128  // Convert '0' to 0. PHP's boolean conversion considers them both
5129  // false, but e.g. JavaScript considers the former as true.
5130  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5131  // and convert all values here.
5132  if ( $row->up_value === '0' ) {
5133  $row->up_value = 0;
5134  }
5135  $data[$row->up_property] = $row->up_value;
5136  }
5137  }
5138 
5139  foreach ( $data as $property => $value ) {
5140  $this->mOptionOverrides[$property] = $value;
5141  $this->mOptions[$property] = $value;
5142  }
5143  }
5144 
5145  // Replace deprecated language codes
5146  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5147  $this->mOptions['language']
5148  );
5149 
5150  $this->mOptionsLoaded = true;
5151 
5152  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5153  }
5154 
5160  protected function saveOptions() {
5161  $this->loadOptions();
5162 
5163  // Not using getOptions(), to keep hidden preferences in database
5164  $saveOptions = $this->mOptions;
5165 
5166  // Allow hooks to abort, for instance to save to a global profile.
5167  // Reset options to default state before saving.
5168  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5169  return;
5170  }
5171 
5172  $userId = $this->getId();
5173 
5174  $insert_rows = []; // all the new preference rows
5175  foreach ( $saveOptions as $key => $value ) {
5176  // Don't bother storing default values
5177  $defaultOption = self::getDefaultOption( $key );
5178  if ( ( $defaultOption === null && $value !== false && $value !== null )
5179  || $value != $defaultOption
5180  ) {
5181  $insert_rows[] = [
5182  'up_user' => $userId,
5183  'up_property' => $key,
5184  'up_value' => $value,
5185  ];
5186  }
5187  }
5188 
5189  $dbw = wfGetDB( DB_MASTER );
5190 
5191  $res = $dbw->select( 'user_properties',
5192  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5193 
5194  // Find prior rows that need to be removed or updated. These rows will
5195  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5196  $keysDelete = [];
5197  foreach ( $res as $row ) {
5198  if ( !isset( $saveOptions[$row->up_property] )
5199  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5200  ) {
5201  $keysDelete[] = $row->up_property;
5202  }
5203  }
5204 
5205  if ( count( $keysDelete ) ) {
5206  // Do the DELETE by PRIMARY KEY for prior rows.
5207  // In the past a very large portion of calls to this function are for setting
5208  // 'rememberpassword' for new accounts (a preference that has since been removed).
5209  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5210  // caused gap locks on [max user ID,+infinity) which caused high contention since
5211  // updates would pile up on each other as they are for higher (newer) user IDs.
5212  // It might not be necessary these days, but it shouldn't hurt either.
5213  $dbw->delete( 'user_properties',
5214  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5215  }
5216  // Insert the new preference rows
5217  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5218  }
5219 
5226  public static function selectFields() {
5227  wfDeprecated( __METHOD__, '1.31' );
5228  return [
5229  'user_id',
5230  'user_name',
5231  'user_real_name',
5232  'user_email',
5233  'user_touched',
5234  'user_token',
5235  'user_email_authenticated',
5236  'user_email_token',
5237  'user_email_token_expires',
5238  'user_registration',
5239  'user_editcount',
5240  ];
5241  }
5242 
5252  public static function getQueryInfo() {
5253  $ret = [
5254  'tables' => [ 'user', 'user_actor' => 'actor' ],
5255  'fields' => [
5256  'user_id',
5257  'user_name',
5258  'user_real_name',
5259  'user_email',
5260  'user_touched',
5261  'user_token',
5262  'user_email_authenticated',
5263  'user_email_token',
5264  'user_email_token_expires',
5265  'user_registration',
5266  'user_editcount',
5267  'user_actor.actor_id',
5268  ],
5269  'joins' => [
5270  'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
5271  ],
5272  ];
5273 
5274  return $ret;
5275  }
5276 
5284  static function newFatalPermissionDeniedStatus( $permission ) {
5285  global $wgLang;
5286 
5287  $groups = [];
5288  foreach ( MediaWikiServices::getInstance()
5290  ->getGroupsWithPermission( $permission ) as $group ) {
5291  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5292  }
5293 
5294  if ( $groups ) {
5295  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5296  }
5297 
5298  return Status::newFatal( 'badaccess-group0' );
5299  }
5300 
5310  public function getInstanceForUpdate() {
5311  if ( !$this->getId() ) {
5312  return null; // anon
5313  }
5314 
5315  $user = self::newFromId( $this->getId() );
5316  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5317  return null;
5318  }
5319 
5320  return $user;
5321  }
5322 
5330  public function equals( UserIdentity $user ) {
5331  // XXX it's not clear whether central ID providers are supposed to obey this
5332  return $this->getName() === $user->getName();
5333  }
5334 
5340  public function isAllowUsertalk() {
5341  return $this->mAllowUsertalk;
5342  }
5343 
5344 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1693
User\saveOptions
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5160
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:4900
User\$mNewtalk
int bool $mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:176
LanguageCode\replaceDeprecatedCodes
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date,...
Definition: LanguageCode.php:164
User\updateNewtalk
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2440
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:388
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:303
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2327
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:1790
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:539
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:316
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:4521
User\__set
__set( $name, $value)
Definition: User.php:257
$wgProxyList
$wgProxyList
Big list of banned IP addresses.
Definition: DefaultSettings.php:5999
WikiMap\getCurrentWikiDbDomain
static getCurrentWikiDbDomain()
Definition: WikiMap.php:292
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:2947
User\$mToken
string $mToken
Definition: User.php:131
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:342
User\resetGetDefaultOptionsForTestsOnly
static resetGetDefaultOptionsForTestsOnly()
Reset the process cache of default user options.
Definition: User.php:1639
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1071
User\getId
getId()
Get the user's ID.
Definition: User.php:2203
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3618
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2295
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:3532
User\$mBlockedFromCreateAccount
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:210
User\$mBlockreason
string $mBlockreason
Definition: User.php:184
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4548
User\isRegistered
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3516
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2271
User\$mImplicitGroups
array $mImplicitGroups
Definition: User.php:188
User\isLocallyBlockedProxy
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1838
User\$mCacheVars
static string[] $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:92
$wgRevokePermissions
$wgRevokePermissions
Permission keys revoked from users in each group.
Definition: DefaultSettings.php:5267
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3054
User\isBlockedFrom
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2084
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1460
WikiMap\isCurrentWikiId
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:312
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5284
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:4356
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:7034
IP\isInRanges
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:655
User\isBot
isBot()
Definition: User.php:3540
User\$mBlock
AbstractBlock $mBlock
Definition: User.php:204
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:2518
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:8963
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3412
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:4203
User\incEditCount
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition: User.php:4959
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:674
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:3111
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4660
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:2111
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2989
true
return true
Definition: router.php:92
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:2946
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:127
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:229
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1869
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2742
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:4190
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2398
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:207
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1805
User\$mOptions
array $mOptions
Definition: User.php:198
User\loadOptions
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5083
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:4832
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:37
User\setEmailWithConfirmation
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:2838
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1693
User\$mHideName
bool $mHideName
Definition: User.php:196
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3281
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1171
User\$mLocked
bool $mLocked
Definition: User.php:194
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:515
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1348
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1264
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4592
IP\isIPv6
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
User\$mEmail
string $mEmail
Definition: User.php:125
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:4275
User\$mOptionOverrides
array $mOptionOverrides
Definition: User.php:145
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3310
$s
$s
Definition: mergeMessageFileList.php:185
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:571
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:3606
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:3241
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:286
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:2821
User\idForName
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:3995
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:917
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:4422
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4775
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2811
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:1911
UserEditCountUpdate
Handles increment the edit count for a given set of users.
Definition: UserEditCountUpdate.php:29
$res
$res
Definition: testCompression.php:52
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:3597
$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:4578
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:696
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6927
User\loadGroups
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1470
User\$mHash
string $mHash
Definition: User.php:182
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:6943
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:902
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
User\deleteNewtalk
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2465
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:136
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:3300
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4030
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3631
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:3359
User\INVALID_TOKEN
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:61
User\setPassword
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2651
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1651
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5310
$dbr
$dbr
Definition: testCompression.php:50
$wgMaxNameChars
$wgMaxNameChars
Maximum number of bytes in username.
Definition: DefaultSettings.php:4805
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:8962
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:737
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:3447
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:4396
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2801
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:4284
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4106
IP\isValidRange
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:125
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:2563
User\setInternalPassword
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2664
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:4530
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2171
User\$mDatePreference
string $mDatePreference
Definition: User.php:178
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:4938
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:1097
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:4561
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1244
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:2102
getPermissionManager
getPermissionManager()
WikiMap\getWikiIdFromDbDomain
static getWikiIdFromDbDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:268
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2485
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2575
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4266
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:3873
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4503
User\initEditCountInternal
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition: User.php:4986
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:429
$wgLang
$wgLang
Definition: Setup.php:881
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1885
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:4833
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2186
User\$mBlockedby
string $mBlockedby
Definition: User.php:180
UserGroupMembership\newFromRow
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
Definition: UserGroupMembership.php:93
IP\getSubnet
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition: IP.php:729
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4246
User\loadDefaults
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition: User.php:1195
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:891
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2595
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3336
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1886
$t
$t
Definition: make-normalization-table.php:143
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:70
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3482
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4620
$wgImplicitGroups
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
Definition: DefaultSettings.php:5272
User\$mOptionsLoaded
bool $mOptionsLoaded
Whether the cache variables have been loaded.
Definition: User.php:152
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4294
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2580
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:34
User\clearInstanceCache
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1601
$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:78
User\TOKEN_LENGTH
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:56
$wgDefaultSkin
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
Definition: DefaultSettings.php:3318
$wgReservedUsernames
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
Definition: DefaultSettings.php:4811
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1898
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3662
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:129
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:596
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2068
User\setName
setName( $str)
Set the user name.
Definition: User.php:2260
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:122
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:3196
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:42
User\$mGroupMemberships
UserGroupMembership[] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:143
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:6954
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:240
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5521
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3696
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:3921
User\addNewUserLogEntry
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5059
User\$defOpt
static array null $defOpt
Is the user an IP range?
Definition: User.php:1629
User\isAllowedAll
isAllowedAll(... $permissions)
Definition: User.php:3572
User\$defOptLang
static string null $defOptLang
Is the user an IP range?
Definition: User.php:1631
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:932
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4689
$wgAutopromoteOnceLogInRC
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
Definition: DefaultSettings.php:5492
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:73
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:2903
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2365
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:613
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:3388
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:809
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:3560
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1255
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:5683
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2124
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2918
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:1777
User\getTouched
getTouched()
Get the user touched timestamp.
Definition: User.php:2607
User\$mId
int $mId
Cache variables.
Definition: User.php:116
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2138
User\__toString
__toString()
Definition: User.php:236
MediaWiki\Block\SystemBlock
System blocks are temporary blocks that are created on enforcement (e.g.
Definition: SystemBlock.php:35
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:1563
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:116
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:2891
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4176
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:3837
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4743
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4920
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3770
User\VERSION
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:67
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:4410
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5516
User\checkPassword
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4304
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4808
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3679
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4823
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:4257
$wgInvalidUsernameCharacters
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
Definition: DefaultSettings.php:4907
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:431
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:5252
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2093
User\$mLoadedItems
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:157
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:8960
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2629
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:85
IP\isIPv4
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
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:42
User\getEditTimestamp
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition: User.php:4711
$context
$context
Definition: load.php:45
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4847
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:438
Autopromote\getAutopromoteOnceGroups
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:65
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2223
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3799
$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:4675
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:241
IP\sanitizeIP
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:139
User\isEveryoneAllowed
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4798
$status
return $status
Definition: SyntaxHighlight.php:347
MediaWiki\User\UserIdentity\getId
getId()
MediaWiki\Block\AbstractBlock\appliesToRight
appliesToRight( $right)
Determine whether the block prevents a given right.
Definition: AbstractBlock.php:257
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5020
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2715
$cache
$cache
Definition: mcc.php:33
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:5765
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3524
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:4339
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:120
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2319
User\purge
static purge( $dbDomain, $userId)
Definition: User.php:418
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2785
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:829
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:871
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:137
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1001
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1139
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4634
User\$mGlobalBlock
AbstractBlock $mGlobalBlock
Definition: User.php:192
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5340
User\getBlockedStatus
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1707
User\$idCacheByName
static int[] $idCacheByName
Definition: User.php:216
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5033
UserCache\singleton
static singleton()
Definition: UserCache.php:34
User\clearSharedCache
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2537
User\isBlocked
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:2057
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:554
$keys
$keys
Definition: testCompression.php:67
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:452
$wgLearnerMemberSince
$wgLearnerMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:8961
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1494
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4477
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:3026
User\$mEmailToken
string $mEmailToken
Definition: User.php:135
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:37
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:5300
User\equals
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5330
User\checkNewtalk
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2425
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1295
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:1093
User\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5226
User\$mName
string $mName
Definition: User.php:118
User\$mRegistration
string $mRegistration
Definition: User.php:139
$wgGroupPermissions
$wgGroupPermissions['sysop']['replacetext']
Definition: ReplaceText.php:56
MediaWiki\Block\AbstractBlock
Definition: AbstractBlock.php:34
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for the wiki.
Definition: DefaultSettings.php:4476
User\addNewUserLogEntryAutoCreate
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5071
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:650
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:83
User\$mRequest
WebRequest $mRequest
Definition: User.php:201
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:752
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3648
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:2977
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:5295
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:966
User\$mFormerGroups
array $mFormerGroups
Definition: User.php:190
User\makeUpdateConditions
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1545
$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:4222
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:4380
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3261
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1234
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:124
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:3005
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:213
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:819
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:2232
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:4926
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3885
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:3323
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4603
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1041
$wgNamespacesToBeSearchedDefault
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
Definition: DefaultSettings.php:6600
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:440
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:4700
User\$mEffectiveGroups
array $mEffectiveGroups
Definition: User.php:186
User\setEditCountInternal
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:4975
User\setPasswordInternal
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2677
$wgDisableAnonTalk
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
Definition: DefaultSettings.php:7040
UserGroupMembership
Represents a "user group membership" – a specific instance of a user belonging to a group.
Definition: UserGroupMembership.php:37
IP\isIPAddress
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
User\$mEditCount
int $mEditCount
Definition: User.php:141
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:133
if
if($IP===false)
Definition: initImageData.php:4
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4756
User\$mFrom
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:170
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3588
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3088
$type
$type
Definition: testCompression.php:48
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:691
User\trackBlockWithCookie
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition: User.php:1284