MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
40 
51 class User implements IDBAccessObject, UserIdentity {
52 
56  const TOKEN_LENGTH = 32;
57 
61  const INVALID_TOKEN = '*** INVALID ***';
62 
67  const VERSION = 13;
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 
117  protected static $mAllRights = false;
118 
120  // @{
122  public $mId;
124  public $mName;
126  protected $mActorId;
128  public $mRealName;
129 
131  public $mEmail;
133  public $mTouched;
135  protected $mQuickTouched;
137  protected $mToken;
141  protected $mEmailToken;
145  protected $mRegistration;
147  protected $mEditCount;
151  protected $mOptionOverrides;
152  // @}
153 
154  // @{
159 
163  protected $mLoadedItems = [];
164  // @}
165 
176  public $mFrom;
177 
182  protected $mNewtalk;
184  protected $mDatePreference;
186  public $mBlockedby;
188  protected $mHash;
190  protected $mBlockreason;
192  protected $mEffectiveGroups;
194  protected $mImplicitGroups;
196  protected $mFormerGroups;
198  protected $mGlobalBlock;
200  protected $mLocked;
202  public $mHideName;
204  public $mOptions;
205 
207  private $mRequest;
208 
210  public $mBlock;
211 
213  protected $mAllowUsertalk;
214 
216  private $mBlockedFromCreateAccount = false;
217 
219  protected $queryFlagsUsed = self::READ_NORMAL;
220 
222  public static $idCacheByName = [];
223 
235  public function __construct() {
236  $this->clearInstanceCache( 'defaults' );
237  }
238 
242  public function __toString() {
243  return (string)$this->getName();
244  }
245 
246  public function &__get( $name ) {
247  // A shortcut for $mRights deprecation phase
248  if ( $name === 'mRights' ) {
249  $copy = $this->getRights();
250  return $copy;
251  } elseif ( !property_exists( $this, $name ) ) {
252  // T227688 - do not break $u->foo['bar'] = 1
253  wfLogWarning( 'tried to get non-existent property' );
254  $this->$name = null;
255  return $this->$name;
256  } else {
257  wfLogWarning( 'tried to get non-visible property' );
258  return null;
259  }
260  }
261 
262  public function __set( $name, $value ) {
263  // A shortcut for $mRights deprecation phase, only known legitimate use was for
264  // testing purposes, other uses seem bad in principle
265  if ( $name === 'mRights' ) {
266  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
267  $this,
268  is_null( $value ) ? [] : $value
269  );
270  } elseif ( !property_exists( $this, $name ) ) {
271  $this->$name = $value;
272  } else {
273  wfLogWarning( 'tried to set non-visible property' );
274  }
275  }
276 
291  public function isSafeToLoad() {
292  global $wgFullyInitialised;
293 
294  // The user is safe to load if:
295  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
296  // * mLoadedItems === true (already loaded)
297  // * mFrom !== 'session' (sessions not involved at all)
298 
299  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
300  $this->mLoadedItems === true || $this->mFrom !== 'session';
301  }
302 
308  public function load( $flags = self::READ_NORMAL ) {
309  global $wgFullyInitialised;
310 
311  if ( $this->mLoadedItems === true ) {
312  return;
313  }
314 
315  // Set it now to avoid infinite recursion in accessors
316  $oldLoadedItems = $this->mLoadedItems;
317  $this->mLoadedItems = true;
318  $this->queryFlagsUsed = $flags;
319 
320  // If this is called too early, things are likely to break.
321  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
323  ->warning( 'User::loadFromSession called before the end of Setup.php', [
324  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
325  ] );
326  $this->loadDefaults();
327  $this->mLoadedItems = $oldLoadedItems;
328  return;
329  }
330 
331  switch ( $this->mFrom ) {
332  case 'defaults':
333  $this->loadDefaults();
334  break;
335  case 'name':
336  // Make sure this thread sees its own changes
337  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
338  if ( $lb->hasOrMadeRecentMasterChanges() ) {
339  $flags |= self::READ_LATEST;
340  $this->queryFlagsUsed = $flags;
341  }
342 
343  $this->mId = self::idFromName( $this->mName, $flags );
344  if ( !$this->mId ) {
345  // Nonexistent user placeholder object
346  $this->loadDefaults( $this->mName );
347  } else {
348  $this->loadFromId( $flags );
349  }
350  break;
351  case 'id':
352  // Make sure this thread sees its own changes, if the ID isn't 0
353  if ( $this->mId != 0 ) {
354  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
355  if ( $lb->hasOrMadeRecentMasterChanges() ) {
356  $flags |= self::READ_LATEST;
357  $this->queryFlagsUsed = $flags;
358  }
359  }
360 
361  $this->loadFromId( $flags );
362  break;
363  case 'actor':
364  // Make sure this thread sees its own changes
365  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
366  if ( $lb->hasOrMadeRecentMasterChanges() ) {
367  $flags |= self::READ_LATEST;
368  $this->queryFlagsUsed = $flags;
369  }
370 
371  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
372  $row = wfGetDB( $index )->selectRow(
373  'actor',
374  [ 'actor_user', 'actor_name' ],
375  [ 'actor_id' => $this->mActorId ],
376  __METHOD__,
377  $options
378  );
379 
380  if ( !$row ) {
381  // Ugh.
382  $this->loadDefaults();
383  } elseif ( $row->actor_user ) {
384  $this->mId = $row->actor_user;
385  $this->loadFromId( $flags );
386  } else {
387  $this->loadDefaults( $row->actor_name );
388  }
389  break;
390  case 'session':
391  if ( !$this->loadFromSession() ) {
392  // Loading from session failed. Load defaults.
393  $this->loadDefaults();
394  }
395  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
396  break;
397  default:
398  throw new UnexpectedValueException(
399  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
400  }
401  }
402 
408  public function loadFromId( $flags = self::READ_NORMAL ) {
409  if ( $this->mId == 0 ) {
410  // Anonymous users are not in the database (don't need cache)
411  $this->loadDefaults();
412  return false;
413  }
414 
415  // Try cache (unless this needs data from the master DB).
416  // NOTE: if this thread called saveSettings(), the cache was cleared.
417  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
418  if ( $latest ) {
419  if ( !$this->loadFromDatabase( $flags ) ) {
420  // Can't load from ID
421  return false;
422  }
423  } else {
424  $this->loadFromCache();
425  }
426 
427  $this->mLoadedItems = true;
428  $this->queryFlagsUsed = $flags;
429 
430  return true;
431  }
432 
438  public static function purge( $dbDomain, $userId ) {
439  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
440  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
441  $cache->delete( $key );
442  }
443 
449  protected function getCacheKey( WANObjectCache $cache ) {
450  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
451 
452  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
453  }
454 
461  $id = $this->getId();
462 
463  return $id ? [ $this->getCacheKey( $cache ) ] : [];
464  }
465 
472  protected function loadFromCache() {
473  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
474  $data = $cache->getWithSetCallback(
475  $this->getCacheKey( $cache ),
476  $cache::TTL_HOUR,
477  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
478  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
479  wfDebug( "User: cache miss for user {$this->mId}\n" );
480 
481  $this->loadFromDatabase( self::READ_NORMAL );
482  $this->loadGroups();
483  $this->loadOptions();
484 
485  $data = [];
486  foreach ( self::$mCacheVars as $name ) {
487  $data[$name] = $this->$name;
488  }
489 
490  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
491 
492  // if a user group membership is about to expire, the cache needs to
493  // expire at that time (T163691)
494  foreach ( $this->mGroupMemberships as $ugm ) {
495  if ( $ugm->getExpiry() ) {
496  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
497  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
498  $ttl = $secondsUntilExpiry;
499  }
500  }
501  }
502 
503  return $data;
504  },
505  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
506  );
507 
508  // Restore from cache
509  foreach ( self::$mCacheVars as $name ) {
510  $this->$name = $data[$name];
511  }
512 
513  return true;
514  }
515 
517  // @{
518 
535  public static function newFromName( $name, $validate = 'valid' ) {
536  if ( $validate === true ) {
537  $validate = 'valid';
538  }
539  $name = self::getCanonicalName( $name, $validate );
540  if ( $name === false ) {
541  return false;
542  }
543 
544  // Create unloaded user object
545  $u = new User;
546  $u->mName = $name;
547  $u->mFrom = 'name';
548  $u->setItemLoaded( 'name' );
549 
550  return $u;
551  }
552 
559  public static function newFromId( $id ) {
560  $u = new User;
561  $u->mId = $id;
562  $u->mFrom = 'id';
563  $u->setItemLoaded( 'id' );
564  return $u;
565  }
566 
574  public static function newFromActorId( $id ) {
576 
577  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
578  // but it does little harm and might be needed for write callers loading a User.
579  if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) ) {
580  throw new BadMethodCallException(
581  'Cannot use ' . __METHOD__
582  . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
583  );
584  }
585 
586  $u = new User;
587  $u->mActorId = $id;
588  $u->mFrom = 'actor';
589  $u->setItemLoaded( 'actor' );
590  return $u;
591  }
592 
602  public static function newFromIdentity( UserIdentity $identity ) {
603  if ( $identity instanceof User ) {
604  return $identity;
605  }
606 
607  return self::newFromAnyId(
608  $identity->getId() === 0 ? null : $identity->getId(),
609  $identity->getName() === '' ? null : $identity->getName(),
610  $identity->getActorId() === 0 ? null : $identity->getActorId()
611  );
612  }
613 
627  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
629 
630  // Stop-gap solution for the problem described in T222212.
631  // Force the User ID and Actor ID to zero for users loaded from the database
632  // of another wiki, to prevent subtle data corruption and confusing failure modes.
633  if ( $dbDomain !== false ) {
634  $userId = 0;
635  $actorId = 0;
636  }
637 
638  $user = new User;
639  $user->mFrom = 'defaults';
640 
641  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
642  // but it does little harm and might be needed for write callers loading a User.
643  if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
644  $user->mActorId = (int)$actorId;
645  if ( $user->mActorId !== 0 ) {
646  $user->mFrom = 'actor';
647  }
648  $user->setItemLoaded( 'actor' );
649  }
650 
651  if ( $userName !== null && $userName !== '' ) {
652  $user->mName = $userName;
653  $user->mFrom = 'name';
654  $user->setItemLoaded( 'name' );
655  }
656 
657  if ( $userId !== null ) {
658  $user->mId = (int)$userId;
659  if ( $user->mId !== 0 ) {
660  $user->mFrom = 'id';
661  }
662  $user->setItemLoaded( 'id' );
663  }
664 
665  if ( $user->mFrom === 'defaults' ) {
666  throw new InvalidArgumentException(
667  'Cannot create a user with no name, no ID, and no actor ID'
668  );
669  }
670 
671  return $user;
672  }
673 
685  public static function newFromConfirmationCode( $code, $flags = 0 ) {
686  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
687  ? wfGetDB( DB_MASTER )
688  : wfGetDB( DB_REPLICA );
689 
690  $id = $db->selectField(
691  'user',
692  'user_id',
693  [
694  'user_email_token' => md5( $code ),
695  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
696  ]
697  );
698 
699  return $id ? self::newFromId( $id ) : null;
700  }
701 
709  public static function newFromSession( WebRequest $request = null ) {
710  $user = new User;
711  $user->mFrom = 'session';
712  $user->mRequest = $request;
713  return $user;
714  }
715 
731  public static function newFromRow( $row, $data = null ) {
732  $user = new User;
733  $user->loadFromRow( $row, $data );
734  return $user;
735  }
736 
772  public static function newSystemUser( $name, $options = [] ) {
773  $options += [
774  'validate' => 'valid',
775  'create' => true,
776  'steal' => false,
777  ];
778 
779  $name = self::getCanonicalName( $name, $options['validate'] );
780  if ( $name === false ) {
781  return null;
782  }
783 
784  $dbr = wfGetDB( DB_REPLICA );
785  $userQuery = self::getQueryInfo();
786  $row = $dbr->selectRow(
787  $userQuery['tables'],
788  $userQuery['fields'],
789  [ 'user_name' => $name ],
790  __METHOD__,
791  [],
792  $userQuery['joins']
793  );
794  if ( !$row ) {
795  // Try the master database...
796  $dbw = wfGetDB( DB_MASTER );
797  $row = $dbw->selectRow(
798  $userQuery['tables'],
799  $userQuery['fields'],
800  [ 'user_name' => $name ],
801  __METHOD__,
802  [],
803  $userQuery['joins']
804  );
805  }
806 
807  if ( !$row ) {
808  // No user. Create it?
809  return $options['create']
810  ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
811  : null;
812  }
813 
814  $user = self::newFromRow( $row );
815 
816  // A user is considered to exist as a non-system user if it can
817  // authenticate, or has an email set, or has a non-invalid token.
818  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
819  AuthManager::singleton()->userCanAuthenticate( $name )
820  ) {
821  // User exists. Steal it?
822  if ( !$options['steal'] ) {
823  return null;
824  }
825 
826  AuthManager::singleton()->revokeAccessForUser( $name );
827 
828  $user->invalidateEmail();
829  $user->mToken = self::INVALID_TOKEN;
830  $user->saveSettings();
831  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
832  }
833 
834  return $user;
835  }
836 
837  // @}
838 
844  public static function whoIs( $id ) {
845  return UserCache::singleton()->getProp( $id, 'name' );
846  }
847 
854  public static function whoIsReal( $id ) {
855  return UserCache::singleton()->getProp( $id, 'real_name' );
856  }
857 
864  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
865  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
866  $name = (string)$name;
868  if ( is_null( $nt ) ) {
869  // Illegal name
870  return null;
871  }
872 
873  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
874  return is_null( self::$idCacheByName[$name] ) ? null : (int)self::$idCacheByName[$name];
875  }
876 
877  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
878  $db = wfGetDB( $index );
879 
880  $s = $db->selectRow(
881  'user',
882  [ 'user_id' ],
883  [ 'user_name' => $nt->getText() ],
884  __METHOD__,
885  $options
886  );
887 
888  if ( $s === false ) {
889  $result = null;
890  } else {
891  $result = (int)$s->user_id;
892  }
893 
894  if ( count( self::$idCacheByName ) >= 1000 ) {
895  self::$idCacheByName = [];
896  }
897 
898  self::$idCacheByName[$name] = $result;
899 
900  return $result;
901  }
902 
906  public static function resetIdByNameCache() {
907  self::$idCacheByName = [];
908  }
909 
926  public static function isIP( $name ) {
927  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
928  || IP::isIPv6( $name );
929  }
930 
937  public function isIPRange() {
938  return IP::isValidRange( $this->mName );
939  }
940 
952  public static function isValidUserName( $name ) {
953  global $wgMaxNameChars;
954 
955  if ( $name == ''
956  || self::isIP( $name )
957  || strpos( $name, '/' ) !== false
958  || strlen( $name ) > $wgMaxNameChars
959  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
960  ) {
961  return false;
962  }
963 
964  // Ensure that the name can't be misresolved as a different title,
965  // such as with extra namespace keys at the start.
966  $parsed = Title::newFromText( $name );
967  if ( is_null( $parsed )
968  || $parsed->getNamespace()
969  || strcmp( $name, $parsed->getPrefixedText() ) ) {
970  return false;
971  }
972 
973  // Check an additional blacklist of troublemaker characters.
974  // Should these be merged into the title char list?
975  $unicodeBlacklist = '/[' .
976  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
977  '\x{00a0}' . # non-breaking space
978  '\x{2000}-\x{200f}' . # various whitespace
979  '\x{2028}-\x{202f}' . # breaks and control chars
980  '\x{3000}' . # ideographic space
981  '\x{e000}-\x{f8ff}' . # private use
982  ']/u';
983  if ( preg_match( $unicodeBlacklist, $name ) ) {
984  return false;
985  }
986 
987  return true;
988  }
989 
1001  public static function isUsableName( $name ) {
1002  global $wgReservedUsernames;
1003  // Must be a valid username, obviously ;)
1004  if ( !self::isValidUserName( $name ) ) {
1005  return false;
1006  }
1007 
1008  static $reservedUsernames = false;
1009  if ( !$reservedUsernames ) {
1010  $reservedUsernames = $wgReservedUsernames;
1011  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
1012  }
1013 
1014  // Certain names may be reserved for batch processes.
1015  foreach ( $reservedUsernames as $reserved ) {
1016  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1017  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1018  }
1019  if ( $reserved == $name ) {
1020  return false;
1021  }
1022  }
1023  return true;
1024  }
1025 
1036  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1037  if ( $groups === [] ) {
1038  return UserArrayFromResult::newFromIDs( [] );
1039  }
1040 
1041  $groups = array_unique( (array)$groups );
1042  $limit = min( 5000, $limit );
1043 
1044  $conds = [ 'ug_group' => $groups ];
1045  if ( $after !== null ) {
1046  $conds[] = 'ug_user > ' . (int)$after;
1047  }
1048 
1049  $dbr = wfGetDB( DB_REPLICA );
1050  $ids = $dbr->selectFieldValues(
1051  'user_groups',
1052  'ug_user',
1053  $conds,
1054  __METHOD__,
1055  [
1056  'DISTINCT' => true,
1057  'ORDER BY' => 'ug_user',
1058  'LIMIT' => $limit,
1059  ]
1060  ) ?: [];
1061  return UserArray::newFromIDs( $ids );
1062  }
1063 
1076  public static function isCreatableName( $name ) {
1078 
1079  // Ensure that the username isn't longer than 235 bytes, so that
1080  // (at least for the builtin skins) user javascript and css files
1081  // will work. (T25080)
1082  if ( strlen( $name ) > 235 ) {
1083  wfDebugLog( 'username', __METHOD__ .
1084  ": '$name' invalid due to length" );
1085  return false;
1086  }
1087 
1088  // Preg yells if you try to give it an empty string
1089  if ( $wgInvalidUsernameCharacters !== '' &&
1090  preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name )
1091  ) {
1092  wfDebugLog( 'username', __METHOD__ .
1093  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1094  return false;
1095  }
1096 
1097  return self::isUsableName( $name );
1098  }
1099 
1106  public function isValidPassword( $password ) {
1107  // simple boolean wrapper for checkPasswordValidity
1108  return $this->checkPasswordValidity( $password )->isGood();
1109  }
1110 
1132  public function checkPasswordValidity( $password ) {
1133  global $wgPasswordPolicy;
1134 
1135  $upp = new UserPasswordPolicy(
1136  $wgPasswordPolicy['policies'],
1137  $wgPasswordPolicy['checks']
1138  );
1139 
1140  $status = Status::newGood( [] );
1141  $result = false; // init $result to false for the internal checks
1142 
1143  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1144  $status->error( $result );
1145  return $status;
1146  }
1147 
1148  if ( $result === false ) {
1149  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1150  return $status;
1151  }
1152 
1153  if ( $result === true ) {
1154  return $status;
1155  }
1156 
1157  $status->error( $result );
1158  return $status; // the isValidPassword hook set a string $result and returned true
1159  }
1160 
1174  public static function getCanonicalName( $name, $validate = 'valid' ) {
1175  // Force usernames to capital
1176  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1177 
1178  # Reject names containing '#'; these will be cleaned up
1179  # with title normalisation, but then it's too late to
1180  # check elsewhere
1181  if ( strpos( $name, '#' ) !== false ) {
1182  return false;
1183  }
1184 
1185  // Clean up name according to title rules,
1186  // but only when validation is requested (T14654)
1187  $t = ( $validate !== false ) ?
1189  // Check for invalid titles
1190  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1191  return false;
1192  }
1193 
1194  $name = $t->getText();
1195 
1196  switch ( $validate ) {
1197  case false:
1198  break;
1199  case 'valid':
1200  if ( !self::isValidUserName( $name ) ) {
1201  $name = false;
1202  }
1203  break;
1204  case 'usable':
1205  if ( !self::isUsableName( $name ) ) {
1206  $name = false;
1207  }
1208  break;
1209  case 'creatable':
1210  if ( !self::isCreatableName( $name ) ) {
1211  $name = false;
1212  }
1213  break;
1214  default:
1215  throw new InvalidArgumentException(
1216  'Invalid parameter value for $validate in ' . __METHOD__ );
1217  }
1218  return $name;
1219  }
1220 
1229  public function loadDefaults( $name = false ) {
1230  $this->mId = 0;
1231  $this->mName = $name;
1232  $this->mActorId = null;
1233  $this->mRealName = '';
1234  $this->mEmail = '';
1235  $this->mOptionOverrides = null;
1236  $this->mOptionsLoaded = false;
1237 
1238  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1239  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1240  if ( $loggedOut !== 0 ) {
1241  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1242  } else {
1243  $this->mTouched = '1'; # Allow any pages to be cached
1244  }
1245 
1246  $this->mToken = null; // Don't run cryptographic functions till we need a token
1247  $this->mEmailAuthenticated = null;
1248  $this->mEmailToken = '';
1249  $this->mEmailTokenExpires = null;
1250  $this->mRegistration = wfTimestamp( TS_MW );
1251  $this->mGroupMemberships = [];
1252 
1253  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1254  }
1255 
1268  public function isItemLoaded( $item, $all = 'all' ) {
1269  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1270  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1271  }
1272 
1278  protected function setItemLoaded( $item ) {
1279  if ( is_array( $this->mLoadedItems ) ) {
1280  $this->mLoadedItems[$item] = true;
1281  }
1282  }
1283 
1289  private function loadFromSession() {
1290  // MediaWiki\Session\Session already did the necessary authentication of the user
1291  // returned here, so just use it if applicable.
1292  $session = $this->getRequest()->getSession();
1293  $user = $session->getUser();
1294  if ( $user->isLoggedIn() ) {
1295  $this->loadFromUserObject( $user );
1296 
1297  // If this user is autoblocked, set a cookie to track the block. This has to be done on
1298  // every session load, because an autoblocked editor might not edit again from the same
1299  // IP address after being blocked.
1300  MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1301 
1302  // Other code expects these to be set in the session, so set them.
1303  $session->set( 'wsUserID', $this->getId() );
1304  $session->set( 'wsUserName', $this->getName() );
1305  $session->set( 'wsToken', $this->getToken() );
1306 
1307  return true;
1308  }
1309 
1310  return false;
1311  }
1312 
1318  public function trackBlockWithCookie() {
1319  MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
1320  }
1321 
1329  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1330  // Paranoia
1331  $this->mId = intval( $this->mId );
1332 
1333  if ( !$this->mId ) {
1334  // Anonymous users are not in the database
1335  $this->loadDefaults();
1336  return false;
1337  }
1338 
1339  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1340  $db = wfGetDB( $index );
1341 
1342  $userQuery = self::getQueryInfo();
1343  $s = $db->selectRow(
1344  $userQuery['tables'],
1345  $userQuery['fields'],
1346  [ 'user_id' => $this->mId ],
1347  __METHOD__,
1348  $options,
1349  $userQuery['joins']
1350  );
1351 
1352  $this->queryFlagsUsed = $flags;
1353  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1354 
1355  if ( $s !== false ) {
1356  // Initialise user table data
1357  $this->loadFromRow( $s );
1358  $this->mGroupMemberships = null; // deferred
1359  $this->getEditCount(); // revalidation for nulls
1360  return true;
1361  }
1362 
1363  // Invalid user_id
1364  $this->mId = 0;
1365  $this->loadDefaults();
1366 
1367  return false;
1368  }
1369 
1382  protected function loadFromRow( $row, $data = null ) {
1384 
1385  if ( !is_object( $row ) ) {
1386  throw new InvalidArgumentException( '$row must be an object' );
1387  }
1388 
1389  $all = true;
1390 
1391  $this->mGroupMemberships = null; // deferred
1392 
1393  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
1394  // but it does little harm and might be needed for write callers loading a User.
1395  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
1396  if ( isset( $row->actor_id ) ) {
1397  $this->mActorId = (int)$row->actor_id;
1398  if ( $this->mActorId !== 0 ) {
1399  $this->mFrom = 'actor';
1400  }
1401  $this->setItemLoaded( 'actor' );
1402  } else {
1403  $all = false;
1404  }
1405  }
1406 
1407  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1408  $this->mName = $row->user_name;
1409  $this->mFrom = 'name';
1410  $this->setItemLoaded( 'name' );
1411  } else {
1412  $all = false;
1413  }
1414 
1415  if ( isset( $row->user_real_name ) ) {
1416  $this->mRealName = $row->user_real_name;
1417  $this->setItemLoaded( 'realname' );
1418  } else {
1419  $all = false;
1420  }
1421 
1422  if ( isset( $row->user_id ) ) {
1423  $this->mId = intval( $row->user_id );
1424  if ( $this->mId !== 0 ) {
1425  $this->mFrom = 'id';
1426  }
1427  $this->setItemLoaded( 'id' );
1428  } else {
1429  $all = false;
1430  }
1431 
1432  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1433  self::$idCacheByName[$row->user_name] = $row->user_id;
1434  }
1435 
1436  if ( isset( $row->user_editcount ) ) {
1437  $this->mEditCount = $row->user_editcount;
1438  } else {
1439  $all = false;
1440  }
1441 
1442  if ( isset( $row->user_touched ) ) {
1443  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1444  } else {
1445  $all = false;
1446  }
1447 
1448  if ( isset( $row->user_token ) ) {
1449  // The definition for the column is binary(32), so trim the NULs
1450  // that appends. The previous definition was char(32), so trim
1451  // spaces too.
1452  $this->mToken = rtrim( $row->user_token, " \0" );
1453  if ( $this->mToken === '' ) {
1454  $this->mToken = null;
1455  }
1456  } else {
1457  $all = false;
1458  }
1459 
1460  if ( isset( $row->user_email ) ) {
1461  $this->mEmail = $row->user_email;
1462  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1463  $this->mEmailToken = $row->user_email_token;
1464  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1465  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1466  } else {
1467  $all = false;
1468  }
1469 
1470  if ( $all ) {
1471  $this->mLoadedItems = true;
1472  }
1473 
1474  if ( is_array( $data ) ) {
1475  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1476  if ( $data['user_groups'] === [] ) {
1477  $this->mGroupMemberships = [];
1478  } else {
1479  $firstGroup = reset( $data['user_groups'] );
1480  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1481  $this->mGroupMemberships = [];
1482  foreach ( $data['user_groups'] as $row ) {
1483  $ugm = UserGroupMembership::newFromRow( (object)$row );
1484  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1485  }
1486  }
1487  }
1488  }
1489  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1490  $this->loadOptions( $data['user_properties'] );
1491  }
1492  }
1493  }
1494 
1500  protected function loadFromUserObject( $user ) {
1501  $user->load();
1502  foreach ( self::$mCacheVars as $var ) {
1503  $this->$var = $user->$var;
1504  }
1505  }
1506 
1510  private function loadGroups() {
1511  if ( is_null( $this->mGroupMemberships ) ) {
1512  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1513  ? wfGetDB( DB_MASTER )
1514  : wfGetDB( DB_REPLICA );
1515  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1516  $this->mId, $db );
1517  }
1518  }
1519 
1534  public function addAutopromoteOnceGroups( $event ) {
1536 
1537  if ( wfReadOnly() || !$this->getId() ) {
1538  return [];
1539  }
1540 
1541  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1542  if ( $toPromote === [] ) {
1543  return [];
1544  }
1545 
1546  if ( !$this->checkAndSetTouched() ) {
1547  return []; // raced out (bug T48834)
1548  }
1549 
1550  $oldGroups = $this->getGroups(); // previous groups
1551  $oldUGMs = $this->getGroupMemberships();
1552  foreach ( $toPromote as $group ) {
1553  $this->addGroup( $group );
1554  }
1555  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1556  $newUGMs = $this->getGroupMemberships();
1557 
1558  // update groups in external authentication database
1559  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1560 
1561  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1562  $logEntry->setPerformer( $this );
1563  $logEntry->setTarget( $this->getUserPage() );
1564  $logEntry->setParameters( [
1565  '4::oldgroups' => $oldGroups,
1566  '5::newgroups' => $newGroups,
1567  ] );
1568  $logid = $logEntry->insert();
1569  if ( $wgAutopromoteOnceLogInRC ) {
1570  $logEntry->publish( $logid );
1571  }
1572 
1573  return $toPromote;
1574  }
1575 
1585  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1586  if ( $this->mTouched ) {
1587  // CAS check: only update if the row wasn't changed sicne it was loaded.
1588  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1589  }
1590 
1591  return $conditions;
1592  }
1593 
1603  protected function checkAndSetTouched() {
1604  $this->load();
1605 
1606  if ( !$this->mId ) {
1607  return false; // anon
1608  }
1609 
1610  // Get a new user_touched that is higher than the old one
1611  $newTouched = $this->newTouchedTimestamp();
1612 
1613  $dbw = wfGetDB( DB_MASTER );
1614  $dbw->update( 'user',
1615  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1616  $this->makeUpdateConditions( $dbw, [
1617  'user_id' => $this->mId,
1618  ] ),
1619  __METHOD__
1620  );
1621  $success = ( $dbw->affectedRows() > 0 );
1622 
1623  if ( $success ) {
1624  $this->mTouched = $newTouched;
1625  $this->clearSharedCache( 'changed' );
1626  } else {
1627  // Clears on failure too since that is desired if the cache is stale
1628  $this->clearSharedCache( 'refresh' );
1629  }
1630 
1631  return $success;
1632  }
1633 
1641  public function clearInstanceCache( $reloadFrom = false ) {
1642  global $wgFullyInitialised;
1643 
1644  $this->mNewtalk = -1;
1645  $this->mDatePreference = null;
1646  $this->mBlockedby = -1; # Unset
1647  $this->mHash = false;
1648  $this->mEffectiveGroups = null;
1649  $this->mImplicitGroups = null;
1650  $this->mGroupMemberships = null;
1651  $this->mOptions = null;
1652  $this->mOptionsLoaded = false;
1653  $this->mEditCount = null;
1654 
1655  // Replacement of former `$this->mRights = null` line
1656  if ( $wgFullyInitialised && $this->mFrom ) {
1657  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
1658  $this
1659  );
1660  }
1661 
1662  if ( $reloadFrom ) {
1663  $this->mLoadedItems = [];
1664  $this->mFrom = $reloadFrom;
1665  }
1666  }
1667 
1669  private static $defOpt = null;
1671  private static $defOptLang = null;
1672 
1679  public static function resetGetDefaultOptionsForTestsOnly() {
1680  Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
1681  self::$defOpt = null;
1682  self::$defOptLang = null;
1683  }
1684 
1691  public static function getDefaultOptions() {
1693 
1694  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1695  if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
1696  // The content language does not change (and should not change) mid-request, but the
1697  // unit tests change it anyway, and expect this method to return values relevant to the
1698  // current content language.
1699  return self::$defOpt;
1700  }
1701 
1702  self::$defOpt = $wgDefaultUserOptions;
1703  // Default language setting
1704  self::$defOptLang = $contLang->getCode();
1705  self::$defOpt['language'] = self::$defOptLang;
1706  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1707  if ( $langCode === $contLang->getCode() ) {
1708  self::$defOpt['variant'] = $langCode;
1709  } else {
1710  self::$defOpt["variant-$langCode"] = $langCode;
1711  }
1712  }
1713 
1714  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1715  // since extensions may change the set of searchable namespaces depending
1716  // on user groups/permissions.
1717  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1718  self::$defOpt['searchNs' . $nsnum] = (bool)$val;
1719  }
1720  self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1721 
1722  Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
1723 
1724  return self::$defOpt;
1725  }
1726 
1733  public static function getDefaultOption( $opt ) {
1734  $defOpts = self::getDefaultOptions();
1735  return $defOpts[$opt] ?? null;
1736  }
1737 
1747  private function getBlockedStatus( $fromReplica = true ) {
1748  if ( $this->mBlockedby != -1 ) {
1749  return;
1750  }
1751 
1752  wfDebug( __METHOD__ . ": checking...\n" );
1753 
1754  // Initialize data...
1755  // Otherwise something ends up stomping on $this->mBlockedby when
1756  // things get lazy-loaded later, causing false positive block hits
1757  // due to -1 !== 0. Probably session-related... Nothing should be
1758  // overwriting mBlockedby, surely?
1759  $this->load();
1760 
1761  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1762  $this,
1763  $fromReplica
1764  );
1765 
1766  if ( $block ) {
1767  $this->mBlock = $block;
1768  $this->mBlockedby = $block->getByName();
1769  $this->mBlockreason = $block->getReason();
1770  $this->mHideName = $block->getHideName();
1771  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1772  } else {
1773  $this->mBlock = null;
1774  $this->mBlockedby = '';
1775  $this->mBlockreason = '';
1776  $this->mHideName = 0;
1777  $this->mAllowUsertalk = false;
1778  }
1779 
1780  // Avoid PHP 7.1 warning of passing $this by reference
1781  $thisUser = $this;
1782  // Extensions
1783  Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
1784  }
1785 
1794  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1795  return MediaWikiServices::getInstance()->getBlockManager()
1796  ->isDnsBlacklisted( $ip, $checkWhitelist );
1797  }
1798 
1807  public function inDnsBlacklist( $ip, $bases ) {
1808  wfDeprecated( __METHOD__, '1.34' );
1809 
1810  $found = false;
1811  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1812  if ( IP::isIPv4( $ip ) ) {
1813  // Reverse IP, T23255
1814  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1815 
1816  foreach ( (array)$bases as $base ) {
1817  // Make hostname
1818  // If we have an access key, use that too (ProjectHoneypot, etc.)
1819  $basename = $base;
1820  if ( is_array( $base ) ) {
1821  if ( count( $base ) >= 2 ) {
1822  // Access key is 1, base URL is 0
1823  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1824  } else {
1825  $host = "$ipReversed.{$base[0]}";
1826  }
1827  $basename = $base[0];
1828  } else {
1829  $host = "$ipReversed.$base";
1830  }
1831 
1832  // Send query
1833  $ipList = gethostbynamel( $host );
1834 
1835  if ( $ipList ) {
1836  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1837  $found = true;
1838  break;
1839  }
1840 
1841  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1842  }
1843  }
1844 
1845  return $found;
1846  }
1847 
1855  public static function isLocallyBlockedProxy( $ip ) {
1856  wfDeprecated( __METHOD__, '1.34' );
1857 
1858  global $wgProxyList;
1859 
1860  if ( !$wgProxyList ) {
1861  return false;
1862  }
1863 
1864  if ( !is_array( $wgProxyList ) ) {
1865  // Load values from the specified file
1866  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1867  }
1868 
1869  $resultProxyList = [];
1870  $deprecatedIPEntries = [];
1871 
1872  // backward compatibility: move all ip addresses in keys to values
1873  foreach ( $wgProxyList as $key => $value ) {
1874  $keyIsIP = IP::isIPAddress( $key );
1875  $valueIsIP = IP::isIPAddress( $value );
1876  if ( $keyIsIP && !$valueIsIP ) {
1877  $deprecatedIPEntries[] = $key;
1878  $resultProxyList[] = $key;
1879  } elseif ( $keyIsIP && $valueIsIP ) {
1880  $deprecatedIPEntries[] = $key;
1881  $resultProxyList[] = $key;
1882  $resultProxyList[] = $value;
1883  } else {
1884  $resultProxyList[] = $value;
1885  }
1886  }
1887 
1888  if ( $deprecatedIPEntries ) {
1889  wfDeprecated(
1890  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1891  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
1892  }
1893 
1894  $proxyListIPSet = new IPSet( $resultProxyList );
1895  return $proxyListIPSet->match( $ip );
1896  }
1897 
1903  public function isPingLimitable() {
1904  global $wgRateLimitsExcludedIPs;
1905  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1906  // No other good way currently to disable rate limits
1907  // for specific IPs. :P
1908  // But this is a crappy hack and should die.
1909  return false;
1910  }
1911  return !$this->isAllowed( 'noratelimit' );
1912  }
1913 
1928  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1929  // Avoid PHP 7.1 warning of passing $this by reference
1930  $user = $this;
1931  // Call the 'PingLimiter' hook
1932  $result = false;
1933  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1934  return $result;
1935  }
1936 
1937  global $wgRateLimits;
1938  if ( !isset( $wgRateLimits[$action] ) ) {
1939  return false;
1940  }
1941 
1942  $limits = array_merge(
1943  [ '&can-bypass' => true ],
1944  $wgRateLimits[$action]
1945  );
1946 
1947  // Some groups shouldn't trigger the ping limiter, ever
1948  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1949  return false;
1950  }
1951 
1952  $keys = [];
1953  $id = $this->getId();
1954  $userLimit = false;
1955  $isNewbie = $this->isNewbie();
1957 
1958  if ( $id == 0 ) {
1959  // limits for anons
1960  if ( isset( $limits['anon'] ) ) {
1961  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1962  }
1963  } elseif ( isset( $limits['user'] ) ) {
1964  // limits for logged-in users
1965  $userLimit = $limits['user'];
1966  }
1967 
1968  // limits for anons and for newbie logged-in users
1969  if ( $isNewbie ) {
1970  // ip-based limits
1971  if ( isset( $limits['ip'] ) ) {
1972  $ip = $this->getRequest()->getIP();
1973  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1974  }
1975  // subnet-based limits
1976  if ( isset( $limits['subnet'] ) ) {
1977  $ip = $this->getRequest()->getIP();
1978  $subnet = IP::getSubnet( $ip );
1979  if ( $subnet !== false ) {
1980  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1981  }
1982  }
1983  }
1984 
1985  // Check for group-specific permissions
1986  // If more than one group applies, use the group with the highest limit ratio (max/period)
1987  foreach ( $this->getGroups() as $group ) {
1988  if ( isset( $limits[$group] ) ) {
1989  if ( $userLimit === false
1990  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1991  ) {
1992  $userLimit = $limits[$group];
1993  }
1994  }
1995  }
1996 
1997  // limits for newbie logged-in users (override all the normal user limits)
1998  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1999  $userLimit = $limits['newbie'];
2000  }
2001 
2002  // Set the user limit key
2003  if ( $userLimit !== false ) {
2004  // phan is confused because &can-bypass's value is a bool, so it assumes
2005  // that $userLimit is also a bool here.
2006  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2007  list( $max, $period ) = $userLimit;
2008  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2009  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2010  }
2011 
2012  // ip-based limits for all ping-limitable users
2013  if ( isset( $limits['ip-all'] ) ) {
2014  $ip = $this->getRequest()->getIP();
2015  // ignore if user limit is more permissive
2016  if ( $isNewbie || $userLimit === false
2017  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2018  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2019  }
2020  }
2021 
2022  // subnet-based limits for all ping-limitable users
2023  if ( isset( $limits['subnet-all'] ) ) {
2024  $ip = $this->getRequest()->getIP();
2025  $subnet = IP::getSubnet( $ip );
2026  if ( $subnet !== false ) {
2027  // ignore if user limit is more permissive
2028  if ( $isNewbie || $userLimit === false
2029  || $limits['ip-all'][0] / $limits['ip-all'][1]
2030  > $userLimit[0] / $userLimit[1] ) {
2031  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2032  }
2033  }
2034  }
2035 
2036  $triggered = false;
2037  foreach ( $keys as $key => $limit ) {
2038  // phan is confused because &can-bypass's value is a bool, so it assumes
2039  // that $userLimit is also a bool here.
2040  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2041  list( $max, $period ) = $limit;
2042  $summary = "(limit $max in {$period}s)";
2043  $count = $cache->get( $key );
2044  // Already pinged?
2045  if ( $count ) {
2046  if ( $count >= $max ) {
2047  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2048  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2049  $triggered = true;
2050  } else {
2051  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2052  }
2053  } else {
2054  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2055  if ( $incrBy > 0 ) {
2056  $cache->add( $key, 0, intval( $period ) ); // first ping
2057  }
2058  }
2059  if ( $incrBy > 0 ) {
2060  $cache->incr( $key, $incrBy );
2061  }
2062  }
2063 
2064  return $triggered;
2065  }
2066 
2078  public function isBlocked( $fromReplica = true ) {
2079  return $this->getBlock( $fromReplica ) instanceof AbstractBlock &&
2080  $this->getBlock()->appliesToRight( 'edit' );
2081  }
2082 
2089  public function getBlock( $fromReplica = true ) {
2090  $this->getBlockedStatus( $fromReplica );
2091  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
2092  }
2093 
2105  public function isBlockedFrom( $title, $fromReplica = false ) {
2106  return MediaWikiServices::getInstance()->getPermissionManager()
2107  ->isBlockedFrom( $this, $title, $fromReplica );
2108  }
2109 
2114  public function blockedBy() {
2115  $this->getBlockedStatus();
2116  return $this->mBlockedby;
2117  }
2118 
2123  public function blockedFor() {
2124  $this->getBlockedStatus();
2125  return $this->mBlockreason;
2126  }
2127 
2132  public function getBlockId() {
2133  $this->getBlockedStatus();
2134  return ( $this->mBlock ? $this->mBlock->getId() : false );
2135  }
2136 
2145  public function isBlockedGlobally( $ip = '' ) {
2146  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
2147  }
2148 
2159  public function getGlobalBlock( $ip = '' ) {
2160  if ( $this->mGlobalBlock !== null ) {
2161  return $this->mGlobalBlock ?: null;
2162  }
2163  // User is already an IP?
2164  if ( IP::isIPAddress( $this->getName() ) ) {
2165  $ip = $this->getName();
2166  } elseif ( !$ip ) {
2167  $ip = $this->getRequest()->getIP();
2168  }
2169  // Avoid PHP 7.1 warning of passing $this by reference
2170  $user = $this;
2171  $blocked = false;
2172  $block = null;
2173  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2174 
2175  if ( $blocked && $block === null ) {
2176  // back-compat: UserIsBlockedGlobally didn't have $block param first
2177  $block = new SystemBlock( [
2178  'address' => $ip,
2179  'systemBlock' => 'global-block'
2180  ] );
2181  }
2182 
2183  $this->mGlobalBlock = $blocked ? $block : false;
2184  return $this->mGlobalBlock ?: null;
2185  }
2186 
2192  public function isLocked() {
2193  if ( $this->mLocked !== null ) {
2194  return $this->mLocked;
2195  }
2196  // Reset for hook
2197  $this->mLocked = false;
2198  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2199  return $this->mLocked;
2200  }
2201 
2207  public function isHidden() {
2208  if ( $this->mHideName !== null ) {
2209  return (bool)$this->mHideName;
2210  }
2211  $this->getBlockedStatus();
2212  if ( !$this->mHideName ) {
2213  // Reset for hook
2214  $this->mHideName = false;
2215  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2216  }
2217  return (bool)$this->mHideName;
2218  }
2219 
2224  public function getId() {
2225  if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2226  // Special case, we know the user is anonymous
2227  return 0;
2228  }
2229 
2230  if ( !$this->isItemLoaded( 'id' ) ) {
2231  // Don't load if this was initialized from an ID
2232  $this->load();
2233  }
2234 
2235  return (int)$this->mId;
2236  }
2237 
2242  public function setId( $v ) {
2243  $this->mId = $v;
2244  $this->clearInstanceCache( 'id' );
2245  }
2246 
2251  public function getName() {
2252  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2253  // Special case optimisation
2254  return $this->mName;
2255  }
2256 
2257  $this->load();
2258  if ( $this->mName === false ) {
2259  // Clean up IPs
2260  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2261  }
2262 
2263  return $this->mName;
2264  }
2265 
2279  public function setName( $str ) {
2280  $this->load();
2281  $this->mName = $str;
2282  }
2283 
2290  public function getActorId( IDatabase $dbw = null ) {
2292 
2293  // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
2294  // but it does little harm and might be needed for write callers loading a User.
2295  if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
2296  return 0;
2297  }
2298 
2299  if ( !$this->isItemLoaded( 'actor' ) ) {
2300  $this->load();
2301  }
2302 
2303  // Currently $this->mActorId might be null if $this was loaded from a
2304  // cache entry that was written when $wgActorTableSchemaMigrationStage
2305  // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
2306  // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
2307  // has been removed), that condition may be removed.
2308  if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2309  $q = [
2310  'actor_user' => $this->getId() ?: null,
2311  'actor_name' => (string)$this->getName(),
2312  ];
2313  if ( $dbw ) {
2314  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2315  throw new CannotCreateActorException(
2316  'Cannot create an actor for a usable name that is not an existing user'
2317  );
2318  }
2319  if ( $q['actor_name'] === '' ) {
2320  throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2321  }
2322  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2323  if ( $dbw->affectedRows() ) {
2324  $this->mActorId = (int)$dbw->insertId();
2325  } else {
2326  // Outdated cache?
2327  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2328  $this->mActorId = (int)$dbw->selectField(
2329  'actor',
2330  'actor_id',
2331  $q,
2332  __METHOD__,
2333  [ 'LOCK IN SHARE MODE' ]
2334  );
2335  if ( !$this->mActorId ) {
2336  throw new CannotCreateActorException(
2337  "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2338  );
2339  }
2340  }
2341  $this->invalidateCache();
2342  } else {
2343  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2344  $db = wfGetDB( $index );
2345  $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2346  }
2347  $this->setItemLoaded( 'actor' );
2348  }
2349 
2350  return (int)$this->mActorId;
2351  }
2352 
2357  public function getTitleKey() {
2358  return str_replace( ' ', '_', $this->getName() );
2359  }
2360 
2365  public function getNewtalk() {
2366  $this->load();
2367 
2368  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2369  if ( $this->mNewtalk === -1 ) {
2370  $this->mNewtalk = false; # reset talk page status
2371 
2372  // Check memcached separately for anons, who have no
2373  // entire User object stored in there.
2374  if ( !$this->mId ) {
2375  global $wgDisableAnonTalk;
2376  if ( $wgDisableAnonTalk ) {
2377  // Anon newtalk disabled by configuration.
2378  $this->mNewtalk = false;
2379  } else {
2380  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2381  }
2382  } else {
2383  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2384  }
2385  }
2386 
2387  return (bool)$this->mNewtalk;
2388  }
2389 
2403  public function getNewMessageLinks() {
2404  // Avoid PHP 7.1 warning of passing $this by reference
2405  $user = $this;
2406  $talks = [];
2407  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2408  return $talks;
2409  }
2410 
2411  if ( !$this->getNewtalk() ) {
2412  return [];
2413  }
2414  $utp = $this->getTalkPage();
2415  $dbr = wfGetDB( DB_REPLICA );
2416  // Get the "last viewed rev" timestamp from the oldest message notification
2417  $timestamp = $dbr->selectField( 'user_newtalk',
2418  'MIN(user_last_timestamp)',
2419  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2420  __METHOD__ );
2421  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2422  return [
2423  [
2425  'link' => $utp->getLocalURL(),
2426  'rev' => $rev
2427  ]
2428  ];
2429  }
2430 
2436  public function getNewMessageRevisionId() {
2437  $newMessageRevisionId = null;
2438  $newMessageLinks = $this->getNewMessageLinks();
2439 
2440  // Note: getNewMessageLinks() never returns more than a single link
2441  // and it is always for the same wiki, but we double-check here in
2442  // case that changes some time in the future.
2443  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2444  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2445  && $newMessageLinks[0]['rev']
2446  ) {
2448  $newMessageRevision = $newMessageLinks[0]['rev'];
2449  $newMessageRevisionId = $newMessageRevision->getId();
2450  }
2451 
2452  return $newMessageRevisionId;
2453  }
2454 
2463  protected function checkNewtalk( $field, $id ) {
2464  $dbr = wfGetDB( DB_REPLICA );
2465 
2466  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2467 
2468  return $ok !== false;
2469  }
2470 
2478  protected function updateNewtalk( $field, $id, $curRev = null ) {
2479  // Get timestamp of the talk page revision prior to the current one
2480  $prevRev = $curRev ? $curRev->getPrevious() : false;
2481  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2482  // Mark the user as having new messages since this revision
2483  $dbw = wfGetDB( DB_MASTER );
2484  $dbw->insert( 'user_newtalk',
2485  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2486  __METHOD__,
2487  [ 'IGNORE' ] );
2488  if ( $dbw->affectedRows() ) {
2489  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2490  return true;
2491  }
2492 
2493  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2494  return false;
2495  }
2496 
2503  protected function deleteNewtalk( $field, $id ) {
2504  $dbw = wfGetDB( DB_MASTER );
2505  $dbw->delete( 'user_newtalk',
2506  [ $field => $id ],
2507  __METHOD__ );
2508  if ( $dbw->affectedRows() ) {
2509  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2510  return true;
2511  }
2512 
2513  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2514  return false;
2515  }
2516 
2523  public function setNewtalk( $val, $curRev = null ) {
2524  if ( wfReadOnly() ) {
2525  return;
2526  }
2527 
2528  $this->load();
2529  $this->mNewtalk = $val;
2530 
2531  if ( $this->isAnon() ) {
2532  $field = 'user_ip';
2533  $id = $this->getName();
2534  } else {
2535  $field = 'user_id';
2536  $id = $this->getId();
2537  }
2538 
2539  if ( $val ) {
2540  $changed = $this->updateNewtalk( $field, $id, $curRev );
2541  } else {
2542  $changed = $this->deleteNewtalk( $field, $id );
2543  }
2544 
2545  if ( $changed ) {
2546  $this->invalidateCache();
2547  }
2548  }
2549 
2556  private function newTouchedTimestamp() {
2557  $time = time();
2558  if ( $this->mTouched ) {
2559  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2560  }
2561 
2562  return wfTimestamp( TS_MW, $time );
2563  }
2564 
2575  public function clearSharedCache( $mode = 'refresh' ) {
2576  if ( !$this->getId() ) {
2577  return;
2578  }
2579 
2580  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2581  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2582  $key = $this->getCacheKey( $cache );
2583 
2584  if ( $mode === 'refresh' ) {
2585  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2586  } else {
2587  $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2588  function () use ( $cache, $key ) {
2589  $cache->delete( $key );
2590  },
2591  __METHOD__
2592  );
2593  }
2594  }
2595 
2601  public function invalidateCache() {
2602  $this->touch();
2603  $this->clearSharedCache( 'changed' );
2604  }
2605 
2618  public function touch() {
2619  $id = $this->getId();
2620  if ( $id ) {
2621  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2622  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2623  $cache->touchCheckKey( $key );
2624  $this->mQuickTouched = null;
2625  }
2626  }
2627 
2633  public function validateCache( $timestamp ) {
2634  return ( $timestamp >= $this->getTouched() );
2635  }
2636 
2645  public function getTouched() {
2646  $this->load();
2647 
2648  if ( $this->mId ) {
2649  if ( $this->mQuickTouched === null ) {
2650  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2651  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2652 
2653  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2654  }
2655 
2656  return max( $this->mTouched, $this->mQuickTouched );
2657  }
2658 
2659  return $this->mTouched;
2660  }
2661 
2667  public function getDBTouched() {
2668  $this->load();
2669 
2670  return $this->mTouched;
2671  }
2672 
2689  public function setPassword( $str ) {
2690  wfDeprecated( __METHOD__, '1.27' );
2691  return $this->setPasswordInternal( $str );
2692  }
2693 
2702  public function setInternalPassword( $str ) {
2703  wfDeprecated( __METHOD__, '1.27' );
2704  $this->setPasswordInternal( $str );
2705  }
2706 
2715  private function setPasswordInternal( $str ) {
2716  $manager = AuthManager::singleton();
2717 
2718  // If the user doesn't exist yet, fail
2719  if ( !$manager->userExists( $this->getName() ) ) {
2720  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2721  }
2722 
2723  $status = $this->changeAuthenticationData( [
2724  'username' => $this->getName(),
2725  'password' => $str,
2726  'retype' => $str,
2727  ] );
2728  if ( !$status->isGood() ) {
2730  ->info( __METHOD__ . ': Password change rejected: '
2731  . $status->getWikiText( null, null, 'en' ) );
2732  return false;
2733  }
2734 
2735  $this->setOption( 'watchlisttoken', false );
2736  SessionManager::singleton()->invalidateSessionsForUser( $this );
2737 
2738  return true;
2739  }
2740 
2753  public function changeAuthenticationData( array $data ) {
2754  $manager = AuthManager::singleton();
2755  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2756  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2757 
2758  $status = Status::newGood( 'ignored' );
2759  foreach ( $reqs as $req ) {
2760  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2761  }
2762  if ( $status->getValue() === 'ignored' ) {
2763  $status->warning( 'authenticationdatachange-ignored' );
2764  }
2765 
2766  if ( $status->isGood() ) {
2767  foreach ( $reqs as $req ) {
2768  $manager->changeAuthenticationData( $req );
2769  }
2770  }
2771  return $status;
2772  }
2773 
2780  public function getToken( $forceCreation = true ) {
2782 
2783  $this->load();
2784  if ( !$this->mToken && $forceCreation ) {
2785  $this->setToken();
2786  }
2787 
2788  if ( !$this->mToken ) {
2789  // The user doesn't have a token, return null to indicate that.
2790  return null;
2791  }
2792 
2793  if ( $this->mToken === self::INVALID_TOKEN ) {
2794  // We return a random value here so existing token checks are very
2795  // likely to fail.
2796  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2797  }
2798 
2799  if ( $wgAuthenticationTokenVersion === null ) {
2800  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2801  return $this->mToken;
2802  }
2803 
2804  // $wgAuthenticationTokenVersion in use, so hmac it.
2805  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2806 
2807  // The raw hash can be overly long. Shorten it up.
2808  $len = max( 32, self::TOKEN_LENGTH );
2809  if ( strlen( $ret ) < $len ) {
2810  // Should never happen, even md5 is 128 bits
2811  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2812  }
2813 
2814  return substr( $ret, -$len );
2815  }
2816 
2823  public function setToken( $token = false ) {
2824  $this->load();
2825  if ( $this->mToken === self::INVALID_TOKEN ) {
2827  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2828  } elseif ( !$token ) {
2829  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2830  } else {
2831  $this->mToken = $token;
2832  }
2833  }
2834 
2843  public function setNewpassword( $str, $throttle = true ) {
2844  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2845  }
2846 
2851  public function getEmail() {
2852  $this->load();
2853  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2854  return $this->mEmail;
2855  }
2856 
2862  $this->load();
2863  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2865  }
2866 
2871  public function setEmail( $str ) {
2872  $this->load();
2873  if ( $str == $this->mEmail ) {
2874  return;
2875  }
2876  $this->invalidateEmail();
2877  $this->mEmail = $str;
2878  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2879  }
2880 
2888  public function setEmailWithConfirmation( $str ) {
2890 
2891  if ( !$wgEnableEmail ) {
2892  return Status::newFatal( 'emaildisabled' );
2893  }
2894 
2895  $oldaddr = $this->getEmail();
2896  if ( $str === $oldaddr ) {
2897  return Status::newGood( true );
2898  }
2899 
2900  $type = $oldaddr != '' ? 'changed' : 'set';
2901  $notificationResult = null;
2902 
2903  if ( $wgEmailAuthentication && $type === 'changed' ) {
2904  // Send the user an email notifying the user of the change in registered
2905  // email address on their previous email address
2906  $change = $str != '' ? 'changed' : 'removed';
2907  $notificationResult = $this->sendMail(
2908  wfMessage( 'notificationemail_subject_' . $change )->text(),
2909  wfMessage( 'notificationemail_body_' . $change,
2910  $this->getRequest()->getIP(),
2911  $this->getName(),
2912  $str )->text()
2913  );
2914  }
2915 
2916  $this->setEmail( $str );
2917 
2918  if ( $str !== '' && $wgEmailAuthentication ) {
2919  // Send a confirmation request to the new address if needed
2920  $result = $this->sendConfirmationMail( $type );
2921 
2922  if ( $notificationResult !== null ) {
2923  $result->merge( $notificationResult );
2924  }
2925 
2926  if ( $result->isGood() ) {
2927  // Say to the caller that a confirmation and notification mail has been sent
2928  $result->value = 'eauth';
2929  }
2930  } else {
2931  $result = Status::newGood( true );
2932  }
2933 
2934  return $result;
2935  }
2936 
2941  public function getRealName() {
2942  if ( !$this->isItemLoaded( 'realname' ) ) {
2943  $this->load();
2944  }
2945 
2946  return $this->mRealName;
2947  }
2948 
2953  public function setRealName( $str ) {
2954  $this->load();
2955  $this->mRealName = $str;
2956  }
2957 
2968  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2969  global $wgHiddenPrefs;
2970  $this->loadOptions();
2971 
2972  # We want 'disabled' preferences to always behave as the default value for
2973  # users, even if they have set the option explicitly in their settings (ie they
2974  # set it, and then it was disabled removing their ability to change it). But
2975  # we don't want to erase the preferences in the database in case the preference
2976  # is re-enabled again. So don't touch $mOptions, just override the returned value
2977  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2978  return self::getDefaultOption( $oname );
2979  }
2980 
2981  if ( array_key_exists( $oname, $this->mOptions ) ) {
2982  return $this->mOptions[$oname];
2983  }
2984 
2985  return $defaultOverride;
2986  }
2987 
2996  public function getOptions( $flags = 0 ) {
2997  global $wgHiddenPrefs;
2998  $this->loadOptions();
3000 
3001  # We want 'disabled' preferences to always behave as the default value for
3002  # users, even if they have set the option explicitly in their settings (ie they
3003  # set it, and then it was disabled removing their ability to change it). But
3004  # we don't want to erase the preferences in the database in case the preference
3005  # is re-enabled again. So don't touch $mOptions, just override the returned value
3006  foreach ( $wgHiddenPrefs as $pref ) {
3007  $default = self::getDefaultOption( $pref );
3008  if ( $default !== null ) {
3009  $options[$pref] = $default;
3010  }
3011  }
3012 
3013  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3014  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3015  }
3016 
3017  return $options;
3018  }
3019 
3027  public function getBoolOption( $oname ) {
3028  return (bool)$this->getOption( $oname );
3029  }
3030 
3039  public function getIntOption( $oname, $defaultOverride = 0 ) {
3040  $val = $this->getOption( $oname );
3041  if ( $val == '' ) {
3042  $val = $defaultOverride;
3043  }
3044  return intval( $val );
3045  }
3046 
3055  public function setOption( $oname, $val ) {
3056  $this->loadOptions();
3057 
3058  // Explicitly NULL values should refer to defaults
3059  if ( is_null( $val ) ) {
3060  $val = self::getDefaultOption( $oname );
3061  }
3062 
3063  $this->mOptions[$oname] = $val;
3064  }
3065 
3076  public function getTokenFromOption( $oname ) {
3077  global $wgHiddenPrefs;
3078 
3079  $id = $this->getId();
3080  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3081  return false;
3082  }
3083 
3084  $token = $this->getOption( $oname );
3085  if ( !$token ) {
3086  // Default to a value based on the user token to avoid space
3087  // wasted on storing tokens for all users. When this option
3088  // is set manually by the user, only then is it stored.
3089  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3090  }
3091 
3092  return $token;
3093  }
3094 
3104  public function resetTokenFromOption( $oname ) {
3105  global $wgHiddenPrefs;
3106  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3107  return false;
3108  }
3109 
3110  $token = MWCryptRand::generateHex( 40 );
3111  $this->setOption( $oname, $token );
3112  return $token;
3113  }
3114 
3138  public static function listOptionKinds() {
3139  return [
3140  'registered',
3141  'registered-multiselect',
3142  'registered-checkmatrix',
3143  'userjs',
3144  'special',
3145  'unused'
3146  ];
3147  }
3148 
3162  $this->loadOptions();
3163  if ( $options === null ) {
3165  }
3166 
3167  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3168  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3169  $mapping = [];
3170 
3171  // Pull out the "special" options, so they don't get converted as
3172  // multiselect or checkmatrix.
3173  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3174  foreach ( $specialOptions as $name => $value ) {
3175  unset( $prefs[$name] );
3176  }
3177 
3178  // Multiselect and checkmatrix options are stored in the database with
3179  // one key per option, each having a boolean value. Extract those keys.
3180  $multiselectOptions = [];
3181  foreach ( $prefs as $name => $info ) {
3182  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3183  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3184  $opts = HTMLFormField::flattenOptions( $info['options'] );
3185  $prefix = $info['prefix'] ?? $name;
3186 
3187  foreach ( $opts as $value ) {
3188  $multiselectOptions["$prefix$value"] = true;
3189  }
3190 
3191  unset( $prefs[$name] );
3192  }
3193  }
3194  $checkmatrixOptions = [];
3195  foreach ( $prefs as $name => $info ) {
3196  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3197  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3198  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3199  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3200  $prefix = $info['prefix'] ?? $name;
3201 
3202  foreach ( $columns as $column ) {
3203  foreach ( $rows as $row ) {
3204  $checkmatrixOptions["$prefix$column-$row"] = true;
3205  }
3206  }
3207 
3208  unset( $prefs[$name] );
3209  }
3210  }
3211 
3212  // $value is ignored
3213  foreach ( $options as $key => $value ) {
3214  if ( isset( $prefs[$key] ) ) {
3215  $mapping[$key] = 'registered';
3216  } elseif ( isset( $multiselectOptions[$key] ) ) {
3217  $mapping[$key] = 'registered-multiselect';
3218  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3219  $mapping[$key] = 'registered-checkmatrix';
3220  } elseif ( isset( $specialOptions[$key] ) ) {
3221  $mapping[$key] = 'special';
3222  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3223  $mapping[$key] = 'userjs';
3224  } else {
3225  $mapping[$key] = 'unused';
3226  }
3227  }
3228 
3229  return $mapping;
3230  }
3231 
3246  public function resetOptions(
3247  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3249  ) {
3250  $this->load();
3251  $defaultOptions = self::getDefaultOptions();
3252 
3253  if ( !is_array( $resetKinds ) ) {
3254  $resetKinds = [ $resetKinds ];
3255  }
3256 
3257  if ( in_array( 'all', $resetKinds ) ) {
3258  $newOptions = $defaultOptions;
3259  } else {
3260  if ( $context === null ) {
3262  }
3263 
3264  $optionKinds = $this->getOptionKinds( $context );
3265  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3266  $newOptions = [];
3267 
3268  // Use default values for the options that should be deleted, and
3269  // copy old values for the ones that shouldn't.
3270  foreach ( $this->mOptions as $key => $value ) {
3271  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3272  if ( array_key_exists( $key, $defaultOptions ) ) {
3273  $newOptions[$key] = $defaultOptions[$key];
3274  }
3275  } else {
3276  $newOptions[$key] = $value;
3277  }
3278  }
3279  }
3280 
3281  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3282 
3283  $this->mOptions = $newOptions;
3284  $this->mOptionsLoaded = true;
3285  }
3286 
3291  public function getDatePreference() {
3292  // Important migration for old data rows
3293  if ( is_null( $this->mDatePreference ) ) {
3294  global $wgLang;
3295  $value = $this->getOption( 'date' );
3296  $map = $wgLang->getDatePreferenceMigrationMap();
3297  if ( isset( $map[$value] ) ) {
3298  $value = $map[$value];
3299  }
3300  $this->mDatePreference = $value;
3301  }
3302  return $this->mDatePreference;
3303  }
3304 
3311  public function requiresHTTPS() {
3312  global $wgSecureLogin;
3313  if ( !$wgSecureLogin ) {
3314  return false;
3315  }
3316 
3317  $https = $this->getBoolOption( 'prefershttps' );
3318  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3319  if ( $https ) {
3320  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3321  }
3322 
3323  return $https;
3324  }
3325 
3331  public function getStubThreshold() {
3332  global $wgMaxArticleSize; # Maximum article size, in Kb
3333  $threshold = $this->getIntOption( 'stubthreshold' );
3334  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3335  // If they have set an impossible value, disable the preference
3336  // so we can use the parser cache again.
3337  $threshold = 0;
3338  }
3339  return $threshold;
3340  }
3341 
3350  public function getRights() {
3351  return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
3352  }
3353 
3360  public function getGroups() {
3361  $this->load();
3362  $this->loadGroups();
3363  return array_keys( $this->mGroupMemberships );
3364  }
3365 
3373  public function getGroupMemberships() {
3374  $this->load();
3375  $this->loadGroups();
3376  return $this->mGroupMemberships;
3377  }
3378 
3386  public function getEffectiveGroups( $recache = false ) {
3387  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3388  $this->mEffectiveGroups = array_unique( array_merge(
3389  $this->getGroups(), // explicit groups
3390  $this->getAutomaticGroups( $recache ) // implicit groups
3391  ) );
3392  // Avoid PHP 7.1 warning of passing $this by reference
3393  $user = $this;
3394  // Hook for additional groups
3395  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3396  // Force reindexation of groups when a hook has unset one of them
3397  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3398  }
3399  return $this->mEffectiveGroups;
3400  }
3401 
3409  public function getAutomaticGroups( $recache = false ) {
3410  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3411  $this->mImplicitGroups = [ '*' ];
3412  if ( $this->getId() ) {
3413  $this->mImplicitGroups[] = 'user';
3414 
3415  $this->mImplicitGroups = array_unique( array_merge(
3416  $this->mImplicitGroups,
3418  ) );
3419  }
3420  if ( $recache ) {
3421  // Assure data consistency with rights/groups,
3422  // as getEffectiveGroups() depends on this function
3423  $this->mEffectiveGroups = null;
3424  }
3425  }
3426  return $this->mImplicitGroups;
3427  }
3428 
3438  public function getFormerGroups() {
3439  $this->load();
3440 
3441  if ( is_null( $this->mFormerGroups ) ) {
3442  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3443  ? wfGetDB( DB_MASTER )
3444  : wfGetDB( DB_REPLICA );
3445  $res = $db->select( 'user_former_groups',
3446  [ 'ufg_group' ],
3447  [ 'ufg_user' => $this->mId ],
3448  __METHOD__ );
3449  $this->mFormerGroups = [];
3450  foreach ( $res as $row ) {
3451  $this->mFormerGroups[] = $row->ufg_group;
3452  }
3453  }
3454 
3455  return $this->mFormerGroups;
3456  }
3457 
3462  public function getEditCount() {
3463  if ( !$this->getId() ) {
3464  return null;
3465  }
3466 
3467  if ( $this->mEditCount === null ) {
3468  /* Populate the count, if it has not been populated yet */
3469  $dbr = wfGetDB( DB_REPLICA );
3470  // check if the user_editcount field has been initialized
3471  $count = $dbr->selectField(
3472  'user', 'user_editcount',
3473  [ 'user_id' => $this->mId ],
3474  __METHOD__
3475  );
3476 
3477  if ( $count === null ) {
3478  // it has not been initialized. do so.
3479  $count = $this->initEditCountInternal( $dbr );
3480  }
3481  $this->mEditCount = $count;
3482  }
3483  return (int)$this->mEditCount;
3484  }
3485 
3497  public function addGroup( $group, $expiry = null ) {
3498  $this->load();
3499  $this->loadGroups();
3500 
3501  if ( $expiry ) {
3502  $expiry = wfTimestamp( TS_MW, $expiry );
3503  }
3504 
3505  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3506  return false;
3507  }
3508 
3509  // create the new UserGroupMembership and put it in the DB
3510  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3511  if ( !$ugm->insert( true ) ) {
3512  return false;
3513  }
3514 
3515  $this->mGroupMemberships[$group] = $ugm;
3516 
3517  // Refresh the groups caches, and clear the rights cache so it will be
3518  // refreshed on the next call to $this->getRights().
3519  $this->getEffectiveGroups( true );
3520  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3521  $this->invalidateCache();
3522 
3523  return true;
3524  }
3525 
3532  public function removeGroup( $group ) {
3533  $this->load();
3534 
3535  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3536  return false;
3537  }
3538 
3539  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3540  // delete the membership entry
3541  if ( !$ugm || !$ugm->delete() ) {
3542  return false;
3543  }
3544 
3545  $this->loadGroups();
3546  unset( $this->mGroupMemberships[$group] );
3547 
3548  // Refresh the groups caches, and clear the rights cache so it will be
3549  // refreshed on the next call to $this->getRights().
3550  $this->getEffectiveGroups( true );
3551  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3552  $this->invalidateCache();
3553 
3554  return true;
3555  }
3556 
3566  public function isRegistered() {
3567  return $this->getId() != 0;
3568  }
3569 
3574  public function isLoggedIn() {
3575  return $this->isRegistered();
3576  }
3577 
3582  public function isAnon() {
3583  return !$this->isRegistered();
3584  }
3585 
3590  public function isBot() {
3591  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3592  return true;
3593  }
3594 
3595  $isBot = false;
3596  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3597 
3598  return $isBot;
3599  }
3600 
3607  public function isAllowedAny() {
3608  $permissions = func_get_args();
3609  foreach ( $permissions as $permission ) {
3610  if ( $this->isAllowed( $permission ) ) {
3611  return true;
3612  }
3613  }
3614  return false;
3615  }
3616 
3622  public function isAllowedAll() {
3623  $permissions = func_get_args();
3624  foreach ( $permissions as $permission ) {
3625  if ( !$this->isAllowed( $permission ) ) {
3626  return false;
3627  }
3628  }
3629  return true;
3630  }
3631 
3642  public function isAllowed( $action = '' ) {
3643  return MediaWikiServices::getInstance()->getPermissionManager()
3644  ->userHasRight( $this, $action );
3645  }
3646 
3651  public function useRCPatrol() {
3652  global $wgUseRCPatrol;
3653  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3654  }
3655 
3660  public function useNPPatrol() {
3662  return (
3663  ( $wgUseRCPatrol || $wgUseNPPatrol )
3664  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3665  );
3666  }
3667 
3672  public function useFilePatrol() {
3674  return (
3675  ( $wgUseRCPatrol || $wgUseFilePatrol )
3676  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3677  );
3678  }
3679 
3685  public function getRequest() {
3686  if ( $this->mRequest ) {
3687  return $this->mRequest;
3688  }
3689 
3690  global $wgRequest;
3691  return $wgRequest;
3692  }
3693 
3702  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3703  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3704  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3705  }
3706  return false;
3707  }
3708 
3716  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3717  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3718  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3719  $this,
3720  [ $title->getSubjectPage(), $title->getTalkPage() ]
3721  );
3722  }
3723  $this->invalidateCache();
3724  }
3725 
3733  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3734  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3735  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3736  $store->removeWatch( $this, $title->getSubjectPage() );
3737  $store->removeWatch( $this, $title->getTalkPage() );
3738  }
3739  $this->invalidateCache();
3740  }
3741 
3750  public function clearNotification( &$title, $oldid = 0 ) {
3752 
3753  // Do nothing if the database is locked to writes
3754  if ( wfReadOnly() ) {
3755  return;
3756  }
3757 
3758  // Do nothing if not allowed to edit the watchlist
3759  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3760  return;
3761  }
3762 
3763  // If we're working on user's talk page, we should update the talk page message indicator
3764  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3765  // Avoid PHP 7.1 warning of passing $this by reference
3766  $user = $this;
3767  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3768  return;
3769  }
3770 
3771  // Try to update the DB post-send and only if needed...
3772  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3773  if ( !$this->getNewtalk() ) {
3774  return; // no notifications to clear
3775  }
3776 
3777  // Delete the last notifications (they stack up)
3778  $this->setNewtalk( false );
3779 
3780  // If there is a new, unseen, revision, use its timestamp
3781  $nextid = $oldid
3782  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3783  : null;
3784  if ( $nextid ) {
3785  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3786  }
3787  } );
3788  }
3789 
3790  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3791  return;
3792  }
3793 
3794  if ( $this->isAnon() ) {
3795  // Nothing else to do...
3796  return;
3797  }
3798 
3799  // Only update the timestamp if the page is being watched.
3800  // The query to find out if it is watched is cached both in memcached and per-invocation,
3801  // and when it does have to be executed, it can be on a replica DB
3802  // If this is the user's newtalk page, we always update the timestamp
3803  $force = '';
3804  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3805  $force = 'force';
3806  }
3807 
3808  MediaWikiServices::getInstance()->getWatchedItemStore()
3809  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3810  }
3811 
3818  public function clearAllNotifications() {
3820  // Do nothing if not allowed to edit the watchlist
3821  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3822  return;
3823  }
3824 
3825  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3826  $this->setNewtalk( false );
3827  return;
3828  }
3829 
3830  $id = $this->getId();
3831  if ( !$id ) {
3832  return;
3833  }
3834 
3835  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
3836  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
3837 
3838  // We also need to clear here the "you have new message" notification for the own
3839  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3840  }
3841 
3847  public function getExperienceLevel() {
3848  global $wgLearnerEdits,
3852 
3853  if ( $this->isAnon() ) {
3854  return false;
3855  }
3856 
3857  $editCount = $this->getEditCount();
3858  $registration = $this->getRegistration();
3859  $now = time();
3860  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3861  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3862 
3863  if ( $editCount < $wgLearnerEdits ||
3864  $registration > $learnerRegistration ) {
3865  return 'newcomer';
3866  }
3867 
3868  if ( $editCount > $wgExperiencedUserEdits &&
3869  $registration <= $experiencedRegistration
3870  ) {
3871  return 'experienced';
3872  }
3873 
3874  return 'learner';
3875  }
3876 
3885  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3886  $this->load();
3887  if ( $this->mId == 0 ) {
3888  return;
3889  }
3890 
3891  $session = $this->getRequest()->getSession();
3892  if ( $request && $session->getRequest() !== $request ) {
3893  $session = $session->sessionWithRequest( $request );
3894  }
3895  $delay = $session->delaySave();
3896 
3897  if ( !$session->getUser()->equals( $this ) ) {
3898  if ( !$session->canSetUser() ) {
3900  ->warning( __METHOD__ .
3901  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3902  );
3903  return;
3904  }
3905  $session->setUser( $this );
3906  }
3907 
3908  $session->setRememberUser( $rememberMe );
3909  if ( $secure !== null ) {
3910  $session->setForceHTTPS( $secure );
3911  }
3912 
3913  $session->persist();
3914 
3915  ScopedCallback::consume( $delay );
3916  }
3917 
3921  public function logout() {
3922  // Avoid PHP 7.1 warning of passing $this by reference
3923  $user = $this;
3924  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
3925  $this->doLogout();
3926  }
3927  }
3928 
3933  public function doLogout() {
3934  $session = $this->getRequest()->getSession();
3935  if ( !$session->canSetUser() ) {
3937  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3938  $error = 'immutable';
3939  } elseif ( !$session->getUser()->equals( $this ) ) {
3941  ->warning( __METHOD__ .
3942  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3943  );
3944  // But we still may as well make this user object anon
3945  $this->clearInstanceCache( 'defaults' );
3946  $error = 'wronguser';
3947  } else {
3948  $this->clearInstanceCache( 'defaults' );
3949  $delay = $session->delaySave();
3950  $session->unpersist(); // Clear cookies (T127436)
3951  $session->setLoggedOutTimestamp( time() );
3952  $session->setUser( new User );
3953  $session->set( 'wsUserID', 0 ); // Other code expects this
3954  $session->resetAllTokens();
3955  ScopedCallback::consume( $delay );
3956  $error = false;
3957  }
3958  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3959  'event' => 'logout',
3960  'successful' => $error === false,
3961  'status' => $error ?: 'success',
3962  ] );
3963  }
3964 
3969  public function saveSettings() {
3970  if ( wfReadOnly() ) {
3971  // @TODO: caller should deal with this instead!
3972  // This should really just be an exception.
3974  null,
3975  "Could not update user with ID '{$this->mId}'; DB is read-only."
3976  ) );
3977  return;
3978  }
3979 
3980  $this->load();
3981  if ( $this->mId == 0 ) {
3982  return; // anon
3983  }
3984 
3985  // Get a new user_touched that is higher than the old one.
3986  // This will be used for a CAS check as a last-resort safety
3987  // check against race conditions and replica DB lag.
3988  $newTouched = $this->newTouchedTimestamp();
3989 
3990  $dbw = wfGetDB( DB_MASTER );
3991  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
3993 
3994  $dbw->update( 'user',
3995  [ /* SET */
3996  'user_name' => $this->mName,
3997  'user_real_name' => $this->mRealName,
3998  'user_email' => $this->mEmail,
3999  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4000  'user_touched' => $dbw->timestamp( $newTouched ),
4001  'user_token' => strval( $this->mToken ),
4002  'user_email_token' => $this->mEmailToken,
4003  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4004  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4005  'user_id' => $this->mId,
4006  ] ), $fname
4007  );
4008 
4009  if ( !$dbw->affectedRows() ) {
4010  // Maybe the problem was a missed cache update; clear it to be safe
4011  $this->clearSharedCache( 'refresh' );
4012  // User was changed in the meantime or loaded with stale data
4013  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4014  LoggerFactory::getInstance( 'preferences' )->warning(
4015  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4016  [ 'user_id' => $this->mId, 'db_flag' => $from ]
4017  );
4018  throw new MWException( "CAS update failed on user_touched. " .
4019  "The version of the user to be saved is older than the current version."
4020  );
4021  }
4022 
4023  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4024  $dbw->update(
4025  'actor',
4026  [ 'actor_name' => $this->mName ],
4027  [ 'actor_user' => $this->mId ],
4028  $fname
4029  );
4030  }
4031  } );
4032 
4033  $this->mTouched = $newTouched;
4034  $this->saveOptions();
4035 
4036  Hooks::run( 'UserSaveSettings', [ $this ] );
4037  $this->clearSharedCache( 'changed' );
4038  $this->getUserPage()->purgeSquid();
4039  }
4040 
4047  public function idForName( $flags = 0 ) {
4048  $s = trim( $this->getName() );
4049  if ( $s === '' ) {
4050  return 0;
4051  }
4052 
4053  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4054  ? wfGetDB( DB_MASTER )
4055  : wfGetDB( DB_REPLICA );
4056 
4057  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4058  ? [ 'LOCK IN SHARE MODE' ]
4059  : [];
4060 
4061  $id = $db->selectField( 'user',
4062  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4063 
4064  return (int)$id;
4065  }
4066 
4082  public static function createNew( $name, $params = [] ) {
4083  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4084  if ( isset( $params[$field] ) ) {
4085  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4086  unset( $params[$field] );
4087  }
4088  }
4089 
4090  $user = new User;
4091  $user->load();
4092  $user->setToken(); // init token
4093  if ( isset( $params['options'] ) ) {
4094  $user->mOptions = $params['options'] + (array)$user->mOptions;
4095  unset( $params['options'] );
4096  }
4097  $dbw = wfGetDB( DB_MASTER );
4098 
4099  $noPass = PasswordFactory::newInvalidPassword()->toString();
4100 
4101  $fields = [
4102  'user_name' => $name,
4103  'user_password' => $noPass,
4104  'user_newpassword' => $noPass,
4105  'user_email' => $user->mEmail,
4106  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4107  'user_real_name' => $user->mRealName,
4108  'user_token' => strval( $user->mToken ),
4109  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4110  'user_editcount' => 0,
4111  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4112  ];
4113  foreach ( $params as $name => $value ) {
4114  $fields["user_$name"] = $value;
4115  }
4116 
4117  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
4118  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4119  if ( $dbw->affectedRows() ) {
4120  $newUser = self::newFromId( $dbw->insertId() );
4121  $newUser->mName = $fields['user_name'];
4122  $newUser->updateActorId( $dbw );
4123  // Load the user from master to avoid replica lag
4124  $newUser->load( self::READ_LATEST );
4125  } else {
4126  $newUser = null;
4127  }
4128  return $newUser;
4129  } );
4130  }
4131 
4158  public function addToDatabase() {
4159  $this->load();
4160  if ( !$this->mToken ) {
4161  $this->setToken(); // init token
4162  }
4163 
4164  if ( !is_string( $this->mName ) ) {
4165  throw new RuntimeException( "User name field is not set." );
4166  }
4167 
4168  $this->mTouched = $this->newTouchedTimestamp();
4169 
4170  $dbw = wfGetDB( DB_MASTER );
4171  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
4172  $noPass = PasswordFactory::newInvalidPassword()->toString();
4173  $dbw->insert( 'user',
4174  [
4175  'user_name' => $this->mName,
4176  'user_password' => $noPass,
4177  'user_newpassword' => $noPass,
4178  'user_email' => $this->mEmail,
4179  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4180  'user_real_name' => $this->mRealName,
4181  'user_token' => strval( $this->mToken ),
4182  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4183  'user_editcount' => 0,
4184  'user_touched' => $dbw->timestamp( $this->mTouched ),
4185  ], $fname,
4186  [ 'IGNORE' ]
4187  );
4188  if ( !$dbw->affectedRows() ) {
4189  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4190  $this->mId = $dbw->selectField(
4191  'user',
4192  'user_id',
4193  [ 'user_name' => $this->mName ],
4194  $fname,
4195  [ 'LOCK IN SHARE MODE' ]
4196  );
4197  $loaded = false;
4198  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
4199  $loaded = true;
4200  }
4201  if ( !$loaded ) {
4202  throw new MWException( $fname . ": hit a key conflict attempting " .
4203  "to insert user '{$this->mName}' row, but it was not present in select!" );
4204  }
4205  return Status::newFatal( 'userexists' );
4206  }
4207  $this->mId = $dbw->insertId();
4208  self::$idCacheByName[$this->mName] = $this->mId;
4209  $this->updateActorId( $dbw );
4210 
4211  return Status::newGood();
4212  } );
4213  if ( !$status->isGood() ) {
4214  return $status;
4215  }
4216 
4217  // Clear instance cache other than user table data and actor, which is already accurate
4218  $this->clearInstanceCache();
4219 
4220  $this->saveOptions();
4221  return Status::newGood();
4222  }
4223 
4228  private function updateActorId( IDatabase $dbw ) {
4230 
4231  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4232  $dbw->insert(
4233  'actor',
4234  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4235  __METHOD__
4236  );
4237  $this->mActorId = (int)$dbw->insertId();
4238  }
4239  }
4240 
4246  public function spreadAnyEditBlock() {
4247  if ( $this->isLoggedIn() && $this->getBlock() ) {
4248  return $this->spreadBlock();
4249  }
4250 
4251  return false;
4252  }
4253 
4259  protected function spreadBlock() {
4260  wfDebug( __METHOD__ . "()\n" );
4261  $this->load();
4262  if ( $this->mId == 0 ) {
4263  return false;
4264  }
4265 
4266  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
4267  if ( !$userblock ) {
4268  return false;
4269  }
4270 
4271  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4272  }
4273 
4278  public function isBlockedFromCreateAccount() {
4279  $this->getBlockedStatus();
4280  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4281  return $this->mBlock;
4282  }
4283 
4284  # T15611: if the IP address the user is trying to create an account from is
4285  # blocked with createaccount disabled, prevent new account creation there even
4286  # when the user is logged in
4287  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4288  $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
4289  null, $this->getRequest()->getIP()
4290  );
4291  }
4292  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
4293  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4294  ? $this->mBlockedFromCreateAccount
4295  : false;
4296  }
4297 
4302  public function isBlockedFromEmailuser() {
4303  $this->getBlockedStatus();
4304  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4305  }
4306 
4313  public function isBlockedFromUpload() {
4314  $this->getBlockedStatus();
4315  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4316  }
4317 
4322  public function isAllowedToCreateAccount() {
4323  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4324  }
4325 
4331  public function getUserPage() {
4332  return Title::makeTitle( NS_USER, $this->getName() );
4333  }
4334 
4340  public function getTalkPage() {
4341  $title = $this->getUserPage();
4342  return $title->getTalkPage();
4343  }
4344 
4350  public function isNewbie() {
4351  return !$this->isAllowed( 'autoconfirmed' );
4352  }
4353 
4360  public function checkPassword( $password ) {
4361  wfDeprecated( __METHOD__, '1.27' );
4362 
4363  $manager = AuthManager::singleton();
4364  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4365  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4366  [
4367  'username' => $this->getName(),
4368  'password' => $password,
4369  ]
4370  );
4371  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4372  switch ( $res->status ) {
4373  case AuthenticationResponse::PASS:
4374  return true;
4375  case AuthenticationResponse::FAIL:
4376  // Hope it's not a PreAuthenticationProvider that failed...
4378  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4379  return false;
4380  default:
4381  throw new BadMethodCallException(
4382  'AuthManager returned a response unsupported by ' . __METHOD__
4383  );
4384  }
4385  }
4386 
4395  public function checkTemporaryPassword( $plaintext ) {
4396  wfDeprecated( __METHOD__, '1.27' );
4397  // Can't check the temporary password individually.
4398  return $this->checkPassword( $plaintext );
4399  }
4400 
4412  public function getEditTokenObject( $salt = '', $request = null ) {
4413  if ( $this->isAnon() ) {
4414  return new LoggedOutEditToken();
4415  }
4416 
4417  if ( !$request ) {
4418  $request = $this->getRequest();
4419  }
4420  return $request->getSession()->getToken( $salt );
4421  }
4422 
4436  public function getEditToken( $salt = '', $request = null ) {
4437  return $this->getEditTokenObject( $salt, $request )->toString();
4438  }
4439 
4452  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4453  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4454  }
4455 
4466  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4467  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4468  return $this->matchEditToken( $val, $salt, $request, $maxage );
4469  }
4470 
4478  public function sendConfirmationMail( $type = 'created' ) {
4479  global $wgLang;
4480  $expiration = null; // gets passed-by-ref and defined in next line.
4481  $token = $this->confirmationToken( $expiration );
4482  $url = $this->confirmationTokenUrl( $token );
4483  $invalidateURL = $this->invalidationTokenUrl( $token );
4484  $this->saveSettings();
4485 
4486  if ( $type == 'created' || $type === false ) {
4487  $message = 'confirmemail_body';
4488  $type = 'created';
4489  } elseif ( $type === true ) {
4490  $message = 'confirmemail_body_changed';
4491  $type = 'changed';
4492  } else {
4493  // Messages: confirmemail_body_changed, confirmemail_body_set
4494  $message = 'confirmemail_body_' . $type;
4495  }
4496 
4497  $mail = [
4498  'subject' => wfMessage( 'confirmemail_subject' )->text(),
4499  'body' => wfMessage( $message,
4500  $this->getRequest()->getIP(),
4501  $this->getName(),
4502  $url,
4503  $wgLang->userTimeAndDate( $expiration, $this ),
4504  $invalidateURL,
4505  $wgLang->userDate( $expiration, $this ),
4506  $wgLang->userTime( $expiration, $this ) )->text(),
4507  'from' => null,
4508  'replyTo' => null,
4509  ];
4510  $info = [
4511  'type' => $type,
4512  'ip' => $this->getRequest()->getIP(),
4513  'confirmURL' => $url,
4514  'invalidateURL' => $invalidateURL,
4515  'expiration' => $expiration
4516  ];
4517 
4518  Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4519  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4520  }
4521 
4533  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4534  global $wgPasswordSender;
4535 
4536  if ( $from instanceof User ) {
4537  $sender = MailAddress::newFromUser( $from );
4538  } else {
4539  $sender = new MailAddress( $wgPasswordSender,
4540  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4541  }
4542  $to = MailAddress::newFromUser( $this );
4543 
4544  return UserMailer::send( $to, $sender, $subject, $body, [
4545  'replyTo' => $replyto,
4546  ] );
4547  }
4548 
4559  protected function confirmationToken( &$expiration ) {
4561  $now = time();
4562  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4563  $expiration = wfTimestamp( TS_MW, $expires );
4564  $this->load();
4565  $token = MWCryptRand::generateHex( 32 );
4566  $hash = md5( $token );
4567  $this->mEmailToken = $hash;
4568  $this->mEmailTokenExpires = $expiration;
4569  return $token;
4570  }
4571 
4577  protected function confirmationTokenUrl( $token ) {
4578  return $this->getTokenUrl( 'ConfirmEmail', $token );
4579  }
4580 
4586  protected function invalidationTokenUrl( $token ) {
4587  return $this->getTokenUrl( 'InvalidateEmail', $token );
4588  }
4589 
4604  protected function getTokenUrl( $page, $token ) {
4605  // Hack to bypass localization of 'Special:'
4606  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4607  return $title->getCanonicalURL();
4608  }
4609 
4617  public function confirmEmail() {
4618  // Check if it's already confirmed, so we don't touch the database
4619  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4620  if ( !$this->isEmailConfirmed() ) {
4622  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4623  }
4624  return true;
4625  }
4626 
4634  public function invalidateEmail() {
4635  $this->load();
4636  $this->mEmailToken = null;
4637  $this->mEmailTokenExpires = null;
4639  $this->mEmail = '';
4640  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4641  return true;
4642  }
4643 
4648  public function setEmailAuthenticationTimestamp( $timestamp ) {
4649  $this->load();
4650  $this->mEmailAuthenticated = $timestamp;
4651  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4652  }
4653 
4659  public function canSendEmail() {
4661  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4662  return false;
4663  }
4664  $canSend = $this->isEmailConfirmed();
4665  // Avoid PHP 7.1 warning of passing $this by reference
4666  $user = $this;
4667  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4668  return $canSend;
4669  }
4670 
4676  public function canReceiveEmail() {
4677  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4678  }
4679 
4690  public function isEmailConfirmed() {
4691  global $wgEmailAuthentication;
4692  $this->load();
4693  // Avoid PHP 7.1 warning of passing $this by reference
4694  $user = $this;
4695  $confirmed = true;
4696  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4697  if ( $this->isAnon() ) {
4698  return false;
4699  }
4700  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4701  return false;
4702  }
4703  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4704  return false;
4705  }
4706  return true;
4707  }
4708 
4709  return $confirmed;
4710  }
4711 
4716  public function isEmailConfirmationPending() {
4717  global $wgEmailAuthentication;
4718  return $wgEmailAuthentication &&
4719  !$this->isEmailConfirmed() &&
4720  $this->mEmailToken &&
4721  $this->mEmailTokenExpires > wfTimestamp();
4722  }
4723 
4731  public function getRegistration() {
4732  if ( $this->isAnon() ) {
4733  return false;
4734  }
4735  $this->load();
4736  return $this->mRegistration;
4737  }
4738 
4745  public function getFirstEditTimestamp() {
4746  return $this->getEditTimestamp( true );
4747  }
4748 
4756  public function getLatestEditTimestamp() {
4757  return $this->getEditTimestamp( false );
4758  }
4759 
4767  private function getEditTimestamp( $first ) {
4768  if ( $this->getId() == 0 ) {
4769  return false; // anons
4770  }
4771  $dbr = wfGetDB( DB_REPLICA );
4772  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4773  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4774  ? 'revactor_timestamp' : 'rev_timestamp';
4775  $sortOrder = $first ? 'ASC' : 'DESC';
4776  $time = $dbr->selectField(
4777  [ 'revision' ] + $actorWhere['tables'],
4778  $tsField,
4779  [ $actorWhere['conds'] ],
4780  __METHOD__,
4781  [ 'ORDER BY' => "$tsField $sortOrder" ],
4782  $actorWhere['joins']
4783  );
4784  if ( !$time ) {
4785  return false; // no edits
4786  }
4787  return wfTimestamp( TS_MW, $time );
4788  }
4789 
4799  public static function getGroupPermissions( $groups ) {
4800  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4801  }
4802 
4812  public static function getGroupsWithPermission( $role ) {
4813  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4814  }
4815 
4831  public static function groupHasPermission( $group, $role ) {
4832  return MediaWikiServices::getInstance()->getPermissionManager()
4833  ->groupHasPermission( $group, $role );
4834  }
4835 
4854  public static function isEveryoneAllowed( $right ) {
4855  return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4856  }
4857 
4864  public static function getAllGroups() {
4866  return array_values( array_diff(
4867  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4868  self::getImplicitGroups()
4869  ) );
4870  }
4871 
4880  public static function getAllRights() {
4881  return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4882  }
4883 
4890  public static function getImplicitGroups() {
4891  global $wgImplicitGroups;
4892  return $wgImplicitGroups;
4893  }
4894 
4904  public static function changeableByGroup( $group ) {
4906 
4907  $groups = [
4908  'add' => [],
4909  'remove' => [],
4910  'add-self' => [],
4911  'remove-self' => []
4912  ];
4913 
4914  if ( empty( $wgAddGroups[$group] ) ) {
4915  // Don't add anything to $groups
4916  } elseif ( $wgAddGroups[$group] === true ) {
4917  // You get everything
4918  $groups['add'] = self::getAllGroups();
4919  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4920  $groups['add'] = $wgAddGroups[$group];
4921  }
4922 
4923  // Same thing for remove
4924  if ( empty( $wgRemoveGroups[$group] ) ) {
4925  // Do nothing
4926  } elseif ( $wgRemoveGroups[$group] === true ) {
4927  $groups['remove'] = self::getAllGroups();
4928  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4929  $groups['remove'] = $wgRemoveGroups[$group];
4930  }
4931 
4932  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4933  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4934  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4935  if ( is_int( $key ) ) {
4936  $wgGroupsAddToSelf['user'][] = $value;
4937  }
4938  }
4939  }
4940 
4941  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4942  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4943  if ( is_int( $key ) ) {
4944  $wgGroupsRemoveFromSelf['user'][] = $value;
4945  }
4946  }
4947  }
4948 
4949  // Now figure out what groups the user can add to him/herself
4950  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4951  // Do nothing
4952  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4953  // No idea WHY this would be used, but it's there
4954  $groups['add-self'] = self::getAllGroups();
4955  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4956  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4957  }
4958 
4959  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4960  // Do nothing
4961  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4962  $groups['remove-self'] = self::getAllGroups();
4963  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4964  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4965  }
4966 
4967  return $groups;
4968  }
4969 
4977  public function changeableGroups() {
4978  if ( $this->isAllowed( 'userrights' ) ) {
4979  // This group gives the right to modify everything (reverse-
4980  // compatibility with old "userrights lets you change
4981  // everything")
4982  // Using array_merge to make the groups reindexed
4983  $all = array_merge( self::getAllGroups() );
4984  return [
4985  'add' => $all,
4986  'remove' => $all,
4987  'add-self' => [],
4988  'remove-self' => []
4989  ];
4990  }
4991 
4992  // Okay, it's not so simple, we will have to go through the arrays
4993  $groups = [
4994  'add' => [],
4995  'remove' => [],
4996  'add-self' => [],
4997  'remove-self' => []
4998  ];
4999  $addergroups = $this->getEffectiveGroups();
5000 
5001  foreach ( $addergroups as $addergroup ) {
5002  $groups = array_merge_recursive(
5003  $groups, $this->changeableByGroup( $addergroup )
5004  );
5005  $groups['add'] = array_unique( $groups['add'] );
5006  $groups['remove'] = array_unique( $groups['remove'] );
5007  $groups['add-self'] = array_unique( $groups['add-self'] );
5008  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5009  }
5010  return $groups;
5011  }
5012 
5016  public function incEditCount() {
5017  if ( $this->isAnon() ) {
5018  return; // sanity
5019  }
5020 
5022  new UserEditCountUpdate( $this, 1 ),
5024  );
5025  }
5026 
5032  public function setEditCountInternal( $count ) {
5033  $this->mEditCount = $count;
5034  }
5035 
5043  public function initEditCountInternal( IDatabase $dbr ) {
5044  // Pull from a replica DB to be less cruel to servers
5045  // Accuracy isn't the point anyway here
5046  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5047  $count = (int)$dbr->selectField(
5048  [ 'revision' ] + $actorWhere['tables'],
5049  'COUNT(*)',
5050  [ $actorWhere['conds'] ],
5051  __METHOD__,
5052  [],
5053  $actorWhere['joins']
5054  );
5055 
5056  $dbw = wfGetDB( DB_MASTER );
5057  $dbw->update(
5058  'user',
5059  [ 'user_editcount' => $count ],
5060  [
5061  'user_id' => $this->getId(),
5062  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5063  ],
5064  __METHOD__
5065  );
5066 
5067  return $count;
5068  }
5069 
5077  public static function getRightDescription( $right ) {
5078  $key = "right-$right";
5079  $msg = wfMessage( $key );
5080  return $msg->isDisabled() ? $right : $msg->text();
5081  }
5082 
5090  public static function getGrantName( $grant ) {
5091  $key = "grant-$grant";
5092  $msg = wfMessage( $key );
5093  return $msg->isDisabled() ? $grant : $msg->text();
5094  }
5095 
5116  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5117  return true; // disabled
5118  }
5119 
5128  public function addNewUserLogEntryAutoCreate() {
5129  $this->addNewUserLogEntry( 'autocreate' );
5130 
5131  return true;
5132  }
5133 
5139  protected function loadOptions( $data = null ) {
5140  $this->load();
5141 
5142  if ( $this->mOptionsLoaded ) {
5143  return;
5144  }
5145 
5146  $this->mOptions = self::getDefaultOptions();
5147 
5148  if ( !$this->getId() ) {
5149  // For unlogged-in users, load language/variant options from request.
5150  // There's no need to do it for logged-in users: they can set preferences,
5151  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5152  // so don't override user's choice (especially when the user chooses site default).
5153  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5154  $this->mOptions['variant'] = $variant;
5155  $this->mOptions['language'] = $variant;
5156  $this->mOptionsLoaded = true;
5157  return;
5158  }
5159 
5160  // Maybe load from the object
5161  if ( !is_null( $this->mOptionOverrides ) ) {
5162  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5163  foreach ( $this->mOptionOverrides as $key => $value ) {
5164  $this->mOptions[$key] = $value;
5165  }
5166  } else {
5167  if ( !is_array( $data ) ) {
5168  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5169  // Load from database
5170  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5171  ? wfGetDB( DB_MASTER )
5172  : wfGetDB( DB_REPLICA );
5173 
5174  $res = $dbr->select(
5175  'user_properties',
5176  [ 'up_property', 'up_value' ],
5177  [ 'up_user' => $this->getId() ],
5178  __METHOD__
5179  );
5180 
5181  $this->mOptionOverrides = [];
5182  $data = [];
5183  foreach ( $res as $row ) {
5184  // Convert '0' to 0. PHP's boolean conversion considers them both
5185  // false, but e.g. JavaScript considers the former as true.
5186  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5187  // and convert all values here.
5188  if ( $row->up_value === '0' ) {
5189  $row->up_value = 0;
5190  }
5191  $data[$row->up_property] = $row->up_value;
5192  }
5193  }
5194 
5195  foreach ( $data as $property => $value ) {
5196  $this->mOptionOverrides[$property] = $value;
5197  $this->mOptions[$property] = $value;
5198  }
5199  }
5200 
5201  // Replace deprecated language codes
5202  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5203  $this->mOptions['language']
5204  );
5205 
5206  $this->mOptionsLoaded = true;
5207 
5208  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5209  }
5210 
5216  protected function saveOptions() {
5217  $this->loadOptions();
5218 
5219  // Not using getOptions(), to keep hidden preferences in database
5220  $saveOptions = $this->mOptions;
5221 
5222  // Allow hooks to abort, for instance to save to a global profile.
5223  // Reset options to default state before saving.
5224  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5225  return;
5226  }
5227 
5228  $userId = $this->getId();
5229 
5230  $insert_rows = []; // all the new preference rows
5231  foreach ( $saveOptions as $key => $value ) {
5232  // Don't bother storing default values
5233  $defaultOption = self::getDefaultOption( $key );
5234  if ( ( $defaultOption === null && $value !== false && $value !== null )
5235  || $value != $defaultOption
5236  ) {
5237  $insert_rows[] = [
5238  'up_user' => $userId,
5239  'up_property' => $key,
5240  'up_value' => $value,
5241  ];
5242  }
5243  }
5244 
5245  $dbw = wfGetDB( DB_MASTER );
5246 
5247  $res = $dbw->select( 'user_properties',
5248  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5249 
5250  // Find prior rows that need to be removed or updated. These rows will
5251  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5252  $keysDelete = [];
5253  foreach ( $res as $row ) {
5254  if ( !isset( $saveOptions[$row->up_property] )
5255  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5256  ) {
5257  $keysDelete[] = $row->up_property;
5258  }
5259  }
5260 
5261  if ( count( $keysDelete ) ) {
5262  // Do the DELETE by PRIMARY KEY for prior rows.
5263  // In the past a very large portion of calls to this function are for setting
5264  // 'rememberpassword' for new accounts (a preference that has since been removed).
5265  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5266  // caused gap locks on [max user ID,+infinity) which caused high contention since
5267  // updates would pile up on each other as they are for higher (newer) user IDs.
5268  // It might not be necessary these days, but it shouldn't hurt either.
5269  $dbw->delete( 'user_properties',
5270  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5271  }
5272  // Insert the new preference rows
5273  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5274  }
5275 
5282  public static function selectFields() {
5283  wfDeprecated( __METHOD__, '1.31' );
5284  return [
5285  'user_id',
5286  'user_name',
5287  'user_real_name',
5288  'user_email',
5289  'user_touched',
5290  'user_token',
5291  'user_email_authenticated',
5292  'user_email_token',
5293  'user_email_token_expires',
5294  'user_registration',
5295  'user_editcount',
5296  ];
5297  }
5298 
5308  public static function getQueryInfo() {
5310 
5311  $ret = [
5312  'tables' => [ 'user' ],
5313  'fields' => [
5314  'user_id',
5315  'user_name',
5316  'user_real_name',
5317  'user_email',
5318  'user_touched',
5319  'user_token',
5320  'user_email_authenticated',
5321  'user_email_token',
5322  'user_email_token_expires',
5323  'user_registration',
5324  'user_editcount',
5325  ],
5326  'joins' => [],
5327  ];
5328 
5329  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
5330  // but it does little harm and might be needed for write callers loading a User.
5331  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
5332  $ret['tables']['user_actor'] = 'actor';
5333  $ret['fields'][] = 'user_actor.actor_id';
5334  $ret['joins']['user_actor'] = [
5335  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
5336  [ 'user_actor.actor_user = user_id' ]
5337  ];
5338  }
5339 
5340  return $ret;
5341  }
5342 
5350  static function newFatalPermissionDeniedStatus( $permission ) {
5351  global $wgLang;
5352 
5353  $groups = [];
5354  foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5355  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5356  }
5357 
5358  if ( $groups ) {
5359  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5360  }
5361 
5362  return Status::newFatal( 'badaccess-group0' );
5363  }
5364 
5374  public function getInstanceForUpdate() {
5375  if ( !$this->getId() ) {
5376  return null; // anon
5377  }
5378 
5379  $user = self::newFromId( $this->getId() );
5380  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5381  return null;
5382  }
5383 
5384  return $user;
5385  }
5386 
5394  public function equals( UserIdentity $user ) {
5395  // XXX it's not clear whether central ID providers are supposed to obey this
5396  return $this->getName() === $user->getName();
5397  }
5398 
5404  public function isAllowUsertalk() {
5405  return $this->mAllowUsertalk;
5406  }
5407 
5408 }
static array null $defOpt
Definition: User.php:1669
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition: User.php:5043
getEmail()
Get the user&#39;s e-mail address.
Definition: User.php:2851
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
isHidden()
Check if user account is hidden.
Definition: User.php:2207
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
static getMembership( $userId, $group, IDatabase $db=null)
Returns a UserGroupMembership object that pertains to the given user and group, or false if the user ...
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2702
string $mBlockedby
Definition: User.php:186
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:125
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:67
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2614
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
setRealName( $str)
Set the user&#39;s real name.
Definition: User.php:2953
$wgMaxArticleSize
Maximum article size in kilobytes.
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1278
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2403
isAllowedAll()
Definition: User.php:3622
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1641
string $mDatePreference
Definition: User.php:184
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4350
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:844
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:102
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3716
$property
$wgMaxNameChars
Maximum number of bytes in username.
UserGroupMembership [] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:149
affectedRows()
Get the number of rows affected by the last write query.
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
loadFromSession()
Load user data from the session.
Definition: User.php:1289
const NS_MAIN
Definition: Defines.php:60
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4904
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4322
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
$success
logout()
Log this user out.
Definition: User.php:3921
clearNotification(&$title, $oldid=0)
Clear the user&#39;s notification timestamp for the given title.
Definition: User.php:3750
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4799
static getImplicitGroups()
Get a list of implicit groups TODO: Should we deprecate this? It&#39;s trivial, but we don&#39;t want to enco...
Definition: User.php:4890
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
saveSettings()
Save this user&#39;s settings into the database.
Definition: User.php:3969
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2242
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Definition: UserMailer.php:115
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4745
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1963
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4854
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:926
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2633
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:73
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3607
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4302
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3386
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:56
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2618
Handles increment the edit count for a given set of users.
AbstractBlock $mGlobalBlock
Definition: User.php:198
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
static string [] $mAllRights
Cached results of getAllRights()
Definition: User.php:117
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2145
setName( $str)
Set the user name.
Definition: User.php:2279
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:2968
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2114
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:163
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:135
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
& __get( $name)
Definition: User.php:246
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:295
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:627
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2089
static getWikiIdFromDbDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:269
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3847
__toString()
Definition: User.php:242
Exception thrown when an actor can&#39;t be created.
null for the local wiki Added in
Definition: hooks.txt:1566
getRealName()
Get the user&#39;s real name.
Definition: User.php:2941
$wgSecureLogin
This is to let user authenticate using https when they come from http.
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2463
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
$value
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user&#39;s account.
Definition: User.php:4533
Check if a user&#39;s password complies with any password policies that apply to that user...
checkAndSetTouched()
Bump user_touched if it didn&#39;t change since this object was loaded.
Definition: User.php:1603
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4977
clearAllNotifications()
Resets all of the given user&#39;s page-change notification timestamps.
Definition: User.php:3818
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:283
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4466
static string null $defOptLang
Definition: User.php:1671
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3574
static getLocalClusterInstance()
Get the main cluster-local cache object.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
$wgGroupsRemoveFromSelf
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2823
insertId()
Get the inserted value of an auto-increment row.
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:864
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:685
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of or an object and a method hook function The function part of a third party developers and local administrators to define code that will be run at certain points in the mainline and to modify the data run by that mainline code Hooks can keep mainline code and make it easier to write extensions Hooks are a principled alternative to local patches for two options in MediaWiki One reverses the order of a title before displaying the article
Definition: hooks.txt:23
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition: User.php:4313
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4395
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
$wgHiddenPrefs
An array of preferences to not show for the user.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object...
Definition: User.php:5308
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:906
array $mFormerGroups
Definition: User.php:196
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2667
target page
$wgRevokePermissions
Permission keys revoked from users in each group.
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:574
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3055
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2159
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition: User.php:4767
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1794
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1780
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:2078
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Value object representing a logged-out user&#39;s edit token.
const DB_MASTER
Definition: defines.php:26
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition: IP.php:729
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user&#39;s session (e.g.
Definition: User.php:3885
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2251
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5282
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4228
string $mName
Cache variables.
Definition: User.php:124
string $mRegistration
Cache variables.
Definition: User.php:145
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
int $mEditCount
Cache variables.
Definition: User.php:147
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2753
int null $mActorId
Cache variables.
Definition: User.php:126
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3733
static purge( $dbDomain, $userId)
Definition: User.php:438
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1961
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4360
getTitleKey()
Get the user&#39;s name escaped by underscores.
Definition: User.php:2357
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4864
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3138
$wgGroupPermissions
Permission keys given to users in each group.
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1036
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:2164
Interface for objects representing user identity.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
string $mEmailTokenExpires
Cache variables.
Definition: User.php:143
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4880
isEmailConfirmed()
Is this user&#39;s e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4690
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1963
int bool $mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:182
getEditToken( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4436
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1174
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1244
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4278
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:408
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:854
makeGlobalKey( $class, $component=null)
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2478
string $mEmailToken
Cache variables.
Definition: User.php:141
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it&#39;s empty (and savin...
Definition: User.php:3076
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1534
$wgPasswordPolicy
Password policy for the wiki.
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4731
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects...
Definition: User.php:3373
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
wfReadOnly()
Check whether the wiki is in read-only mode.
$wgLang
Definition: Setup.php:937
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5077
static newMigration()
Static constructor.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
static getMain()
Get the RequestContext object associated with the main request.
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations, but may be used internally either by batch processes or by user accounts which have already been created.
Definition: User.php:1076
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4577
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing we can concentrate it all in an extension file
Definition: hooks.txt:91
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:460
static newFromIDs( $ids)
Definition: UserArray.php:42
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5128
$wgExperiencedUserEdits
Name of the external diff engine to use.
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1106
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4812
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:139
getBoolOption( $oname)
Get the user&#39;s current setting for a given option, as a boolean value.
Definition: User.php:3027
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1585
string $mBlockreason
Definition: User.php:190
isAnon()
Get whether the user is anonymous.
Definition: User.php:3582
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5404
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1747
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4604
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2575
static int [] $idCacheByName
Definition: User.php:222
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1855
Stores a single person&#39;s name and email address.
Definition: MailAddress.php:32
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:282
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5090
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3104
$res
Definition: database.txt:21
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:219
$wgImplicitGroups
Implicit groups, aren&#39;t shown on Special:Listusers or somewhere else.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getEditTokenObject( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4412
doLogout()
Clear the user&#39;s session, and reset the instance cache.
Definition: User.php:3933
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:308
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5394
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:472
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4716
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:56
incEditCount()
Schedule a deferred update to update the user&#39;s edit count.
Definition: User.php:5016
System blocks are temporary blocks that are created on enforcement (e.g.
Definition: SystemBlock.php:35
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2556
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1807
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
getOptionKinds(IContextSource $context, $options=null)
Return an associative array mapping preferences keys to the kind of a preference they&#39;re used for...
Definition: User.php:3161
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1329
$wgProxyList
Big list of banned IP addresses.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:133
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4659
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4756
array $mEffectiveGroups
Definition: User.php:192
$cache
Definition: mcc.php:33
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2715
const IGNORE_USER_RIGHTS
Definition: User.php:83
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
$params
string $mEmailAuthenticated
Cache variables.
Definition: User.php:139
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3642
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1963
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
array $wgLearnerEdits
The following variables define 3 user experience levels:
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:176
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3702
WebRequest $mRequest
Definition: User.php:207
$wgRemoveGroups
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format...
Definition: MWCryptRand.php:36
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1268
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:767
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:35
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:1001
if(ini_get('mbstring.func_overload'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:51
trackBlockWithCookie()
Set the &#39;BlockID&#39; cookie depending on block type and user authentication status.
Definition: User.php:1318
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
requiresHTTPS()
Determine based on the wiki configuration and the user&#39;s options, whether this user must be over HTTP...
Definition: User.php:3311
$wgRateLimits
Simple rate limiter options to brake edit floods.
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2105
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3566
array $mImplicitGroups
Definition: User.php:194
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1500
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they&#39;ve successfully logged in from...
Definition: User.php:4259
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
static hasFlags( $bitfield, $flags)
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
isBot()
Definition: User.php:3590
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
AbstractBlock $mBlock
Definition: User.php:210
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4648
__set( $name, $value)
Definition: User.php:262
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1747
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:5032
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:767
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:602
string $mToken
Cache variables.
Definition: User.php:137
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:131
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4831
getToken( $forceCreation=true)
Get the user&#39;s current token.
Definition: User.php:2780
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:991
static resetGetDefaultOptionsForTestsOnly()
Reset the process cache of default user options.
Definition: User.php:1679
static newInvalidPassword()
Create an InvalidPassword.
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3651
$wgPasswordSender
Sender email address for e-mail notifications.
appliesToRight( $right)
Determine whether the block prevents a given right.
$wgLearnerMemberSince
Name of the external diff engine to use.
loadDefaults( $name=false)
Set cached properties to default.
Definition: User.php:1229
array $mOptions
Definition: User.php:204
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:216
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1733
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:2843
invalidateEmail()
Invalidate the user&#39;s e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4634
loadGroups()
Load the groups from the database if they aren&#39;t already loaded.
Definition: User.php:1510
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5216
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:618
static string [] $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:92
isIPRange()
Is the user an IP range?
Definition: User.php:937
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:590
static getCurrentWikiDbDomain()
Definition: WikiMap.php:293
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3331
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3685
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
string $mEmail
Cache variables.
Definition: User.php:131
this hook is for auditing only $req
Definition: hooks.txt:960
getNewtalk()
Check if the user has new messages.
Definition: User.php:2365
bool $mLocked
Definition: User.php:200
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:559
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1382
$wgUseEnotif
Definition: Setup.php:446
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3360
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:235
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date...
getIntOption( $oname, $defaultOverride=0)
Get the user&#39;s current setting for a given option, as an integer value.
Definition: User.php:3039
idForName( $flags=0)
If only this user&#39;s username is known, and it exists, return the user ID.
Definition: User.php:4047
getOptions( $flags=0)
Get all user&#39;s options.
Definition: User.php:2996
The ContentHandler facility adds support for arbitrary content types on wiki pages
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they&#39;ve successfully logged in from...
Definition: User.php:4246
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:313
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:952
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3672
getId()
Get the user&#39;s ID.
Definition: User.php:2224
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4452
getEmailAuthenticationTimestamp()
Get the timestamp of the user&#39;s e-mail authentication.
Definition: User.php:2861
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1928
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5139
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4158
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
getActorId(IDatabase $dbw=null)
Get the user&#39;s actor ID.
Definition: User.php:2290
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:731
bool $mAllowUsertalk
Definition: User.php:213
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2601
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4617
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5350
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
bool $mHideName
Definition: User.php:202
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:709
getEditCount()
Get the user&#39;s edit count.
Definition: User.php:3462
getUserPage()
Get this user&#39;s personal page title.
Definition: User.php:4331
setNewtalk( $val, $curRev=null)
Update the &#39;You have new messages!&#39; status.
Definition: User.php:2523
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5374
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3660
const SCHEMA_COMPAT_NEW
Definition: Defines.php:287
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2614
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3291
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2132
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:655
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user&#39;s give...
Definition: User.php:4478
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2436
MediaWiki Logger LoggerFactory implements a PSR [0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method. MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances. The "Spi" in MediaWiki\Logger\Spi stands for "service provider interface". An SPI is an API intended to be implemented or extended by a third party. This software design pattern is intended to enable framework extension and replaceable components. It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki. The service provider interface allows the backend logging library to be implemented in multiple ways. The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime. This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance. Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
isLocked()
Check if user account is locked.
Definition: User.php:2192
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:791
string $mHash
Definition: User.php:188
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2503
bool $mOptionsLoaded
Whether the cache variables have been loaded.
Definition: User.php:158
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4676
const DB_REPLICA
Definition: defines.php:25
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS...
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4586
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1132
setEmailWithConfirmation( $str)
Set the user&#39;s e-mail address and a confirmation mail if needed.
Definition: User.php:2888
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4082
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3409
array $mOptionOverrides
Cache variables.
Definition: User.php:151
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:535
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1691
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:61
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2689
string $mRealName
Cache variables.
Definition: User.php:128
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3497
isSafeToLoad()
Test if it&#39;s safe to load this User object.
Definition: User.php:291
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:118
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
getCacheKey(WANObjectCache $cache)
Definition: User.php:449
setEmail( $str)
Set the user&#39;s e-mail address.
Definition: User.php:2871
const NS_USER_TALK
Definition: Defines.php:63
static array $languagesWithVariants
languages supporting variants
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5116
getTalkPage()
Get this user&#39;s talk page title.
Definition: User.php:4340
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1903
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2614
static singleton()
Definition: UserCache.php:34
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3532
const CHECK_USER_RIGHTS
Definition: User.php:78
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1454
getRights()
Get the permissions this user has.
Definition: User.php:3350
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3438
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2123
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
Base class for the more common types of database errors.
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3246
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:772
int $mId
Cache variables.
Definition: User.php:122
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4559
getTouched()
Get the user touched timestamp.
Definition: User.php:2645
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:320