MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
36 
47 class User implements IDBAccessObject, UserIdentity {
51  const TOKEN_LENGTH = 32;
52 
56  const INVALID_TOKEN = '*** INVALID ***';
57 
63  const EDIT_TOKEN_SUFFIX = Token::SUFFIX;
64 
68  const VERSION = 12;
69 
75 
79  const CHECK_USER_RIGHTS = true;
80 
84  const IGNORE_USER_RIGHTS = false;
85 
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 
119  protected static $mCoreRights = [
120  'apihighlimits',
121  'applychangetags',
122  'autoconfirmed',
123  'autocreateaccount',
124  'autopatrol',
125  'bigdelete',
126  'block',
127  'blockemail',
128  'bot',
129  'browsearchive',
130  'changetags',
131  'createaccount',
132  'createpage',
133  'createtalk',
134  'delete',
135  'deletechangetags',
136  'deletedhistory',
137  'deletedtext',
138  'deletelogentry',
139  'deleterevision',
140  'edit',
141  'editcontentmodel',
142  'editinterface',
143  'editprotected',
144  'editmyoptions',
145  'editmyprivateinfo',
146  'editmyusercss',
147  'editmyuserjson',
148  'editmyuserjs',
149  'editmywatchlist',
150  'editsemiprotected',
151  'editsitecss',
152  'editsitejson',
153  'editsitejs',
154  'editusercss',
155  'edituserjson',
156  'edituserjs',
157  'hideuser',
158  'import',
159  'importupload',
160  'ipblock-exempt',
161  'managechangetags',
162  'markbotedits',
163  'mergehistory',
164  'minoredit',
165  'move',
166  'movefile',
167  'move-categorypages',
168  'move-rootuserpages',
169  'move-subpages',
170  'nominornewtalk',
171  'noratelimit',
172  'override-export-depth',
173  'pagelang',
174  'patrol',
175  'patrolmarks',
176  'protect',
177  'purge',
178  'read',
179  'reupload',
180  'reupload-own',
181  'reupload-shared',
182  'rollback',
183  'sendemail',
184  'siteadmin',
185  'suppressionlog',
186  'suppressredirect',
187  'suppressrevision',
188  'unblockself',
189  'undelete',
190  'unwatchedpages',
191  'upload',
192  'upload_by_url',
193  'userrights',
194  'userrights-interwiki',
195  'viewmyprivateinfo',
196  'viewmywatchlist',
197  'viewsuppressed',
198  'writeapi',
199  ];
200 
204  protected static $mAllRights = false;
205 
207  // @{
209  public $mId;
211  public $mName;
213  protected $mActorId;
215  public $mRealName;
216 
218  public $mEmail;
220  public $mTouched;
222  protected $mQuickTouched;
224  protected $mToken;
228  protected $mEmailToken;
232  protected $mRegistration;
234  protected $mEditCount;
238  protected $mOptionOverrides;
239  // @}
240 
244  // @{
246 
250  protected $mLoadedItems = [];
251  // @}
252 
263  public $mFrom;
264 
268  protected $mNewtalk;
270  protected $mDatePreference;
272  public $mBlockedby;
274  protected $mHash;
276  public $mRights;
278  protected $mBlockreason;
280  protected $mEffectiveGroups;
282  protected $mImplicitGroups;
284  protected $mFormerGroups;
286  protected $mGlobalBlock;
288  protected $mLocked;
290  public $mHideName;
292  public $mOptions;
293 
295  private $mRequest;
296 
298  public $mBlock;
299 
301  protected $mAllowUsertalk;
302 
304  private $mBlockedFromCreateAccount = false;
305 
307  protected $queryFlagsUsed = self::READ_NORMAL;
308 
309  public static $idCacheByName = [];
310 
322  public function __construct() {
323  $this->clearInstanceCache( 'defaults' );
324  }
325 
329  public function __toString() {
330  return (string)$this->getName();
331  }
332 
347  public function isSafeToLoad() {
348  global $wgFullyInitialised;
349 
350  // The user is safe to load if:
351  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
352  // * mLoadedItems === true (already loaded)
353  // * mFrom !== 'session' (sessions not involved at all)
354 
355  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
356  $this->mLoadedItems === true || $this->mFrom !== 'session';
357  }
358 
364  public function load( $flags = self::READ_NORMAL ) {
365  global $wgFullyInitialised;
366 
367  if ( $this->mLoadedItems === true ) {
368  return;
369  }
370 
371  // Set it now to avoid infinite recursion in accessors
372  $oldLoadedItems = $this->mLoadedItems;
373  $this->mLoadedItems = true;
374  $this->queryFlagsUsed = $flags;
375 
376  // If this is called too early, things are likely to break.
377  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
379  ->warning( 'User::loadFromSession called before the end of Setup.php', [
380  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
381  ] );
382  $this->loadDefaults();
383  $this->mLoadedItems = $oldLoadedItems;
384  return;
385  }
386 
387  switch ( $this->mFrom ) {
388  case 'defaults':
389  $this->loadDefaults();
390  break;
391  case 'name':
392  // Make sure this thread sees its own changes
393  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
394  if ( $lb->hasOrMadeRecentMasterChanges() ) {
395  $flags |= self::READ_LATEST;
396  $this->queryFlagsUsed = $flags;
397  }
398 
399  $this->mId = self::idFromName( $this->mName, $flags );
400  if ( !$this->mId ) {
401  // Nonexistent user placeholder object
402  $this->loadDefaults( $this->mName );
403  } else {
404  $this->loadFromId( $flags );
405  }
406  break;
407  case 'id':
408  // Make sure this thread sees its own changes, if the ID isn't 0
409  if ( $this->mId != 0 ) {
410  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
411  if ( $lb->hasOrMadeRecentMasterChanges() ) {
412  $flags |= self::READ_LATEST;
413  $this->queryFlagsUsed = $flags;
414  }
415  }
416 
417  $this->loadFromId( $flags );
418  break;
419  case 'actor':
420  // Make sure this thread sees its own changes
421  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
422  if ( $lb->hasOrMadeRecentMasterChanges() ) {
423  $flags |= self::READ_LATEST;
424  $this->queryFlagsUsed = $flags;
425  }
426 
427  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
428  $row = wfGetDB( $index )->selectRow(
429  'actor',
430  [ 'actor_user', 'actor_name' ],
431  [ 'actor_id' => $this->mActorId ],
432  __METHOD__,
433  $options
434  );
435 
436  if ( !$row ) {
437  // Ugh.
438  $this->loadDefaults();
439  } elseif ( $row->actor_user ) {
440  $this->mId = $row->actor_user;
441  $this->loadFromId( $flags );
442  } else {
443  $this->loadDefaults( $row->actor_name );
444  }
445  break;
446  case 'session':
447  if ( !$this->loadFromSession() ) {
448  // Loading from session failed. Load defaults.
449  $this->loadDefaults();
450  }
451  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
452  break;
453  default:
454  throw new UnexpectedValueException(
455  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
456  }
457  }
458 
464  public function loadFromId( $flags = self::READ_NORMAL ) {
465  if ( $this->mId == 0 ) {
466  // Anonymous users are not in the database (don't need cache)
467  $this->loadDefaults();
468  return false;
469  }
470 
471  // Try cache (unless this needs data from the master DB).
472  // NOTE: if this thread called saveSettings(), the cache was cleared.
473  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
474  if ( $latest ) {
475  if ( !$this->loadFromDatabase( $flags ) ) {
476  // Can't load from ID
477  return false;
478  }
479  } else {
480  $this->loadFromCache();
481  }
482 
483  $this->mLoadedItems = true;
484  $this->queryFlagsUsed = $flags;
485 
486  return true;
487  }
488 
494  public static function purge( $wikiId, $userId ) {
495  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
496  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
497  $cache->delete( $key );
498  }
499 
505  protected function getCacheKey( WANObjectCache $cache ) {
506  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
507  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
508 
509  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
510  }
511 
518  $id = $this->getId();
519 
520  return $id ? [ $this->getCacheKey( $cache ) ] : [];
521  }
522 
529  protected function loadFromCache() {
531  $data = $cache->getWithSetCallback(
532  $this->getCacheKey( $cache ),
533  $cache::TTL_HOUR,
534  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
535  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
536  wfDebug( "User: cache miss for user {$this->mId}\n" );
537 
538  $this->loadFromDatabase( self::READ_NORMAL );
539  $this->loadGroups();
540  $this->loadOptions();
541 
542  $data = [];
543  foreach ( self::$mCacheVars as $name ) {
544  $data[$name] = $this->$name;
545  }
546 
547  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
548 
549  // if a user group membership is about to expire, the cache needs to
550  // expire at that time (T163691)
551  foreach ( $this->mGroupMemberships as $ugm ) {
552  if ( $ugm->getExpiry() ) {
553  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
554  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
555  $ttl = $secondsUntilExpiry;
556  }
557  }
558  }
559 
560  return $data;
561  },
562  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
563  );
564 
565  // Restore from cache
566  foreach ( self::$mCacheVars as $name ) {
567  $this->$name = $data[$name];
568  }
569 
570  return true;
571  }
572 
574  // @{
575 
592  public static function newFromName( $name, $validate = 'valid' ) {
593  if ( $validate === true ) {
594  $validate = 'valid';
595  }
596  $name = self::getCanonicalName( $name, $validate );
597  if ( $name === false ) {
598  return false;
599  } else {
600  // Create unloaded user object
601  $u = new User;
602  $u->mName = $name;
603  $u->mFrom = 'name';
604  $u->setItemLoaded( 'name' );
605  return $u;
606  }
607  }
608 
615  public static function newFromId( $id ) {
616  $u = new User;
617  $u->mId = $id;
618  $u->mFrom = 'id';
619  $u->setItemLoaded( 'id' );
620  return $u;
621  }
622 
630  public static function newFromActorId( $id ) {
632 
633  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
634  // but it does little harm and might be needed for write callers loading a User.
635  if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) ) {
636  throw new BadMethodCallException(
637  'Cannot use ' . __METHOD__
638  . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
639  );
640  }
641 
642  $u = new User;
643  $u->mActorId = $id;
644  $u->mFrom = 'actor';
645  $u->setItemLoaded( 'actor' );
646  return $u;
647  }
648 
658  public static function newFromIdentity( UserIdentity $identity ) {
659  if ( $identity instanceof User ) {
660  return $identity;
661  }
662 
663  return self::newFromAnyId(
664  $identity->getId() === 0 ? null : $identity->getId(),
665  $identity->getName() === '' ? null : $identity->getName(),
666  $identity->getActorId() === 0 ? null : $identity->getActorId()
667  );
668  }
669 
682  public static function newFromAnyId( $userId, $userName, $actorId ) {
684 
685  $user = new User;
686  $user->mFrom = 'defaults';
687 
688  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
689  // but it does little harm and might be needed for write callers loading a User.
690  if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
691  $user->mActorId = (int)$actorId;
692  if ( $user->mActorId !== 0 ) {
693  $user->mFrom = 'actor';
694  }
695  $user->setItemLoaded( 'actor' );
696  }
697 
698  if ( $userName !== null && $userName !== '' ) {
699  $user->mName = $userName;
700  $user->mFrom = 'name';
701  $user->setItemLoaded( 'name' );
702  }
703 
704  if ( $userId !== null ) {
705  $user->mId = (int)$userId;
706  if ( $user->mId !== 0 ) {
707  $user->mFrom = 'id';
708  }
709  $user->setItemLoaded( 'id' );
710  }
711 
712  if ( $user->mFrom === 'defaults' ) {
713  throw new InvalidArgumentException(
714  'Cannot create a user with no name, no ID, and no actor ID'
715  );
716  }
717 
718  return $user;
719  }
720 
732  public static function newFromConfirmationCode( $code, $flags = 0 ) {
733  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
734  ? wfGetDB( DB_MASTER )
735  : wfGetDB( DB_REPLICA );
736 
737  $id = $db->selectField(
738  'user',
739  'user_id',
740  [
741  'user_email_token' => md5( $code ),
742  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
743  ]
744  );
745 
746  return $id ? self::newFromId( $id ) : null;
747  }
748 
756  public static function newFromSession( WebRequest $request = null ) {
757  $user = new User;
758  $user->mFrom = 'session';
759  $user->mRequest = $request;
760  return $user;
761  }
762 
778  public static function newFromRow( $row, $data = null ) {
779  $user = new User;
780  $user->loadFromRow( $row, $data );
781  return $user;
782  }
783 
819  public static function newSystemUser( $name, $options = [] ) {
820  $options += [
821  'validate' => 'valid',
822  'create' => true,
823  'steal' => false,
824  ];
825 
826  $name = self::getCanonicalName( $name, $options['validate'] );
827  if ( $name === false ) {
828  return null;
829  }
830 
831  $dbr = wfGetDB( DB_REPLICA );
832  $userQuery = self::getQueryInfo();
833  $row = $dbr->selectRow(
834  $userQuery['tables'],
835  $userQuery['fields'],
836  [ 'user_name' => $name ],
837  __METHOD__,
838  [],
839  $userQuery['joins']
840  );
841  if ( !$row ) {
842  // Try the master database...
843  $dbw = wfGetDB( DB_MASTER );
844  $row = $dbw->selectRow(
845  $userQuery['tables'],
846  $userQuery['fields'],
847  [ 'user_name' => $name ],
848  __METHOD__,
849  [],
850  $userQuery['joins']
851  );
852  }
853 
854  if ( !$row ) {
855  // No user. Create it?
856  return $options['create']
857  ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
858  : null;
859  }
860 
861  $user = self::newFromRow( $row );
862 
863  // A user is considered to exist as a non-system user if it can
864  // authenticate, or has an email set, or has a non-invalid token.
865  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
866  AuthManager::singleton()->userCanAuthenticate( $name )
867  ) {
868  // User exists. Steal it?
869  if ( !$options['steal'] ) {
870  return null;
871  }
872 
873  AuthManager::singleton()->revokeAccessForUser( $name );
874 
875  $user->invalidateEmail();
876  $user->mToken = self::INVALID_TOKEN;
877  $user->saveSettings();
878  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
879  }
880 
881  return $user;
882  }
883 
884  // @}
885 
891  public static function whoIs( $id ) {
892  return UserCache::singleton()->getProp( $id, 'name' );
893  }
894 
901  public static function whoIsReal( $id ) {
902  return UserCache::singleton()->getProp( $id, 'real_name' );
903  }
904 
911  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
912  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
913  $name = (string)$name;
915  if ( is_null( $nt ) ) {
916  // Illegal name
917  return null;
918  }
919 
920  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
921  return self::$idCacheByName[$name];
922  }
923 
924  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
925  $db = wfGetDB( $index );
926 
927  $s = $db->selectRow(
928  'user',
929  [ 'user_id' ],
930  [ 'user_name' => $nt->getText() ],
931  __METHOD__,
932  $options
933  );
934 
935  if ( $s === false ) {
936  $result = null;
937  } else {
938  $result = $s->user_id;
939  }
940 
941  self::$idCacheByName[$name] = $result;
942 
943  if ( count( self::$idCacheByName ) > 1000 ) {
944  self::$idCacheByName = [];
945  }
946 
947  return $result;
948  }
949 
953  public static function resetIdByNameCache() {
954  self::$idCacheByName = [];
955  }
956 
973  public static function isIP( $name ) {
974  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
975  || IP::isIPv6( $name );
976  }
977 
984  public function isIPRange() {
985  return IP::isValidRange( $this->mName );
986  }
987 
999  public static function isValidUserName( $name ) {
1000  global $wgMaxNameChars;
1001 
1002  if ( $name == ''
1003  || self::isIP( $name )
1004  || strpos( $name, '/' ) !== false
1005  || strlen( $name ) > $wgMaxNameChars
1006  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
1007  ) {
1008  return false;
1009  }
1010 
1011  // Ensure that the name can't be misresolved as a different title,
1012  // such as with extra namespace keys at the start.
1013  $parsed = Title::newFromText( $name );
1014  if ( is_null( $parsed )
1015  || $parsed->getNamespace()
1016  || strcmp( $name, $parsed->getPrefixedText() ) ) {
1017  return false;
1018  }
1019 
1020  // Check an additional blacklist of troublemaker characters.
1021  // Should these be merged into the title char list?
1022  $unicodeBlacklist = '/[' .
1023  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
1024  '\x{00a0}' . # non-breaking space
1025  '\x{2000}-\x{200f}' . # various whitespace
1026  '\x{2028}-\x{202f}' . # breaks and control chars
1027  '\x{3000}' . # ideographic space
1028  '\x{e000}-\x{f8ff}' . # private use
1029  ']/u';
1030  if ( preg_match( $unicodeBlacklist, $name ) ) {
1031  return false;
1032  }
1033 
1034  return true;
1035  }
1036 
1048  public static function isUsableName( $name ) {
1049  global $wgReservedUsernames;
1050  // Must be a valid username, obviously ;)
1051  if ( !self::isValidUserName( $name ) ) {
1052  return false;
1053  }
1054 
1055  static $reservedUsernames = false;
1056  if ( !$reservedUsernames ) {
1057  $reservedUsernames = $wgReservedUsernames;
1058  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
1059  }
1060 
1061  // Certain names may be reserved for batch processes.
1062  foreach ( $reservedUsernames as $reserved ) {
1063  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1064  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1065  }
1066  if ( $reserved == $name ) {
1067  return false;
1068  }
1069  }
1070  return true;
1071  }
1072 
1083  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1084  if ( $groups === [] ) {
1085  return UserArrayFromResult::newFromIDs( [] );
1086  }
1087 
1088  $groups = array_unique( (array)$groups );
1089  $limit = min( 5000, $limit );
1090 
1091  $conds = [ 'ug_group' => $groups ];
1092  if ( $after !== null ) {
1093  $conds[] = 'ug_user > ' . (int)$after;
1094  }
1095 
1096  $dbr = wfGetDB( DB_REPLICA );
1097  $ids = $dbr->selectFieldValues(
1098  'user_groups',
1099  'ug_user',
1100  $conds,
1101  __METHOD__,
1102  [
1103  'DISTINCT' => true,
1104  'ORDER BY' => 'ug_user',
1105  'LIMIT' => $limit,
1106  ]
1107  ) ?: [];
1108  return UserArray::newFromIDs( $ids );
1109  }
1110 
1123  public static function isCreatableName( $name ) {
1125 
1126  // Ensure that the username isn't longer than 235 bytes, so that
1127  // (at least for the builtin skins) user javascript and css files
1128  // will work. (T25080)
1129  if ( strlen( $name ) > 235 ) {
1130  wfDebugLog( 'username', __METHOD__ .
1131  ": '$name' invalid due to length" );
1132  return false;
1133  }
1134 
1135  // Preg yells if you try to give it an empty string
1136  if ( $wgInvalidUsernameCharacters !== '' ) {
1137  if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
1138  wfDebugLog( 'username', __METHOD__ .
1139  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1140  return false;
1141  }
1142  }
1143 
1144  return self::isUsableName( $name );
1145  }
1146 
1153  public function isValidPassword( $password ) {
1154  // simple boolean wrapper for getPasswordValidity
1155  return $this->getPasswordValidity( $password ) === true;
1156  }
1157 
1164  public function getPasswordValidity( $password ) {
1165  $result = $this->checkPasswordValidity( $password );
1166  if ( $result->isGood() ) {
1167  return true;
1168  } else {
1169  $messages = [];
1170  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1171  $messages[] = $error['message'];
1172  }
1173  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1174  $messages[] = $warning['message'];
1175  }
1176  if ( count( $messages ) === 1 ) {
1177  return $messages[0];
1178  }
1179  return $messages;
1180  }
1181  }
1182 
1200  public function checkPasswordValidity( $password ) {
1201  global $wgPasswordPolicy;
1202 
1203  $upp = new UserPasswordPolicy(
1204  $wgPasswordPolicy['policies'],
1205  $wgPasswordPolicy['checks']
1206  );
1207 
1209  $result = false; // init $result to false for the internal checks
1210 
1211  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1212  $status->error( $result );
1213  return $status;
1214  }
1215 
1216  if ( $result === false ) {
1217  $status->merge( $upp->checkUserPassword( $this, $password ) );
1218  return $status;
1219  } elseif ( $result === true ) {
1220  return $status;
1221  } else {
1222  $status->error( $result );
1223  return $status; // the isValidPassword hook set a string $result and returned true
1224  }
1225  }
1226 
1240  public static function getCanonicalName( $name, $validate = 'valid' ) {
1241  // Force usernames to capital
1242  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1243 
1244  # Reject names containing '#'; these will be cleaned up
1245  # with title normalisation, but then it's too late to
1246  # check elsewhere
1247  if ( strpos( $name, '#' ) !== false ) {
1248  return false;
1249  }
1250 
1251  // Clean up name according to title rules,
1252  // but only when validation is requested (T14654)
1253  $t = ( $validate !== false ) ?
1255  // Check for invalid titles
1256  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1257  return false;
1258  }
1259 
1260  // Reject various classes of invalid names
1261  $name = AuthManager::callLegacyAuthPlugin(
1262  'getCanonicalName', [ $t->getText() ], $t->getText()
1263  );
1264 
1265  switch ( $validate ) {
1266  case false:
1267  break;
1268  case 'valid':
1269  if ( !self::isValidUserName( $name ) ) {
1270  $name = false;
1271  }
1272  break;
1273  case 'usable':
1274  if ( !self::isUsableName( $name ) ) {
1275  $name = false;
1276  }
1277  break;
1278  case 'creatable':
1279  if ( !self::isCreatableName( $name ) ) {
1280  $name = false;
1281  }
1282  break;
1283  default:
1284  throw new InvalidArgumentException(
1285  'Invalid parameter value for $validate in ' . __METHOD__ );
1286  }
1287  return $name;
1288  }
1289 
1296  public static function randomPassword() {
1297  global $wgMinimalPasswordLength;
1298  return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1299  }
1300 
1309  public function loadDefaults( $name = false ) {
1310  $this->mId = 0;
1311  $this->mName = $name;
1312  $this->mActorId = null;
1313  $this->mRealName = '';
1314  $this->mEmail = '';
1315  $this->mOptionOverrides = null;
1316  $this->mOptionsLoaded = false;
1317 
1318  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1319  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1320  if ( $loggedOut !== 0 ) {
1321  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1322  } else {
1323  $this->mTouched = '1'; # Allow any pages to be cached
1324  }
1325 
1326  $this->mToken = null; // Don't run cryptographic functions till we need a token
1327  $this->mEmailAuthenticated = null;
1328  $this->mEmailToken = '';
1329  $this->mEmailTokenExpires = null;
1330  $this->mRegistration = wfTimestamp( TS_MW );
1331  $this->mGroupMemberships = [];
1332 
1333  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1334  }
1335 
1348  public function isItemLoaded( $item, $all = 'all' ) {
1349  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1350  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1351  }
1352 
1358  protected function setItemLoaded( $item ) {
1359  if ( is_array( $this->mLoadedItems ) ) {
1360  $this->mLoadedItems[$item] = true;
1361  }
1362  }
1363 
1369  private function loadFromSession() {
1370  // Deprecated hook
1371  $result = null;
1372  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1373  if ( $result !== null ) {
1374  return $result;
1375  }
1376 
1377  // MediaWiki\Session\Session already did the necessary authentication of the user
1378  // returned here, so just use it if applicable.
1379  $session = $this->getRequest()->getSession();
1380  $user = $session->getUser();
1381  if ( $user->isLoggedIn() ) {
1382  $this->loadFromUserObject( $user );
1383  if ( $user->isBlocked() ) {
1384  // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1385  // every session load, because an autoblocked editor might not edit again from the same
1386  // IP address after being blocked.
1387  $this->trackBlockWithCookie();
1388  }
1389 
1390  // Other code expects these to be set in the session, so set them.
1391  $session->set( 'wsUserID', $this->getId() );
1392  $session->set( 'wsUserName', $this->getName() );
1393  $session->set( 'wsToken', $this->getToken() );
1394 
1395  return true;
1396  }
1397 
1398  return false;
1399  }
1400 
1404  public function trackBlockWithCookie() {
1405  $block = $this->getBlock();
1406  if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null ) {
1407  $config = RequestContext::getMain()->getConfig();
1408  $shouldSetCookie = false;
1409 
1410  if ( $this->isAnon() && $config->get( 'CookieSetOnIpBlock' ) ) {
1411  // If user is logged-out, set a cookie to track the Block
1412  $shouldSetCookie = in_array( $block->getType(), [
1414  ] );
1415  if ( $shouldSetCookie ) {
1416  $block->setCookie( $this->getRequest()->response() );
1417 
1418  // temporary measure the use of cookies on ip blocks
1419  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1420  $stats->increment( 'block.ipblock.setCookie.success' );
1421  }
1422  } elseif ( $this->isLoggedIn() && $config->get( 'CookieSetOnAutoblock' ) ) {
1423  $shouldSetCookie = $block->getType() === Block::TYPE_USER && $block->isAutoblocking();
1424  if ( $shouldSetCookie ) {
1425  $block->setCookie( $this->getRequest()->response() );
1426  }
1427  }
1428  }
1429  }
1430 
1438  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1439  // Paranoia
1440  $this->mId = intval( $this->mId );
1441 
1442  if ( !$this->mId ) {
1443  // Anonymous users are not in the database
1444  $this->loadDefaults();
1445  return false;
1446  }
1447 
1448  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1449  $db = wfGetDB( $index );
1450 
1451  $userQuery = self::getQueryInfo();
1452  $s = $db->selectRow(
1453  $userQuery['tables'],
1454  $userQuery['fields'],
1455  [ 'user_id' => $this->mId ],
1456  __METHOD__,
1457  $options,
1458  $userQuery['joins']
1459  );
1460 
1461  $this->queryFlagsUsed = $flags;
1462  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1463 
1464  if ( $s !== false ) {
1465  // Initialise user table data
1466  $this->loadFromRow( $s );
1467  $this->mGroupMemberships = null; // deferred
1468  $this->getEditCount(); // revalidation for nulls
1469  return true;
1470  } else {
1471  // Invalid user_id
1472  $this->mId = 0;
1473  $this->loadDefaults();
1474  return false;
1475  }
1476  }
1477 
1490  protected function loadFromRow( $row, $data = null ) {
1492 
1493  if ( !is_object( $row ) ) {
1494  throw new InvalidArgumentException( '$row must be an object' );
1495  }
1496 
1497  $all = true;
1498 
1499  $this->mGroupMemberships = null; // deferred
1500 
1501  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
1502  // but it does little harm and might be needed for write callers loading a User.
1503  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
1504  if ( isset( $row->actor_id ) ) {
1505  $this->mActorId = (int)$row->actor_id;
1506  if ( $this->mActorId !== 0 ) {
1507  $this->mFrom = 'actor';
1508  }
1509  $this->setItemLoaded( 'actor' );
1510  } else {
1511  $all = false;
1512  }
1513  }
1514 
1515  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1516  $this->mName = $row->user_name;
1517  $this->mFrom = 'name';
1518  $this->setItemLoaded( 'name' );
1519  } else {
1520  $all = false;
1521  }
1522 
1523  if ( isset( $row->user_real_name ) ) {
1524  $this->mRealName = $row->user_real_name;
1525  $this->setItemLoaded( 'realname' );
1526  } else {
1527  $all = false;
1528  }
1529 
1530  if ( isset( $row->user_id ) ) {
1531  $this->mId = intval( $row->user_id );
1532  if ( $this->mId !== 0 ) {
1533  $this->mFrom = 'id';
1534  }
1535  $this->setItemLoaded( 'id' );
1536  } else {
1537  $all = false;
1538  }
1539 
1540  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1541  self::$idCacheByName[$row->user_name] = $row->user_id;
1542  }
1543 
1544  if ( isset( $row->user_editcount ) ) {
1545  $this->mEditCount = $row->user_editcount;
1546  } else {
1547  $all = false;
1548  }
1549 
1550  if ( isset( $row->user_touched ) ) {
1551  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1552  } else {
1553  $all = false;
1554  }
1555 
1556  if ( isset( $row->user_token ) ) {
1557  // The definition for the column is binary(32), so trim the NULs
1558  // that appends. The previous definition was char(32), so trim
1559  // spaces too.
1560  $this->mToken = rtrim( $row->user_token, " \0" );
1561  if ( $this->mToken === '' ) {
1562  $this->mToken = null;
1563  }
1564  } else {
1565  $all = false;
1566  }
1567 
1568  if ( isset( $row->user_email ) ) {
1569  $this->mEmail = $row->user_email;
1570  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1571  $this->mEmailToken = $row->user_email_token;
1572  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1573  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1574  } else {
1575  $all = false;
1576  }
1577 
1578  if ( $all ) {
1579  $this->mLoadedItems = true;
1580  }
1581 
1582  if ( is_array( $data ) ) {
1583  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1584  if ( !count( $data['user_groups'] ) ) {
1585  $this->mGroupMemberships = [];
1586  } else {
1587  $firstGroup = reset( $data['user_groups'] );
1588  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1589  $this->mGroupMemberships = [];
1590  foreach ( $data['user_groups'] as $row ) {
1591  $ugm = UserGroupMembership::newFromRow( (object)$row );
1592  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1593  }
1594  }
1595  }
1596  }
1597  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1598  $this->loadOptions( $data['user_properties'] );
1599  }
1600  }
1601  }
1602 
1608  protected function loadFromUserObject( $user ) {
1609  $user->load();
1610  foreach ( self::$mCacheVars as $var ) {
1611  $this->$var = $user->$var;
1612  }
1613  }
1614 
1618  private function loadGroups() {
1619  if ( is_null( $this->mGroupMemberships ) ) {
1620  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1621  ? wfGetDB( DB_MASTER )
1622  : wfGetDB( DB_REPLICA );
1623  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1624  $this->mId, $db );
1625  }
1626  }
1627 
1642  public function addAutopromoteOnceGroups( $event ) {
1644 
1645  if ( wfReadOnly() || !$this->getId() ) {
1646  return [];
1647  }
1648 
1649  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1650  if ( !count( $toPromote ) ) {
1651  return [];
1652  }
1653 
1654  if ( !$this->checkAndSetTouched() ) {
1655  return []; // raced out (bug T48834)
1656  }
1657 
1658  $oldGroups = $this->getGroups(); // previous groups
1659  $oldUGMs = $this->getGroupMemberships();
1660  foreach ( $toPromote as $group ) {
1661  $this->addGroup( $group );
1662  }
1663  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1664  $newUGMs = $this->getGroupMemberships();
1665 
1666  // update groups in external authentication database
1667  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1668  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1669 
1670  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1671  $logEntry->setPerformer( $this );
1672  $logEntry->setTarget( $this->getUserPage() );
1673  $logEntry->setParameters( [
1674  '4::oldgroups' => $oldGroups,
1675  '5::newgroups' => $newGroups,
1676  ] );
1677  $logid = $logEntry->insert();
1678  if ( $wgAutopromoteOnceLogInRC ) {
1679  $logEntry->publish( $logid );
1680  }
1681 
1682  return $toPromote;
1683  }
1684 
1694  protected function makeUpdateConditions( Database $db, array $conditions ) {
1695  if ( $this->mTouched ) {
1696  // CAS check: only update if the row wasn't changed sicne it was loaded.
1697  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1698  }
1699 
1700  return $conditions;
1701  }
1702 
1712  protected function checkAndSetTouched() {
1713  $this->load();
1714 
1715  if ( !$this->mId ) {
1716  return false; // anon
1717  }
1718 
1719  // Get a new user_touched that is higher than the old one
1720  $newTouched = $this->newTouchedTimestamp();
1721 
1722  $dbw = wfGetDB( DB_MASTER );
1723  $dbw->update( 'user',
1724  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1725  $this->makeUpdateConditions( $dbw, [
1726  'user_id' => $this->mId,
1727  ] ),
1728  __METHOD__
1729  );
1730  $success = ( $dbw->affectedRows() > 0 );
1731 
1732  if ( $success ) {
1733  $this->mTouched = $newTouched;
1734  $this->clearSharedCache();
1735  } else {
1736  // Clears on failure too since that is desired if the cache is stale
1737  $this->clearSharedCache( 'refresh' );
1738  }
1739 
1740  return $success;
1741  }
1742 
1750  public function clearInstanceCache( $reloadFrom = false ) {
1751  $this->mNewtalk = -1;
1752  $this->mDatePreference = null;
1753  $this->mBlockedby = -1; # Unset
1754  $this->mHash = false;
1755  $this->mRights = null;
1756  $this->mEffectiveGroups = null;
1757  $this->mImplicitGroups = null;
1758  $this->mGroupMemberships = null;
1759  $this->mOptions = null;
1760  $this->mOptionsLoaded = false;
1761  $this->mEditCount = null;
1762 
1763  if ( $reloadFrom ) {
1764  $this->mLoadedItems = [];
1765  $this->mFrom = $reloadFrom;
1766  }
1767  }
1768 
1775  public static function getDefaultOptions() {
1777 
1778  static $defOpt = null;
1779  static $defOptLang = null;
1780 
1781  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1782  if ( $defOpt !== null && $defOptLang === $contLang->getCode() ) {
1783  // The content language does not change (and should not change) mid-request, but the
1784  // unit tests change it anyway, and expect this method to return values relevant to the
1785  // current content language.
1786  return $defOpt;
1787  }
1788 
1789  $defOpt = $wgDefaultUserOptions;
1790  // Default language setting
1791  $defOptLang = $contLang->getCode();
1792  $defOpt['language'] = $defOptLang;
1793  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1794  if ( $langCode === $contLang->getCode() ) {
1795  $defOpt['variant'] = $langCode;
1796  } else {
1797  $defOpt["variant-$langCode"] = $langCode;
1798  }
1799  }
1800 
1801  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1802  // since extensions may change the set of searchable namespaces depending
1803  // on user groups/permissions.
1804  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1805  $defOpt['searchNs' . $nsnum] = (bool)$val;
1806  }
1807  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1808 
1809  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1810 
1811  return $defOpt;
1812  }
1813 
1820  public static function getDefaultOption( $opt ) {
1821  $defOpts = self::getDefaultOptions();
1822  return $defOpts[$opt] ?? null;
1823  }
1824 
1831  private function getBlockedStatus( $bFromReplica = true ) {
1833 
1834  if ( -1 != $this->mBlockedby ) {
1835  return;
1836  }
1837 
1838  wfDebug( __METHOD__ . ": checking...\n" );
1839 
1840  // Initialize data...
1841  // Otherwise something ends up stomping on $this->mBlockedby when
1842  // things get lazy-loaded later, causing false positive block hits
1843  // due to -1 !== 0. Probably session-related... Nothing should be
1844  // overwriting mBlockedby, surely?
1845  $this->load();
1846 
1847  # We only need to worry about passing the IP address to the Block generator if the
1848  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1849  # know which IP address they're actually coming from
1850  $ip = null;
1851  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1852  // $wgUser->getName() only works after the end of Setup.php. Until
1853  // then, assume it's a logged-out user.
1854  $globalUserName = $wgUser->isSafeToLoad()
1855  ? $wgUser->getName()
1856  : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1857  if ( $this->getName() === $globalUserName ) {
1858  $ip = $this->getRequest()->getIP();
1859  }
1860  }
1861 
1862  // User/IP blocking
1863  $block = Block::newFromTarget( $this, $ip, !$bFromReplica );
1864 
1865  // Cookie blocking
1866  if ( !$block instanceof Block ) {
1867  $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
1868  }
1869 
1870  // Proxy blocking
1871  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1872  // Local list
1873  if ( self::isLocallyBlockedProxy( $ip ) ) {
1874  $block = new Block( [
1875  'byText' => wfMessage( 'proxyblocker' )->text(),
1876  'reason' => wfMessage( 'proxyblockreason' )->plain(),
1877  'address' => $ip,
1878  'systemBlock' => 'proxy',
1879  ] );
1880  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1881  $block = new Block( [
1882  'byText' => wfMessage( 'sorbs' )->text(),
1883  'reason' => wfMessage( 'sorbsreason' )->plain(),
1884  'address' => $ip,
1885  'systemBlock' => 'dnsbl',
1886  ] );
1887  }
1888  }
1889 
1890  // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
1891  if ( !$block instanceof Block
1892  && $wgApplyIpBlocksToXff
1893  && $ip !== null
1894  && !in_array( $ip, $wgProxyWhitelist )
1895  ) {
1896  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1897  $xff = array_map( 'trim', explode( ',', $xff ) );
1898  $xff = array_diff( $xff, [ $ip ] );
1899  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromReplica );
1900  $block = Block::chooseBlock( $xffblocks, $xff );
1901  if ( $block instanceof Block ) {
1902  # Mangle the reason to alert the user that the block
1903  # originated from matching the X-Forwarded-For header.
1904  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->plain();
1905  }
1906  }
1907 
1908  if ( !$block instanceof Block
1909  && $ip !== null
1910  && $this->isAnon()
1911  && IP::isInRanges( $ip, $wgSoftBlockRanges )
1912  ) {
1913  $block = new Block( [
1914  'address' => $ip,
1915  'byText' => 'MediaWiki default',
1916  'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
1917  'anonOnly' => true,
1918  'systemBlock' => 'wgSoftBlockRanges',
1919  ] );
1920  }
1921 
1922  if ( $block instanceof Block ) {
1923  wfDebug( __METHOD__ . ": Found block.\n" );
1924  $this->mBlock = $block;
1925  $this->mBlockedby = $block->getByName();
1926  $this->mBlockreason = $block->mReason;
1927  $this->mHideName = $block->mHideName;
1928  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1929  } else {
1930  $this->mBlock = null;
1931  $this->mBlockedby = '';
1932  $this->mBlockreason = '';
1933  $this->mHideName = 0;
1934  $this->mAllowUsertalk = false;
1935  }
1936 
1937  // Avoid PHP 7.1 warning of passing $this by reference
1938  $user = $this;
1939  // Extensions
1940  Hooks::run( 'GetBlockedStatus', [ &$user ] );
1941  }
1942 
1948  protected function getBlockFromCookieValue( $blockCookieVal ) {
1949  // Make sure there's something to check. The cookie value must start with a number.
1950  if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
1951  return false;
1952  }
1953  // Load the Block from the ID in the cookie.
1954  $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
1955  if ( $blockCookieId !== null ) {
1956  // An ID was found in the cookie.
1957  $tmpBlock = Block::newFromID( $blockCookieId );
1958  if ( $tmpBlock instanceof Block ) {
1959  $config = RequestContext::getMain()->getConfig();
1960 
1961  switch ( $tmpBlock->getType() ) {
1962  case Block::TYPE_USER:
1963  $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
1964  $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
1965  break;
1966  case Block::TYPE_IP:
1967  case Block::TYPE_RANGE:
1968  // If block is type IP or IP range, load only if user is not logged in (T152462)
1969  $blockIsValid = !$tmpBlock->isExpired() && !$this->isLoggedIn();
1970  $useBlockCookie = ( $config->get( 'CookieSetOnIpBlock' ) === true );
1971  break;
1972  default:
1973  $blockIsValid = false;
1974  $useBlockCookie = false;
1975  }
1976 
1977  if ( $blockIsValid && $useBlockCookie ) {
1978  // Use the block.
1979  return $tmpBlock;
1980  } else {
1981  // If the block is not valid, remove the cookie.
1982  Block::clearCookie( $this->getRequest()->response() );
1983  }
1984  } else {
1985  // If the block doesn't exist, remove the cookie.
1986  Block::clearCookie( $this->getRequest()->response() );
1987  }
1988  }
1989  return false;
1990  }
1991 
1999  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
2001 
2002  if ( !$wgEnableDnsBlacklist ) {
2003  return false;
2004  }
2005 
2006  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
2007  return false;
2008  }
2009 
2010  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
2011  }
2012 
2020  public function inDnsBlacklist( $ip, $bases ) {
2021  $found = false;
2022  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
2023  if ( IP::isIPv4( $ip ) ) {
2024  // Reverse IP, T23255
2025  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
2026 
2027  foreach ( (array)$bases as $base ) {
2028  // Make hostname
2029  // If we have an access key, use that too (ProjectHoneypot, etc.)
2030  $basename = $base;
2031  if ( is_array( $base ) ) {
2032  if ( count( $base ) >= 2 ) {
2033  // Access key is 1, base URL is 0
2034  $host = "{$base[1]}.$ipReversed.{$base[0]}";
2035  } else {
2036  $host = "$ipReversed.{$base[0]}";
2037  }
2038  $basename = $base[0];
2039  } else {
2040  $host = "$ipReversed.$base";
2041  }
2042 
2043  // Send query
2044  $ipList = gethostbynamel( $host );
2045 
2046  if ( $ipList ) {
2047  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
2048  $found = true;
2049  break;
2050  } else {
2051  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
2052  }
2053  }
2054  }
2055 
2056  return $found;
2057  }
2058 
2066  public static function isLocallyBlockedProxy( $ip ) {
2067  global $wgProxyList;
2068 
2069  if ( !$wgProxyList ) {
2070  return false;
2071  }
2072 
2073  if ( !is_array( $wgProxyList ) ) {
2074  // Load values from the specified file
2075  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
2076  }
2077 
2078  $resultProxyList = [];
2079  $deprecatedIPEntries = [];
2080 
2081  // backward compatibility: move all ip addresses in keys to values
2082  foreach ( $wgProxyList as $key => $value ) {
2083  $keyIsIP = IP::isIPAddress( $key );
2084  $valueIsIP = IP::isIPAddress( $value );
2085  if ( $keyIsIP && !$valueIsIP ) {
2086  $deprecatedIPEntries[] = $key;
2087  $resultProxyList[] = $key;
2088  } elseif ( $keyIsIP && $valueIsIP ) {
2089  $deprecatedIPEntries[] = $key;
2090  $resultProxyList[] = $key;
2091  $resultProxyList[] = $value;
2092  } else {
2093  $resultProxyList[] = $value;
2094  }
2095  }
2096 
2097  if ( $deprecatedIPEntries ) {
2098  wfDeprecated(
2099  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
2100  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
2101  }
2102 
2103  $proxyListIPSet = new IPSet( $resultProxyList );
2104  return $proxyListIPSet->match( $ip );
2105  }
2106 
2112  public function isPingLimitable() {
2113  global $wgRateLimitsExcludedIPs;
2114  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
2115  // No other good way currently to disable rate limits
2116  // for specific IPs. :P
2117  // But this is a crappy hack and should die.
2118  return false;
2119  }
2120  return !$this->isAllowed( 'noratelimit' );
2121  }
2122 
2137  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
2138  // Avoid PHP 7.1 warning of passing $this by reference
2139  $user = $this;
2140  // Call the 'PingLimiter' hook
2141  $result = false;
2142  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
2143  return $result;
2144  }
2145 
2146  global $wgRateLimits;
2147  if ( !isset( $wgRateLimits[$action] ) ) {
2148  return false;
2149  }
2150 
2151  $limits = array_merge(
2152  [ '&can-bypass' => true ],
2153  $wgRateLimits[$action]
2154  );
2155 
2156  // Some groups shouldn't trigger the ping limiter, ever
2157  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
2158  return false;
2159  }
2160 
2161  $keys = [];
2162  $id = $this->getId();
2163  $userLimit = false;
2164  $isNewbie = $this->isNewbie();
2166 
2167  if ( $id == 0 ) {
2168  // limits for anons
2169  if ( isset( $limits['anon'] ) ) {
2170  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
2171  }
2172  } else {
2173  // limits for logged-in users
2174  if ( isset( $limits['user'] ) ) {
2175  $userLimit = $limits['user'];
2176  }
2177  }
2178 
2179  // limits for anons and for newbie logged-in users
2180  if ( $isNewbie ) {
2181  // ip-based limits
2182  if ( isset( $limits['ip'] ) ) {
2183  $ip = $this->getRequest()->getIP();
2184  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
2185  }
2186  // subnet-based limits
2187  if ( isset( $limits['subnet'] ) ) {
2188  $ip = $this->getRequest()->getIP();
2189  $subnet = IP::getSubnet( $ip );
2190  if ( $subnet !== false ) {
2191  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
2192  }
2193  }
2194  }
2195 
2196  // Check for group-specific permissions
2197  // If more than one group applies, use the group with the highest limit ratio (max/period)
2198  foreach ( $this->getGroups() as $group ) {
2199  if ( isset( $limits[$group] ) ) {
2200  if ( $userLimit === false
2201  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2202  ) {
2203  $userLimit = $limits[$group];
2204  }
2205  }
2206  }
2207 
2208  // limits for newbie logged-in users (override all the normal user limits)
2209  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2210  $userLimit = $limits['newbie'];
2211  }
2212 
2213  // Set the user limit key
2214  if ( $userLimit !== false ) {
2215  list( $max, $period ) = $userLimit;
2216  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2217  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2218  }
2219 
2220  // ip-based limits for all ping-limitable users
2221  if ( isset( $limits['ip-all'] ) ) {
2222  $ip = $this->getRequest()->getIP();
2223  // ignore if user limit is more permissive
2224  if ( $isNewbie || $userLimit === false
2225  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2226  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2227  }
2228  }
2229 
2230  // subnet-based limits for all ping-limitable users
2231  if ( isset( $limits['subnet-all'] ) ) {
2232  $ip = $this->getRequest()->getIP();
2233  $subnet = IP::getSubnet( $ip );
2234  if ( $subnet !== false ) {
2235  // ignore if user limit is more permissive
2236  if ( $isNewbie || $userLimit === false
2237  || $limits['ip-all'][0] / $limits['ip-all'][1]
2238  > $userLimit[0] / $userLimit[1] ) {
2239  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2240  }
2241  }
2242  }
2243 
2244  $triggered = false;
2245  foreach ( $keys as $key => $limit ) {
2246  list( $max, $period ) = $limit;
2247  $summary = "(limit $max in {$period}s)";
2248  $count = $cache->get( $key );
2249  // Already pinged?
2250  if ( $count ) {
2251  if ( $count >= $max ) {
2252  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2253  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2254  $triggered = true;
2255  } else {
2256  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2257  }
2258  } else {
2259  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2260  if ( $incrBy > 0 ) {
2261  $cache->add( $key, 0, intval( $period ) ); // first ping
2262  }
2263  }
2264  if ( $incrBy > 0 ) {
2265  $cache->incr( $key, $incrBy );
2266  }
2267  }
2268 
2269  return $triggered;
2270  }
2271 
2279  public function isBlocked( $bFromReplica = true ) {
2280  return $this->getBlock( $bFromReplica ) instanceof Block && $this->getBlock()->prevents( 'edit' );
2281  }
2282 
2289  public function getBlock( $bFromReplica = true ) {
2290  $this->getBlockedStatus( $bFromReplica );
2291  return $this->mBlock instanceof Block ? $this->mBlock : null;
2292  }
2293 
2301  public function isBlockedFrom( $title, $fromReplica = false ) {
2302  $blocked = $this->isHidden();
2303 
2304  if ( !$blocked ) {
2305  $block = $this->getBlock( $fromReplica );
2306  if ( $block ) {
2307  $blocked = $block->preventsEdit( $title );
2308  }
2309  }
2310 
2311  // only for the purpose of the hook. We really don't need this here.
2312  $allowUsertalk = $this->mAllowUsertalk;
2313 
2314  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
2315 
2316  return $blocked;
2317  }
2318 
2323  public function blockedBy() {
2324  $this->getBlockedStatus();
2325  return $this->mBlockedby;
2326  }
2327 
2332  public function blockedFor() {
2333  $this->getBlockedStatus();
2334  return $this->mBlockreason;
2335  }
2336 
2341  public function getBlockId() {
2342  $this->getBlockedStatus();
2343  return ( $this->mBlock ? $this->mBlock->getId() : false );
2344  }
2345 
2354  public function isBlockedGlobally( $ip = '' ) {
2355  return $this->getGlobalBlock( $ip ) instanceof Block;
2356  }
2357 
2368  public function getGlobalBlock( $ip = '' ) {
2369  if ( $this->mGlobalBlock !== null ) {
2370  return $this->mGlobalBlock ?: null;
2371  }
2372  // User is already an IP?
2373  if ( IP::isIPAddress( $this->getName() ) ) {
2374  $ip = $this->getName();
2375  } elseif ( !$ip ) {
2376  $ip = $this->getRequest()->getIP();
2377  }
2378  // Avoid PHP 7.1 warning of passing $this by reference
2379  $user = $this;
2380  $blocked = false;
2381  $block = null;
2382  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2383 
2384  if ( $blocked && $block === null ) {
2385  // back-compat: UserIsBlockedGlobally didn't have $block param first
2386  $block = new Block( [
2387  'address' => $ip,
2388  'systemBlock' => 'global-block'
2389  ] );
2390  }
2391 
2392  $this->mGlobalBlock = $blocked ? $block : false;
2393  return $this->mGlobalBlock ?: null;
2394  }
2395 
2401  public function isLocked() {
2402  if ( $this->mLocked !== null ) {
2403  return $this->mLocked;
2404  }
2405  // Avoid PHP 7.1 warning of passing $this by reference
2406  $user = $this;
2407  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2408  $this->mLocked = $authUser && $authUser->isLocked();
2409  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2410  return $this->mLocked;
2411  }
2412 
2418  public function isHidden() {
2419  if ( $this->mHideName !== null ) {
2420  return (bool)$this->mHideName;
2421  }
2422  $this->getBlockedStatus();
2423  if ( !$this->mHideName ) {
2424  // Avoid PHP 7.1 warning of passing $this by reference
2425  $user = $this;
2426  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2427  $this->mHideName = $authUser && $authUser->isHidden();
2428  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2429  }
2430  return (bool)$this->mHideName;
2431  }
2432 
2437  public function getId() {
2438  if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2439  // Special case, we know the user is anonymous
2440  return 0;
2441  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2442  // Don't load if this was initialized from an ID
2443  $this->load();
2444  }
2445 
2446  return (int)$this->mId;
2447  }
2448 
2453  public function setId( $v ) {
2454  $this->mId = $v;
2455  $this->clearInstanceCache( 'id' );
2456  }
2457 
2462  public function getName() {
2463  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2464  // Special case optimisation
2465  return $this->mName;
2466  } else {
2467  $this->load();
2468  if ( $this->mName === false ) {
2469  // Clean up IPs
2470  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2471  }
2472  return $this->mName;
2473  }
2474  }
2475 
2489  public function setName( $str ) {
2490  $this->load();
2491  $this->mName = $str;
2492  }
2493 
2500  public function getActorId( IDatabase $dbw = null ) {
2502 
2503  // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
2504  // but it does little harm and might be needed for write callers loading a User.
2505  if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
2506  return 0;
2507  }
2508 
2509  if ( !$this->isItemLoaded( 'actor' ) ) {
2510  $this->load();
2511  }
2512 
2513  // Currently $this->mActorId might be null if $this was loaded from a
2514  // cache entry that was written when $wgActorTableSchemaMigrationStage
2515  // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
2516  // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
2517  // has been removed), that condition may be removed.
2518  if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2519  $q = [
2520  'actor_user' => $this->getId() ?: null,
2521  'actor_name' => (string)$this->getName(),
2522  ];
2523  if ( $dbw ) {
2524  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2525  throw new CannotCreateActorException(
2526  'Cannot create an actor for a usable name that is not an existing user'
2527  );
2528  }
2529  if ( $q['actor_name'] === '' ) {
2530  throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2531  }
2532  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2533  if ( $dbw->affectedRows() ) {
2534  $this->mActorId = (int)$dbw->insertId();
2535  } else {
2536  // Outdated cache?
2537  list( , $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2538  $this->mActorId = (int)$dbw->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2539  if ( !$this->mActorId ) {
2540  throw new CannotCreateActorException(
2541  "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2542  );
2543  }
2544  }
2545  $this->invalidateCache();
2546  } else {
2547  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2548  $db = wfGetDB( $index );
2549  $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2550  }
2551  $this->setItemLoaded( 'actor' );
2552  }
2553 
2554  return (int)$this->mActorId;
2555  }
2556 
2561  public function getTitleKey() {
2562  return str_replace( ' ', '_', $this->getName() );
2563  }
2564 
2569  public function getNewtalk() {
2570  $this->load();
2571 
2572  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2573  if ( $this->mNewtalk === -1 ) {
2574  $this->mNewtalk = false; # reset talk page status
2575 
2576  // Check memcached separately for anons, who have no
2577  // entire User object stored in there.
2578  if ( !$this->mId ) {
2579  global $wgDisableAnonTalk;
2580  if ( $wgDisableAnonTalk ) {
2581  // Anon newtalk disabled by configuration.
2582  $this->mNewtalk = false;
2583  } else {
2584  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2585  }
2586  } else {
2587  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2588  }
2589  }
2590 
2591  return (bool)$this->mNewtalk;
2592  }
2593 
2607  public function getNewMessageLinks() {
2608  // Avoid PHP 7.1 warning of passing $this by reference
2609  $user = $this;
2610  $talks = [];
2611  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2612  return $talks;
2613  } elseif ( !$this->getNewtalk() ) {
2614  return [];
2615  }
2616  $utp = $this->getTalkPage();
2617  $dbr = wfGetDB( DB_REPLICA );
2618  // Get the "last viewed rev" timestamp from the oldest message notification
2619  $timestamp = $dbr->selectField( 'user_newtalk',
2620  'MIN(user_last_timestamp)',
2621  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2622  __METHOD__ );
2623  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2624  return [
2625  [
2627  'link' => $utp->getLocalURL(),
2628  'rev' => $rev
2629  ]
2630  ];
2631  }
2632 
2638  public function getNewMessageRevisionId() {
2639  $newMessageRevisionId = null;
2640  $newMessageLinks = $this->getNewMessageLinks();
2641  if ( $newMessageLinks ) {
2642  // Note: getNewMessageLinks() never returns more than a single link
2643  // and it is always for the same wiki, but we double-check here in
2644  // case that changes some time in the future.
2645  if ( count( $newMessageLinks ) === 1
2646  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2647  && $newMessageLinks[0]['rev']
2648  ) {
2650  $newMessageRevision = $newMessageLinks[0]['rev'];
2651  $newMessageRevisionId = $newMessageRevision->getId();
2652  }
2653  }
2654  return $newMessageRevisionId;
2655  }
2656 
2665  protected function checkNewtalk( $field, $id ) {
2666  $dbr = wfGetDB( DB_REPLICA );
2667 
2668  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2669 
2670  return $ok !== false;
2671  }
2672 
2680  protected function updateNewtalk( $field, $id, $curRev = null ) {
2681  // Get timestamp of the talk page revision prior to the current one
2682  $prevRev = $curRev ? $curRev->getPrevious() : false;
2683  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2684  // Mark the user as having new messages since this revision
2685  $dbw = wfGetDB( DB_MASTER );
2686  $dbw->insert( 'user_newtalk',
2687  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2688  __METHOD__,
2689  'IGNORE' );
2690  if ( $dbw->affectedRows() ) {
2691  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2692  return true;
2693  } else {
2694  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2695  return false;
2696  }
2697  }
2698 
2705  protected function deleteNewtalk( $field, $id ) {
2706  $dbw = wfGetDB( DB_MASTER );
2707  $dbw->delete( 'user_newtalk',
2708  [ $field => $id ],
2709  __METHOD__ );
2710  if ( $dbw->affectedRows() ) {
2711  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2712  return true;
2713  } else {
2714  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2715  return false;
2716  }
2717  }
2718 
2725  public function setNewtalk( $val, $curRev = null ) {
2726  if ( wfReadOnly() ) {
2727  return;
2728  }
2729 
2730  $this->load();
2731  $this->mNewtalk = $val;
2732 
2733  if ( $this->isAnon() ) {
2734  $field = 'user_ip';
2735  $id = $this->getName();
2736  } else {
2737  $field = 'user_id';
2738  $id = $this->getId();
2739  }
2740 
2741  if ( $val ) {
2742  $changed = $this->updateNewtalk( $field, $id, $curRev );
2743  } else {
2744  $changed = $this->deleteNewtalk( $field, $id );
2745  }
2746 
2747  if ( $changed ) {
2748  $this->invalidateCache();
2749  }
2750  }
2751 
2757  private function newTouchedTimestamp() {
2758  global $wgClockSkewFudge;
2759 
2760  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2761  if ( $this->mTouched && $time <= $this->mTouched ) {
2762  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2763  }
2764 
2765  return $time;
2766  }
2767 
2778  public function clearSharedCache( $mode = 'changed' ) {
2779  if ( !$this->getId() ) {
2780  return;
2781  }
2782 
2784  $key = $this->getCacheKey( $cache );
2785  if ( $mode === 'refresh' ) {
2786  $cache->delete( $key, 1 );
2787  } else {
2788  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2789  if ( $lb->hasOrMadeRecentMasterChanges() ) {
2790  $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
2791  function () use ( $cache, $key ) {
2792  $cache->delete( $key );
2793  },
2794  __METHOD__
2795  );
2796  } else {
2797  $cache->delete( $key );
2798  }
2799  }
2800  }
2801 
2807  public function invalidateCache() {
2808  $this->touch();
2809  $this->clearSharedCache();
2810  }
2811 
2824  public function touch() {
2825  $id = $this->getId();
2826  if ( $id ) {
2827  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2828  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2829  $cache->touchCheckKey( $key );
2830  $this->mQuickTouched = null;
2831  }
2832  }
2833 
2839  public function validateCache( $timestamp ) {
2840  return ( $timestamp >= $this->getTouched() );
2841  }
2842 
2851  public function getTouched() {
2852  $this->load();
2853 
2854  if ( $this->mId ) {
2855  if ( $this->mQuickTouched === null ) {
2856  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2857  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2858 
2859  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2860  }
2861 
2862  return max( $this->mTouched, $this->mQuickTouched );
2863  }
2864 
2865  return $this->mTouched;
2866  }
2867 
2873  public function getDBTouched() {
2874  $this->load();
2875 
2876  return $this->mTouched;
2877  }
2878 
2895  public function setPassword( $str ) {
2896  wfDeprecated( __METHOD__, '1.27' );
2897  return $this->setPasswordInternal( $str );
2898  }
2899 
2908  public function setInternalPassword( $str ) {
2909  wfDeprecated( __METHOD__, '1.27' );
2910  $this->setPasswordInternal( $str );
2911  }
2912 
2921  private function setPasswordInternal( $str ) {
2922  $manager = AuthManager::singleton();
2923 
2924  // If the user doesn't exist yet, fail
2925  if ( !$manager->userExists( $this->getName() ) ) {
2926  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2927  }
2928 
2929  $status = $this->changeAuthenticationData( [
2930  'username' => $this->getName(),
2931  'password' => $str,
2932  'retype' => $str,
2933  ] );
2934  if ( !$status->isGood() ) {
2936  ->info( __METHOD__ . ': Password change rejected: '
2937  . $status->getWikiText( null, null, 'en' ) );
2938  return false;
2939  }
2940 
2941  $this->setOption( 'watchlisttoken', false );
2942  SessionManager::singleton()->invalidateSessionsForUser( $this );
2943 
2944  return true;
2945  }
2946 
2959  public function changeAuthenticationData( array $data ) {
2960  $manager = AuthManager::singleton();
2961  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2962  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2963 
2964  $status = Status::newGood( 'ignored' );
2965  foreach ( $reqs as $req ) {
2966  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2967  }
2968  if ( $status->getValue() === 'ignored' ) {
2969  $status->warning( 'authenticationdatachange-ignored' );
2970  }
2971 
2972  if ( $status->isGood() ) {
2973  foreach ( $reqs as $req ) {
2974  $manager->changeAuthenticationData( $req );
2975  }
2976  }
2977  return $status;
2978  }
2979 
2986  public function getToken( $forceCreation = true ) {
2988 
2989  $this->load();
2990  if ( !$this->mToken && $forceCreation ) {
2991  $this->setToken();
2992  }
2993 
2994  if ( !$this->mToken ) {
2995  // The user doesn't have a token, return null to indicate that.
2996  return null;
2997  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2998  // We return a random value here so existing token checks are very
2999  // likely to fail.
3000  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
3001  } elseif ( $wgAuthenticationTokenVersion === null ) {
3002  // $wgAuthenticationTokenVersion not in use, so return the raw secret
3003  return $this->mToken;
3004  } else {
3005  // $wgAuthenticationTokenVersion in use, so hmac it.
3006  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
3007 
3008  // The raw hash can be overly long. Shorten it up.
3009  $len = max( 32, self::TOKEN_LENGTH );
3010  if ( strlen( $ret ) < $len ) {
3011  // Should never happen, even md5 is 128 bits
3012  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
3013  }
3014  return substr( $ret, -$len );
3015  }
3016  }
3017 
3024  public function setToken( $token = false ) {
3025  $this->load();
3026  if ( $this->mToken === self::INVALID_TOKEN ) {
3028  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
3029  } elseif ( !$token ) {
3030  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
3031  } else {
3032  $this->mToken = $token;
3033  }
3034  }
3035 
3044  public function setNewpassword( $str, $throttle = true ) {
3045  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
3046  }
3047 
3052  public function getEmail() {
3053  $this->load();
3054  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
3055  return $this->mEmail;
3056  }
3057 
3063  $this->load();
3064  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
3066  }
3067 
3072  public function setEmail( $str ) {
3073  $this->load();
3074  if ( $str == $this->mEmail ) {
3075  return;
3076  }
3077  $this->invalidateEmail();
3078  $this->mEmail = $str;
3079  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
3080  }
3081 
3089  public function setEmailWithConfirmation( $str ) {
3091 
3092  if ( !$wgEnableEmail ) {
3093  return Status::newFatal( 'emaildisabled' );
3094  }
3095 
3096  $oldaddr = $this->getEmail();
3097  if ( $str === $oldaddr ) {
3098  return Status::newGood( true );
3099  }
3100 
3101  $type = $oldaddr != '' ? 'changed' : 'set';
3102  $notificationResult = null;
3103 
3104  if ( $wgEmailAuthentication ) {
3105  // Send the user an email notifying the user of the change in registered
3106  // email address on their previous email address
3107  if ( $type == 'changed' ) {
3108  $change = $str != '' ? 'changed' : 'removed';
3109  $notificationResult = $this->sendMail(
3110  wfMessage( 'notificationemail_subject_' . $change )->text(),
3111  wfMessage( 'notificationemail_body_' . $change,
3112  $this->getRequest()->getIP(),
3113  $this->getName(),
3114  $str )->text()
3115  );
3116  }
3117  }
3118 
3119  $this->setEmail( $str );
3120 
3121  if ( $str !== '' && $wgEmailAuthentication ) {
3122  // Send a confirmation request to the new address if needed
3123  $result = $this->sendConfirmationMail( $type );
3124 
3125  if ( $notificationResult !== null ) {
3126  $result->merge( $notificationResult );
3127  }
3128 
3129  if ( $result->isGood() ) {
3130  // Say to the caller that a confirmation and notification mail has been sent
3131  $result->value = 'eauth';
3132  }
3133  } else {
3134  $result = Status::newGood( true );
3135  }
3136 
3137  return $result;
3138  }
3139 
3144  public function getRealName() {
3145  if ( !$this->isItemLoaded( 'realname' ) ) {
3146  $this->load();
3147  }
3148 
3149  return $this->mRealName;
3150  }
3151 
3156  public function setRealName( $str ) {
3157  $this->load();
3158  $this->mRealName = $str;
3159  }
3160 
3171  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3172  global $wgHiddenPrefs;
3173  $this->loadOptions();
3174 
3175  # We want 'disabled' preferences to always behave as the default value for
3176  # users, even if they have set the option explicitly in their settings (ie they
3177  # set it, and then it was disabled removing their ability to change it). But
3178  # we don't want to erase the preferences in the database in case the preference
3179  # is re-enabled again. So don't touch $mOptions, just override the returned value
3180  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3181  return self::getDefaultOption( $oname );
3182  }
3183 
3184  if ( array_key_exists( $oname, $this->mOptions ) ) {
3185  return $this->mOptions[$oname];
3186  } else {
3187  return $defaultOverride;
3188  }
3189  }
3190 
3199  public function getOptions( $flags = 0 ) {
3200  global $wgHiddenPrefs;
3201  $this->loadOptions();
3203 
3204  # We want 'disabled' preferences to always behave as the default value for
3205  # users, even if they have set the option explicitly in their settings (ie they
3206  # set it, and then it was disabled removing their ability to change it). But
3207  # we don't want to erase the preferences in the database in case the preference
3208  # is re-enabled again. So don't touch $mOptions, just override the returned value
3209  foreach ( $wgHiddenPrefs as $pref ) {
3210  $default = self::getDefaultOption( $pref );
3211  if ( $default !== null ) {
3212  $options[$pref] = $default;
3213  }
3214  }
3215 
3216  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3217  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3218  }
3219 
3220  return $options;
3221  }
3222 
3230  public function getBoolOption( $oname ) {
3231  return (bool)$this->getOption( $oname );
3232  }
3233 
3242  public function getIntOption( $oname, $defaultOverride = 0 ) {
3243  $val = $this->getOption( $oname );
3244  if ( $val == '' ) {
3245  $val = $defaultOverride;
3246  }
3247  return intval( $val );
3248  }
3249 
3258  public function setOption( $oname, $val ) {
3259  $this->loadOptions();
3260 
3261  // Explicitly NULL values should refer to defaults
3262  if ( is_null( $val ) ) {
3263  $val = self::getDefaultOption( $oname );
3264  }
3265 
3266  $this->mOptions[$oname] = $val;
3267  }
3268 
3279  public function getTokenFromOption( $oname ) {
3280  global $wgHiddenPrefs;
3281 
3282  $id = $this->getId();
3283  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3284  return false;
3285  }
3286 
3287  $token = $this->getOption( $oname );
3288  if ( !$token ) {
3289  // Default to a value based on the user token to avoid space
3290  // wasted on storing tokens for all users. When this option
3291  // is set manually by the user, only then is it stored.
3292  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3293  }
3294 
3295  return $token;
3296  }
3297 
3307  public function resetTokenFromOption( $oname ) {
3308  global $wgHiddenPrefs;
3309  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3310  return false;
3311  }
3312 
3313  $token = MWCryptRand::generateHex( 40 );
3314  $this->setOption( $oname, $token );
3315  return $token;
3316  }
3317 
3341  public static function listOptionKinds() {
3342  return [
3343  'registered',
3344  'registered-multiselect',
3345  'registered-checkmatrix',
3346  'userjs',
3347  'special',
3348  'unused'
3349  ];
3350  }
3351 
3364  public function getOptionKinds( IContextSource $context, $options = null ) {
3365  $this->loadOptions();
3366  if ( $options === null ) {
3368  }
3369 
3370  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3371  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3372  $mapping = [];
3373 
3374  // Pull out the "special" options, so they don't get converted as
3375  // multiselect or checkmatrix.
3376  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3377  foreach ( $specialOptions as $name => $value ) {
3378  unset( $prefs[$name] );
3379  }
3380 
3381  // Multiselect and checkmatrix options are stored in the database with
3382  // one key per option, each having a boolean value. Extract those keys.
3383  $multiselectOptions = [];
3384  foreach ( $prefs as $name => $info ) {
3385  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3386  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3387  $opts = HTMLFormField::flattenOptions( $info['options'] );
3388  $prefix = $info['prefix'] ?? $name;
3389 
3390  foreach ( $opts as $value ) {
3391  $multiselectOptions["$prefix$value"] = true;
3392  }
3393 
3394  unset( $prefs[$name] );
3395  }
3396  }
3397  $checkmatrixOptions = [];
3398  foreach ( $prefs as $name => $info ) {
3399  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3400  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3401  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3402  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3403  $prefix = $info['prefix'] ?? $name;
3404 
3405  foreach ( $columns as $column ) {
3406  foreach ( $rows as $row ) {
3407  $checkmatrixOptions["$prefix$column-$row"] = true;
3408  }
3409  }
3410 
3411  unset( $prefs[$name] );
3412  }
3413  }
3414 
3415  // $value is ignored
3416  foreach ( $options as $key => $value ) {
3417  if ( isset( $prefs[$key] ) ) {
3418  $mapping[$key] = 'registered';
3419  } elseif ( isset( $multiselectOptions[$key] ) ) {
3420  $mapping[$key] = 'registered-multiselect';
3421  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3422  $mapping[$key] = 'registered-checkmatrix';
3423  } elseif ( isset( $specialOptions[$key] ) ) {
3424  $mapping[$key] = 'special';
3425  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3426  $mapping[$key] = 'userjs';
3427  } else {
3428  $mapping[$key] = 'unused';
3429  }
3430  }
3431 
3432  return $mapping;
3433  }
3434 
3449  public function resetOptions(
3450  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3451  IContextSource $context = null
3452  ) {
3453  $this->load();
3454  $defaultOptions = self::getDefaultOptions();
3455 
3456  if ( !is_array( $resetKinds ) ) {
3457  $resetKinds = [ $resetKinds ];
3458  }
3459 
3460  if ( in_array( 'all', $resetKinds ) ) {
3461  $newOptions = $defaultOptions;
3462  } else {
3463  if ( $context === null ) {
3465  }
3466 
3467  $optionKinds = $this->getOptionKinds( $context );
3468  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3469  $newOptions = [];
3470 
3471  // Use default values for the options that should be deleted, and
3472  // copy old values for the ones that shouldn't.
3473  foreach ( $this->mOptions as $key => $value ) {
3474  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3475  if ( array_key_exists( $key, $defaultOptions ) ) {
3476  $newOptions[$key] = $defaultOptions[$key];
3477  }
3478  } else {
3479  $newOptions[$key] = $value;
3480  }
3481  }
3482  }
3483 
3484  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3485 
3486  $this->mOptions = $newOptions;
3487  $this->mOptionsLoaded = true;
3488  }
3489 
3494  public function getDatePreference() {
3495  // Important migration for old data rows
3496  if ( is_null( $this->mDatePreference ) ) {
3497  global $wgLang;
3498  $value = $this->getOption( 'date' );
3499  $map = $wgLang->getDatePreferenceMigrationMap();
3500  if ( isset( $map[$value] ) ) {
3501  $value = $map[$value];
3502  }
3503  $this->mDatePreference = $value;
3504  }
3505  return $this->mDatePreference;
3506  }
3507 
3514  public function requiresHTTPS() {
3515  global $wgSecureLogin;
3516  if ( !$wgSecureLogin ) {
3517  return false;
3518  } else {
3519  $https = $this->getBoolOption( 'prefershttps' );
3520  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3521  if ( $https ) {
3522  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3523  }
3524  return $https;
3525  }
3526  }
3527 
3533  public function getStubThreshold() {
3534  global $wgMaxArticleSize; # Maximum article size, in Kb
3535  $threshold = $this->getIntOption( 'stubthreshold' );
3536  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3537  // If they have set an impossible value, disable the preference
3538  // so we can use the parser cache again.
3539  $threshold = 0;
3540  }
3541  return $threshold;
3542  }
3543 
3548  public function getRights() {
3549  if ( is_null( $this->mRights ) ) {
3550  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3551  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3552 
3553  // Deny any rights denied by the user's session, unless this
3554  // endpoint has no sessions.
3555  if ( !defined( 'MW_NO_SESSION' ) ) {
3556  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3557  if ( $allowedRights !== null ) {
3558  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3559  }
3560  }
3561 
3562  Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
3563  // Force reindexation of rights when a hook has unset one of them
3564  $this->mRights = array_values( array_unique( $this->mRights ) );
3565 
3566  // If block disables login, we should also remove any
3567  // extra rights blocked users might have, in case the
3568  // blocked user has a pre-existing session (T129738).
3569  // This is checked here for cases where people only call
3570  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3571  // to give a better error message in the common case.
3572  $config = RequestContext::getMain()->getConfig();
3573  if (
3574  $this->isLoggedIn() &&
3575  $config->get( 'BlockDisablesLogin' ) &&
3576  $this->isBlocked()
3577  ) {
3578  $anon = new User;
3579  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3580  }
3581  }
3582  return $this->mRights;
3583  }
3584 
3590  public function getGroups() {
3591  $this->load();
3592  $this->loadGroups();
3593  return array_keys( $this->mGroupMemberships );
3594  }
3595 
3603  public function getGroupMemberships() {
3604  $this->load();
3605  $this->loadGroups();
3606  return $this->mGroupMemberships;
3607  }
3608 
3616  public function getEffectiveGroups( $recache = false ) {
3617  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3618  $this->mEffectiveGroups = array_unique( array_merge(
3619  $this->getGroups(), // explicit groups
3620  $this->getAutomaticGroups( $recache ) // implicit groups
3621  ) );
3622  // Avoid PHP 7.1 warning of passing $this by reference
3623  $user = $this;
3624  // Hook for additional groups
3625  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3626  // Force reindexation of groups when a hook has unset one of them
3627  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3628  }
3629  return $this->mEffectiveGroups;
3630  }
3631 
3639  public function getAutomaticGroups( $recache = false ) {
3640  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3641  $this->mImplicitGroups = [ '*' ];
3642  if ( $this->getId() ) {
3643  $this->mImplicitGroups[] = 'user';
3644 
3645  $this->mImplicitGroups = array_unique( array_merge(
3646  $this->mImplicitGroups,
3648  ) );
3649  }
3650  if ( $recache ) {
3651  // Assure data consistency with rights/groups,
3652  // as getEffectiveGroups() depends on this function
3653  $this->mEffectiveGroups = null;
3654  }
3655  }
3656  return $this->mImplicitGroups;
3657  }
3658 
3668  public function getFormerGroups() {
3669  $this->load();
3670 
3671  if ( is_null( $this->mFormerGroups ) ) {
3672  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3673  ? wfGetDB( DB_MASTER )
3674  : wfGetDB( DB_REPLICA );
3675  $res = $db->select( 'user_former_groups',
3676  [ 'ufg_group' ],
3677  [ 'ufg_user' => $this->mId ],
3678  __METHOD__ );
3679  $this->mFormerGroups = [];
3680  foreach ( $res as $row ) {
3681  $this->mFormerGroups[] = $row->ufg_group;
3682  }
3683  }
3684 
3685  return $this->mFormerGroups;
3686  }
3687 
3692  public function getEditCount() {
3693  if ( !$this->getId() ) {
3694  return null;
3695  }
3696 
3697  if ( $this->mEditCount === null ) {
3698  /* Populate the count, if it has not been populated yet */
3699  $dbr = wfGetDB( DB_REPLICA );
3700  // check if the user_editcount field has been initialized
3701  $count = $dbr->selectField(
3702  'user', 'user_editcount',
3703  [ 'user_id' => $this->mId ],
3704  __METHOD__
3705  );
3706 
3707  if ( $count === null ) {
3708  // it has not been initialized. do so.
3709  $count = $this->initEditCountInternal();
3710  }
3711  $this->mEditCount = $count;
3712  }
3713  return (int)$this->mEditCount;
3714  }
3715 
3727  public function addGroup( $group, $expiry = null ) {
3728  $this->load();
3729  $this->loadGroups();
3730 
3731  if ( $expiry ) {
3732  $expiry = wfTimestamp( TS_MW, $expiry );
3733  }
3734 
3735  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3736  return false;
3737  }
3738 
3739  // create the new UserGroupMembership and put it in the DB
3740  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3741  if ( !$ugm->insert( true ) ) {
3742  return false;
3743  }
3744 
3745  $this->mGroupMemberships[$group] = $ugm;
3746 
3747  // Refresh the groups caches, and clear the rights cache so it will be
3748  // refreshed on the next call to $this->getRights().
3749  $this->getEffectiveGroups( true );
3750  $this->mRights = null;
3751 
3752  $this->invalidateCache();
3753 
3754  return true;
3755  }
3756 
3763  public function removeGroup( $group ) {
3764  $this->load();
3765 
3766  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3767  return false;
3768  }
3769 
3770  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3771  // delete the membership entry
3772  if ( !$ugm || !$ugm->delete() ) {
3773  return false;
3774  }
3775 
3776  $this->loadGroups();
3777  unset( $this->mGroupMemberships[$group] );
3778 
3779  // Refresh the groups caches, and clear the rights cache so it will be
3780  // refreshed on the next call to $this->getRights().
3781  $this->getEffectiveGroups( true );
3782  $this->mRights = null;
3783 
3784  $this->invalidateCache();
3785 
3786  return true;
3787  }
3788 
3793  public function isLoggedIn() {
3794  return $this->getId() != 0;
3795  }
3796 
3801  public function isAnon() {
3802  return !$this->isLoggedIn();
3803  }
3804 
3809  public function isBot() {
3810  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3811  return true;
3812  }
3813 
3814  $isBot = false;
3815  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3816 
3817  return $isBot;
3818  }
3819 
3826  public function isAllowedAny() {
3827  $permissions = func_get_args();
3828  foreach ( $permissions as $permission ) {
3829  if ( $this->isAllowed( $permission ) ) {
3830  return true;
3831  }
3832  }
3833  return false;
3834  }
3835 
3841  public function isAllowedAll() {
3842  $permissions = func_get_args();
3843  foreach ( $permissions as $permission ) {
3844  if ( !$this->isAllowed( $permission ) ) {
3845  return false;
3846  }
3847  }
3848  return true;
3849  }
3850 
3856  public function isAllowed( $action = '' ) {
3857  if ( $action === '' ) {
3858  return true; // In the spirit of DWIM
3859  }
3860  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3861  // by misconfiguration: 0 == 'foo'
3862  return in_array( $action, $this->getRights(), true );
3863  }
3864 
3869  public function useRCPatrol() {
3870  global $wgUseRCPatrol;
3871  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3872  }
3873 
3878  public function useNPPatrol() {
3880  return (
3881  ( $wgUseRCPatrol || $wgUseNPPatrol )
3882  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3883  );
3884  }
3885 
3890  public function useFilePatrol() {
3892  return (
3893  ( $wgUseRCPatrol || $wgUseFilePatrol )
3894  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3895  );
3896  }
3897 
3903  public function getRequest() {
3904  if ( $this->mRequest ) {
3905  return $this->mRequest;
3906  } else {
3907  global $wgRequest;
3908  return $wgRequest;
3909  }
3910  }
3911 
3920  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3921  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3922  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3923  }
3924  return false;
3925  }
3926 
3934  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3935  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3936  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3937  $this,
3938  [ $title->getSubjectPage(), $title->getTalkPage() ]
3939  );
3940  }
3941  $this->invalidateCache();
3942  }
3943 
3951  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3952  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3953  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3954  $store->removeWatch( $this, $title->getSubjectPage() );
3955  $store->removeWatch( $this, $title->getTalkPage() );
3956  }
3957  $this->invalidateCache();
3958  }
3959 
3968  public function clearNotification( &$title, $oldid = 0 ) {
3970 
3971  // Do nothing if the database is locked to writes
3972  if ( wfReadOnly() ) {
3973  return;
3974  }
3975 
3976  // Do nothing if not allowed to edit the watchlist
3977  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3978  return;
3979  }
3980 
3981  // If we're working on user's talk page, we should update the talk page message indicator
3982  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3983  // Avoid PHP 7.1 warning of passing $this by reference
3984  $user = $this;
3985  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3986  return;
3987  }
3988 
3989  // Try to update the DB post-send and only if needed...
3990  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3991  if ( !$this->getNewtalk() ) {
3992  return; // no notifications to clear
3993  }
3994 
3995  // Delete the last notifications (they stack up)
3996  $this->setNewtalk( false );
3997 
3998  // If there is a new, unseen, revision, use its timestamp
3999  $nextid = $oldid
4000  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
4001  : null;
4002  if ( $nextid ) {
4003  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
4004  }
4005  } );
4006  }
4007 
4008  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4009  return;
4010  }
4011 
4012  if ( $this->isAnon() ) {
4013  // Nothing else to do...
4014  return;
4015  }
4016 
4017  // Only update the timestamp if the page is being watched.
4018  // The query to find out if it is watched is cached both in memcached and per-invocation,
4019  // and when it does have to be executed, it can be on a replica DB
4020  // If this is the user's newtalk page, we always update the timestamp
4021  $force = '';
4022  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
4023  $force = 'force';
4024  }
4025 
4026  MediaWikiServices::getInstance()->getWatchedItemStore()
4027  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
4028  }
4029 
4036  public function clearAllNotifications() {
4038  // Do nothing if not allowed to edit the watchlist
4039  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
4040  return;
4041  }
4042 
4043  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4044  $this->setNewtalk( false );
4045  return;
4046  }
4047 
4048  $id = $this->getId();
4049  if ( !$id ) {
4050  return;
4051  }
4052 
4053  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
4054  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
4055 
4056  // We also need to clear here the "you have new message" notification for the own
4057  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
4058  }
4059 
4065  public function getExperienceLevel() {
4066  global $wgLearnerEdits,
4070 
4071  if ( $this->isAnon() ) {
4072  return false;
4073  }
4074 
4075  $editCount = $this->getEditCount();
4076  $registration = $this->getRegistration();
4077  $now = time();
4078  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
4079  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
4080 
4081  if (
4082  $editCount < $wgLearnerEdits ||
4083  $registration > $learnerRegistration
4084  ) {
4085  return 'newcomer';
4086  } elseif (
4087  $editCount > $wgExperiencedUserEdits &&
4088  $registration <= $experiencedRegistration
4089  ) {
4090  return 'experienced';
4091  } else {
4092  return 'learner';
4093  }
4094  }
4095 
4104  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
4105  $this->load();
4106  if ( 0 == $this->mId ) {
4107  return;
4108  }
4109 
4110  $session = $this->getRequest()->getSession();
4111  if ( $request && $session->getRequest() !== $request ) {
4112  $session = $session->sessionWithRequest( $request );
4113  }
4114  $delay = $session->delaySave();
4115 
4116  if ( !$session->getUser()->equals( $this ) ) {
4117  if ( !$session->canSetUser() ) {
4119  ->warning( __METHOD__ .
4120  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
4121  );
4122  return;
4123  }
4124  $session->setUser( $this );
4125  }
4126 
4127  $session->setRememberUser( $rememberMe );
4128  if ( $secure !== null ) {
4129  $session->setForceHTTPS( $secure );
4130  }
4131 
4132  $session->persist();
4133 
4134  ScopedCallback::consume( $delay );
4135  }
4136 
4140  public function logout() {
4141  // Avoid PHP 7.1 warning of passing $this by reference
4142  $user = $this;
4143  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
4144  $this->doLogout();
4145  }
4146  }
4147 
4152  public function doLogout() {
4153  $session = $this->getRequest()->getSession();
4154  if ( !$session->canSetUser() ) {
4156  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
4157  $error = 'immutable';
4158  } elseif ( !$session->getUser()->equals( $this ) ) {
4160  ->warning( __METHOD__ .
4161  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4162  );
4163  // But we still may as well make this user object anon
4164  $this->clearInstanceCache( 'defaults' );
4165  $error = 'wronguser';
4166  } else {
4167  $this->clearInstanceCache( 'defaults' );
4168  $delay = $session->delaySave();
4169  $session->unpersist(); // Clear cookies (T127436)
4170  $session->setLoggedOutTimestamp( time() );
4171  $session->setUser( new User );
4172  $session->set( 'wsUserID', 0 ); // Other code expects this
4173  $session->resetAllTokens();
4174  ScopedCallback::consume( $delay );
4175  $error = false;
4176  }
4177  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4178  'event' => 'logout',
4179  'successful' => $error === false,
4180  'status' => $error ?: 'success',
4181  ] );
4182  }
4183 
4188  public function saveSettings() {
4189  if ( wfReadOnly() ) {
4190  // @TODO: caller should deal with this instead!
4191  // This should really just be an exception.
4193  null,
4194  "Could not update user with ID '{$this->mId}'; DB is read-only."
4195  ) );
4196  return;
4197  }
4198 
4199  $this->load();
4200  if ( 0 == $this->mId ) {
4201  return; // anon
4202  }
4203 
4204  // Get a new user_touched that is higher than the old one.
4205  // This will be used for a CAS check as a last-resort safety
4206  // check against race conditions and replica DB lag.
4207  $newTouched = $this->newTouchedTimestamp();
4208 
4209  $dbw = wfGetDB( DB_MASTER );
4210  $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
4212 
4213  $dbw->update( 'user',
4214  [ /* SET */
4215  'user_name' => $this->mName,
4216  'user_real_name' => $this->mRealName,
4217  'user_email' => $this->mEmail,
4218  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4219  'user_touched' => $dbw->timestamp( $newTouched ),
4220  'user_token' => strval( $this->mToken ),
4221  'user_email_token' => $this->mEmailToken,
4222  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4223  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4224  'user_id' => $this->mId,
4225  ] ), $fname
4226  );
4227 
4228  if ( !$dbw->affectedRows() ) {
4229  // Maybe the problem was a missed cache update; clear it to be safe
4230  $this->clearSharedCache( 'refresh' );
4231  // User was changed in the meantime or loaded with stale data
4232  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4233  LoggerFactory::getInstance( 'preferences' )->warning(
4234  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4235  [ 'user_id' => $this->mId, 'db_flag' => $from ]
4236  );
4237  throw new MWException( "CAS update failed on user_touched. " .
4238  "The version of the user to be saved is older than the current version."
4239  );
4240  }
4241 
4242  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4243  $dbw->update(
4244  'actor',
4245  [ 'actor_name' => $this->mName ],
4246  [ 'actor_user' => $this->mId ],
4247  $fname
4248  );
4249  }
4250  } );
4251 
4252  $this->mTouched = $newTouched;
4253  $this->saveOptions();
4254 
4255  Hooks::run( 'UserSaveSettings', [ $this ] );
4256  $this->clearSharedCache();
4257  $this->getUserPage()->invalidateCache();
4258  }
4259 
4266  public function idForName( $flags = 0 ) {
4267  $s = trim( $this->getName() );
4268  if ( $s === '' ) {
4269  return 0;
4270  }
4271 
4272  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4273  ? wfGetDB( DB_MASTER )
4274  : wfGetDB( DB_REPLICA );
4275 
4276  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4277  ? [ 'LOCK IN SHARE MODE' ]
4278  : [];
4279 
4280  $id = $db->selectField( 'user',
4281  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4282 
4283  return (int)$id;
4284  }
4285 
4301  public static function createNew( $name, $params = [] ) {
4302  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4303  if ( isset( $params[$field] ) ) {
4304  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4305  unset( $params[$field] );
4306  }
4307  }
4308 
4309  $user = new User;
4310  $user->load();
4311  $user->setToken(); // init token
4312  if ( isset( $params['options'] ) ) {
4313  $user->mOptions = $params['options'] + (array)$user->mOptions;
4314  unset( $params['options'] );
4315  }
4316  $dbw = wfGetDB( DB_MASTER );
4317 
4318  $noPass = PasswordFactory::newInvalidPassword()->toString();
4319 
4320  $fields = [
4321  'user_name' => $name,
4322  'user_password' => $noPass,
4323  'user_newpassword' => $noPass,
4324  'user_email' => $user->mEmail,
4325  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4326  'user_real_name' => $user->mRealName,
4327  'user_token' => strval( $user->mToken ),
4328  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4329  'user_editcount' => 0,
4330  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4331  ];
4332  foreach ( $params as $name => $value ) {
4333  $fields["user_$name"] = $value;
4334  }
4335 
4336  return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
4337  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4338  if ( $dbw->affectedRows() ) {
4339  $newUser = self::newFromId( $dbw->insertId() );
4340  $newUser->mName = $fields['user_name'];
4341  $newUser->updateActorId( $dbw );
4342  // Load the user from master to avoid replica lag
4343  $newUser->load( self::READ_LATEST );
4344  } else {
4345  $newUser = null;
4346  }
4347  return $newUser;
4348  } );
4349  }
4350 
4377  public function addToDatabase() {
4378  $this->load();
4379  if ( !$this->mToken ) {
4380  $this->setToken(); // init token
4381  }
4382 
4383  if ( !is_string( $this->mName ) ) {
4384  throw new RuntimeException( "User name field is not set." );
4385  }
4386 
4387  $this->mTouched = $this->newTouchedTimestamp();
4388 
4389  $dbw = wfGetDB( DB_MASTER );
4390  $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
4391  $noPass = PasswordFactory::newInvalidPassword()->toString();
4392  $dbw->insert( 'user',
4393  [
4394  'user_name' => $this->mName,
4395  'user_password' => $noPass,
4396  'user_newpassword' => $noPass,
4397  'user_email' => $this->mEmail,
4398  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4399  'user_real_name' => $this->mRealName,
4400  'user_token' => strval( $this->mToken ),
4401  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4402  'user_editcount' => 0,
4403  'user_touched' => $dbw->timestamp( $this->mTouched ),
4404  ], $fname,
4405  [ 'IGNORE' ]
4406  );
4407  if ( !$dbw->affectedRows() ) {
4408  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4409  $this->mId = $dbw->selectField(
4410  'user',
4411  'user_id',
4412  [ 'user_name' => $this->mName ],
4413  $fname,
4414  [ 'LOCK IN SHARE MODE' ]
4415  );
4416  $loaded = false;
4417  if ( $this->mId ) {
4418  if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4419  $loaded = true;
4420  }
4421  }
4422  if ( !$loaded ) {
4423  throw new MWException( $fname . ": hit a key conflict attempting " .
4424  "to insert user '{$this->mName}' row, but it was not present in select!" );
4425  }
4426  return Status::newFatal( 'userexists' );
4427  }
4428  $this->mId = $dbw->insertId();
4429  self::$idCacheByName[$this->mName] = $this->mId;
4430  $this->updateActorId( $dbw );
4431 
4432  return Status::newGood();
4433  } );
4434  if ( !$status->isGood() ) {
4435  return $status;
4436  }
4437 
4438  // Clear instance cache other than user table data and actor, which is already accurate
4439  $this->clearInstanceCache();
4440 
4441  $this->saveOptions();
4442  return Status::newGood();
4443  }
4444 
4449  private function updateActorId( IDatabase $dbw ) {
4451 
4452  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4453  $dbw->insert(
4454  'actor',
4455  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4456  __METHOD__
4457  );
4458  $this->mActorId = (int)$dbw->insertId();
4459  }
4460  }
4461 
4467  public function spreadAnyEditBlock() {
4468  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4469  return $this->spreadBlock();
4470  }
4471 
4472  return false;
4473  }
4474 
4480  protected function spreadBlock() {
4481  wfDebug( __METHOD__ . "()\n" );
4482  $this->load();
4483  if ( $this->mId == 0 ) {
4484  return false;
4485  }
4486 
4487  $userblock = Block::newFromTarget( $this->getName() );
4488  if ( !$userblock ) {
4489  return false;
4490  }
4491 
4492  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4493  }
4494 
4499  public function isBlockedFromCreateAccount() {
4500  $this->getBlockedStatus();
4501  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4502  return $this->mBlock;
4503  }
4504 
4505  # T15611: if the IP address the user is trying to create an account from is
4506  # blocked with createaccount disabled, prevent new account creation there even
4507  # when the user is logged in
4508  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4509  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4510  }
4511  return $this->mBlockedFromCreateAccount instanceof Block
4512  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4513  ? $this->mBlockedFromCreateAccount
4514  : false;
4515  }
4516 
4521  public function isBlockedFromEmailuser() {
4522  $this->getBlockedStatus();
4523  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4524  }
4525 
4531  public function isBlockedFromUpload() {
4532  $this->getBlockedStatus();
4533  return $this->mBlock && $this->mBlock->prevents( 'upload' );
4534  }
4535 
4540  public function isAllowedToCreateAccount() {
4541  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4542  }
4543 
4549  public function getUserPage() {
4550  return Title::makeTitle( NS_USER, $this->getName() );
4551  }
4552 
4558  public function getTalkPage() {
4559  $title = $this->getUserPage();
4560  return $title->getTalkPage();
4561  }
4562 
4568  public function isNewbie() {
4569  return !$this->isAllowed( 'autoconfirmed' );
4570  }
4571 
4578  public function checkPassword( $password ) {
4579  wfDeprecated( __METHOD__, '1.27' );
4580 
4581  $manager = AuthManager::singleton();
4582  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4583  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4584  [
4585  'username' => $this->getName(),
4586  'password' => $password,
4587  ]
4588  );
4589  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4590  switch ( $res->status ) {
4591  case AuthenticationResponse::PASS:
4592  return true;
4593  case AuthenticationResponse::FAIL:
4594  // Hope it's not a PreAuthenticationProvider that failed...
4596  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4597  return false;
4598  default:
4599  throw new BadMethodCallException(
4600  'AuthManager returned a response unsupported by ' . __METHOD__
4601  );
4602  }
4603  }
4604 
4613  public function checkTemporaryPassword( $plaintext ) {
4614  wfDeprecated( __METHOD__, '1.27' );
4615  // Can't check the temporary password individually.
4616  return $this->checkPassword( $plaintext );
4617  }
4618 
4630  public function getEditTokenObject( $salt = '', $request = null ) {
4631  if ( $this->isAnon() ) {
4632  return new LoggedOutEditToken();
4633  }
4634 
4635  if ( !$request ) {
4636  $request = $this->getRequest();
4637  }
4638  return $request->getSession()->getToken( $salt );
4639  }
4640 
4654  public function getEditToken( $salt = '', $request = null ) {
4655  return $this->getEditTokenObject( $salt, $request )->toString();
4656  }
4657 
4670  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4671  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4672  }
4673 
4684  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4685  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4686  return $this->matchEditToken( $val, $salt, $request, $maxage );
4687  }
4688 
4696  public function sendConfirmationMail( $type = 'created' ) {
4697  global $wgLang;
4698  $expiration = null; // gets passed-by-ref and defined in next line.
4699  $token = $this->confirmationToken( $expiration );
4700  $url = $this->confirmationTokenUrl( $token );
4701  $invalidateURL = $this->invalidationTokenUrl( $token );
4702  $this->saveSettings();
4703 
4704  if ( $type == 'created' || $type === false ) {
4705  $message = 'confirmemail_body';
4706  } elseif ( $type === true ) {
4707  $message = 'confirmemail_body_changed';
4708  } else {
4709  // Messages: confirmemail_body_changed, confirmemail_body_set
4710  $message = 'confirmemail_body_' . $type;
4711  }
4712 
4713  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4714  wfMessage( $message,
4715  $this->getRequest()->getIP(),
4716  $this->getName(),
4717  $url,
4718  $wgLang->userTimeAndDate( $expiration, $this ),
4719  $invalidateURL,
4720  $wgLang->userDate( $expiration, $this ),
4721  $wgLang->userTime( $expiration, $this ) )->text() );
4722  }
4723 
4735  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4736  global $wgPasswordSender;
4737 
4738  if ( $from instanceof User ) {
4739  $sender = MailAddress::newFromUser( $from );
4740  } else {
4741  $sender = new MailAddress( $wgPasswordSender,
4742  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4743  }
4744  $to = MailAddress::newFromUser( $this );
4745 
4746  return UserMailer::send( $to, $sender, $subject, $body, [
4747  'replyTo' => $replyto,
4748  ] );
4749  }
4750 
4761  protected function confirmationToken( &$expiration ) {
4763  $now = time();
4764  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4765  $expiration = wfTimestamp( TS_MW, $expires );
4766  $this->load();
4767  $token = MWCryptRand::generateHex( 32 );
4768  $hash = md5( $token );
4769  $this->mEmailToken = $hash;
4770  $this->mEmailTokenExpires = $expiration;
4771  return $token;
4772  }
4773 
4779  protected function confirmationTokenUrl( $token ) {
4780  return $this->getTokenUrl( 'ConfirmEmail', $token );
4781  }
4782 
4788  protected function invalidationTokenUrl( $token ) {
4789  return $this->getTokenUrl( 'InvalidateEmail', $token );
4790  }
4791 
4806  protected function getTokenUrl( $page, $token ) {
4807  // Hack to bypass localization of 'Special:'
4808  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4809  return $title->getCanonicalURL();
4810  }
4811 
4819  public function confirmEmail() {
4820  // Check if it's already confirmed, so we don't touch the database
4821  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4822  if ( !$this->isEmailConfirmed() ) {
4824  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4825  }
4826  return true;
4827  }
4828 
4836  public function invalidateEmail() {
4837  $this->load();
4838  $this->mEmailToken = null;
4839  $this->mEmailTokenExpires = null;
4840  $this->setEmailAuthenticationTimestamp( null );
4841  $this->mEmail = '';
4842  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4843  return true;
4844  }
4845 
4850  public function setEmailAuthenticationTimestamp( $timestamp ) {
4851  $this->load();
4852  $this->mEmailAuthenticated = $timestamp;
4853  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4854  }
4855 
4861  public function canSendEmail() {
4863  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4864  return false;
4865  }
4866  $canSend = $this->isEmailConfirmed();
4867  // Avoid PHP 7.1 warning of passing $this by reference
4868  $user = $this;
4869  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4870  return $canSend;
4871  }
4872 
4878  public function canReceiveEmail() {
4879  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4880  }
4881 
4892  public function isEmailConfirmed() {
4893  global $wgEmailAuthentication;
4894  $this->load();
4895  // Avoid PHP 7.1 warning of passing $this by reference
4896  $user = $this;
4897  $confirmed = true;
4898  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4899  if ( $this->isAnon() ) {
4900  return false;
4901  }
4902  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4903  return false;
4904  }
4905  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4906  return false;
4907  }
4908  return true;
4909  } else {
4910  return $confirmed;
4911  }
4912  }
4913 
4918  public function isEmailConfirmationPending() {
4919  global $wgEmailAuthentication;
4920  return $wgEmailAuthentication &&
4921  !$this->isEmailConfirmed() &&
4922  $this->mEmailToken &&
4923  $this->mEmailTokenExpires > wfTimestamp();
4924  }
4925 
4933  public function getRegistration() {
4934  if ( $this->isAnon() ) {
4935  return false;
4936  }
4937  $this->load();
4938  return $this->mRegistration;
4939  }
4940 
4947  public function getFirstEditTimestamp() {
4948  if ( $this->getId() == 0 ) {
4949  return false; // anons
4950  }
4951  $dbr = wfGetDB( DB_REPLICA );
4952  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4953  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4954  ? 'revactor_timestamp' : 'rev_timestamp';
4955  $time = $dbr->selectField(
4956  [ 'revision' ] + $actorWhere['tables'],
4957  $tsField,
4958  [ $actorWhere['conds'] ],
4959  __METHOD__,
4960  [ 'ORDER BY' => "$tsField ASC" ],
4961  $actorWhere['joins']
4962  );
4963  if ( !$time ) {
4964  return false; // no edits
4965  }
4966  return wfTimestamp( TS_MW, $time );
4967  }
4968 
4975  public static function getGroupPermissions( $groups ) {
4977  $rights = [];
4978  // grant every granted permission first
4979  foreach ( $groups as $group ) {
4980  if ( isset( $wgGroupPermissions[$group] ) ) {
4981  $rights = array_merge( $rights,
4982  // array_filter removes empty items
4983  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4984  }
4985  }
4986  // now revoke the revoked permissions
4987  foreach ( $groups as $group ) {
4988  if ( isset( $wgRevokePermissions[$group] ) ) {
4989  $rights = array_diff( $rights,
4990  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4991  }
4992  }
4993  return array_unique( $rights );
4994  }
4995 
5002  public static function getGroupsWithPermission( $role ) {
5003  global $wgGroupPermissions;
5004  $allowedGroups = [];
5005  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
5006  if ( self::groupHasPermission( $group, $role ) ) {
5007  $allowedGroups[] = $group;
5008  }
5009  }
5010  return $allowedGroups;
5011  }
5012 
5025  public static function groupHasPermission( $group, $role ) {
5027  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
5028  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
5029  }
5030 
5045  public static function isEveryoneAllowed( $right ) {
5047  static $cache = [];
5048 
5049  // Use the cached results, except in unit tests which rely on
5050  // being able change the permission mid-request
5051  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
5052  return $cache[$right];
5053  }
5054 
5055  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
5056  $cache[$right] = false;
5057  return false;
5058  }
5059 
5060  // If it's revoked anywhere, then everyone doesn't have it
5061  foreach ( $wgRevokePermissions as $rights ) {
5062  if ( isset( $rights[$right] ) && $rights[$right] ) {
5063  $cache[$right] = false;
5064  return false;
5065  }
5066  }
5067 
5068  // Remove any rights that aren't allowed to the global-session user,
5069  // unless there are no sessions for this endpoint.
5070  if ( !defined( 'MW_NO_SESSION' ) ) {
5071  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
5072  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
5073  $cache[$right] = false;
5074  return false;
5075  }
5076  }
5077 
5078  // Allow extensions to say false
5079  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
5080  $cache[$right] = false;
5081  return false;
5082  }
5083 
5084  $cache[$right] = true;
5085  return true;
5086  }
5087 
5095  public static function getGroupName( $group ) {
5096  wfDeprecated( __METHOD__, '1.29' );
5097  return UserGroupMembership::getGroupName( $group );
5098  }
5099 
5108  public static function getGroupMember( $group, $username = '#' ) {
5109  wfDeprecated( __METHOD__, '1.29' );
5111  }
5112 
5119  public static function getAllGroups() {
5121  return array_values( array_diff(
5122  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
5123  self::getImplicitGroups()
5124  ) );
5125  }
5126 
5131  public static function getAllRights() {
5132  if ( self::$mAllRights === false ) {
5133  global $wgAvailableRights;
5134  if ( count( $wgAvailableRights ) ) {
5135  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
5136  } else {
5137  self::$mAllRights = self::$mCoreRights;
5138  }
5139  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
5140  }
5141  return self::$mAllRights;
5142  }
5143 
5150  public static function getImplicitGroups() {
5151  global $wgImplicitGroups;
5152  return $wgImplicitGroups;
5153  }
5154 
5162  public static function getGroupPage( $group ) {
5163  wfDeprecated( __METHOD__, '1.29' );
5164  return UserGroupMembership::getGroupPage( $group );
5165  }
5166 
5177  public static function makeGroupLinkHTML( $group, $text = '' ) {
5178  wfDeprecated( __METHOD__, '1.29' );
5179 
5180  if ( $text == '' ) {
5181  $text = UserGroupMembership::getGroupName( $group );
5182  }
5184  if ( $title ) {
5185  return MediaWikiServices::getInstance()
5186  ->getLinkRenderer()->makeLink( $title, $text );
5187  } else {
5188  return htmlspecialchars( $text );
5189  }
5190  }
5191 
5202  public static function makeGroupLinkWiki( $group, $text = '' ) {
5203  wfDeprecated( __METHOD__, '1.29' );
5204 
5205  if ( $text == '' ) {
5206  $text = UserGroupMembership::getGroupName( $group );
5207  }
5209  if ( $title ) {
5210  $page = $title->getFullText();
5211  return "[[$page|$text]]";
5212  } else {
5213  return $text;
5214  }
5215  }
5216 
5226  public static function changeableByGroup( $group ) {
5228 
5229  $groups = [
5230  'add' => [],
5231  'remove' => [],
5232  'add-self' => [],
5233  'remove-self' => []
5234  ];
5235 
5236  if ( empty( $wgAddGroups[$group] ) ) {
5237  // Don't add anything to $groups
5238  } elseif ( $wgAddGroups[$group] === true ) {
5239  // You get everything
5240  $groups['add'] = self::getAllGroups();
5241  } elseif ( is_array( $wgAddGroups[$group] ) ) {
5242  $groups['add'] = $wgAddGroups[$group];
5243  }
5244 
5245  // Same thing for remove
5246  if ( empty( $wgRemoveGroups[$group] ) ) {
5247  // Do nothing
5248  } elseif ( $wgRemoveGroups[$group] === true ) {
5249  $groups['remove'] = self::getAllGroups();
5250  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5251  $groups['remove'] = $wgRemoveGroups[$group];
5252  }
5253 
5254  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5255  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5256  foreach ( $wgGroupsAddToSelf as $key => $value ) {
5257  if ( is_int( $key ) ) {
5258  $wgGroupsAddToSelf['user'][] = $value;
5259  }
5260  }
5261  }
5262 
5263  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5264  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5265  if ( is_int( $key ) ) {
5266  $wgGroupsRemoveFromSelf['user'][] = $value;
5267  }
5268  }
5269  }
5270 
5271  // Now figure out what groups the user can add to him/herself
5272  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5273  // Do nothing
5274  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5275  // No idea WHY this would be used, but it's there
5276  $groups['add-self'] = self::getAllGroups();
5277  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5278  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5279  }
5280 
5281  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5282  // Do nothing
5283  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5284  $groups['remove-self'] = self::getAllGroups();
5285  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5286  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5287  }
5288 
5289  return $groups;
5290  }
5291 
5299  public function changeableGroups() {
5300  if ( $this->isAllowed( 'userrights' ) ) {
5301  // This group gives the right to modify everything (reverse-
5302  // compatibility with old "userrights lets you change
5303  // everything")
5304  // Using array_merge to make the groups reindexed
5305  $all = array_merge( self::getAllGroups() );
5306  return [
5307  'add' => $all,
5308  'remove' => $all,
5309  'add-self' => [],
5310  'remove-self' => []
5311  ];
5312  }
5313 
5314  // Okay, it's not so simple, we will have to go through the arrays
5315  $groups = [
5316  'add' => [],
5317  'remove' => [],
5318  'add-self' => [],
5319  'remove-self' => []
5320  ];
5321  $addergroups = $this->getEffectiveGroups();
5322 
5323  foreach ( $addergroups as $addergroup ) {
5324  $groups = array_merge_recursive(
5325  $groups, $this->changeableByGroup( $addergroup )
5326  );
5327  $groups['add'] = array_unique( $groups['add'] );
5328  $groups['remove'] = array_unique( $groups['remove'] );
5329  $groups['add-self'] = array_unique( $groups['add-self'] );
5330  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5331  }
5332  return $groups;
5333  }
5334 
5338  public function incEditCount() {
5339  if ( $this->isAnon() ) {
5340  return; // sanity
5341  }
5342 
5344  new UserEditCountUpdate( $this, 1 ),
5346  );
5347  }
5348 
5354  public function setEditCountInternal( $count ) {
5355  $this->mEditCount = $count;
5356  }
5357 
5365  public function initEditCountInternal() {
5366  // Pull from a replica DB to be less cruel to servers
5367  // Accuracy isn't the point anyway here
5368  $dbr = wfGetDB( DB_REPLICA );
5369  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5370  $count = (int)$dbr->selectField(
5371  [ 'revision' ] + $actorWhere['tables'],
5372  'COUNT(*)',
5373  [ $actorWhere['conds'] ],
5374  __METHOD__,
5375  [],
5376  $actorWhere['joins']
5377  );
5378 
5379  $dbw = wfGetDB( DB_MASTER );
5380  $dbw->update(
5381  'user',
5382  [ 'user_editcount' => $count ],
5383  [
5384  'user_id' => $this->getId(),
5385  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5386  ],
5387  __METHOD__
5388  );
5389 
5390  return $count;
5391  }
5392 
5400  public static function getRightDescription( $right ) {
5401  $key = "right-$right";
5402  $msg = wfMessage( $key );
5403  return $msg->isDisabled() ? $right : $msg->text();
5404  }
5405 
5413  public static function getGrantName( $grant ) {
5414  $key = "grant-$grant";
5415  $msg = wfMessage( $key );
5416  return $msg->isDisabled() ? $grant : $msg->text();
5417  }
5418 
5439  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5440  return true; // disabled
5441  }
5442 
5451  public function addNewUserLogEntryAutoCreate() {
5452  $this->addNewUserLogEntry( 'autocreate' );
5453 
5454  return true;
5455  }
5456 
5462  protected function loadOptions( $data = null ) {
5463  $this->load();
5464 
5465  if ( $this->mOptionsLoaded ) {
5466  return;
5467  }
5468 
5469  $this->mOptions = self::getDefaultOptions();
5470 
5471  if ( !$this->getId() ) {
5472  // For unlogged-in users, load language/variant options from request.
5473  // There's no need to do it for logged-in users: they can set preferences,
5474  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5475  // so don't override user's choice (especially when the user chooses site default).
5476  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5477  $this->mOptions['variant'] = $variant;
5478  $this->mOptions['language'] = $variant;
5479  $this->mOptionsLoaded = true;
5480  return;
5481  }
5482 
5483  // Maybe load from the object
5484  if ( !is_null( $this->mOptionOverrides ) ) {
5485  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5486  foreach ( $this->mOptionOverrides as $key => $value ) {
5487  $this->mOptions[$key] = $value;
5488  }
5489  } else {
5490  if ( !is_array( $data ) ) {
5491  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5492  // Load from database
5493  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5494  ? wfGetDB( DB_MASTER )
5495  : wfGetDB( DB_REPLICA );
5496 
5497  $res = $dbr->select(
5498  'user_properties',
5499  [ 'up_property', 'up_value' ],
5500  [ 'up_user' => $this->getId() ],
5501  __METHOD__
5502  );
5503 
5504  $this->mOptionOverrides = [];
5505  $data = [];
5506  foreach ( $res as $row ) {
5507  // Convert '0' to 0. PHP's boolean conversion considers them both
5508  // false, but e.g. JavaScript considers the former as true.
5509  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5510  // and convert all values here.
5511  if ( $row->up_value === '0' ) {
5512  $row->up_value = 0;
5513  }
5514  $data[$row->up_property] = $row->up_value;
5515  }
5516  }
5517 
5518  foreach ( $data as $property => $value ) {
5519  $this->mOptionOverrides[$property] = $value;
5520  $this->mOptions[$property] = $value;
5521  }
5522  }
5523 
5524  // Replace deprecated language codes
5525  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5526  $this->mOptions['language']
5527  );
5528 
5529  $this->mOptionsLoaded = true;
5530 
5531  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5532  }
5533 
5539  protected function saveOptions() {
5540  $this->loadOptions();
5541 
5542  // Not using getOptions(), to keep hidden preferences in database
5543  $saveOptions = $this->mOptions;
5544 
5545  // Allow hooks to abort, for instance to save to a global profile.
5546  // Reset options to default state before saving.
5547  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5548  return;
5549  }
5550 
5551  $userId = $this->getId();
5552 
5553  $insert_rows = []; // all the new preference rows
5554  foreach ( $saveOptions as $key => $value ) {
5555  // Don't bother storing default values
5556  $defaultOption = self::getDefaultOption( $key );
5557  if ( ( $defaultOption === null && $value !== false && $value !== null )
5558  || $value != $defaultOption
5559  ) {
5560  $insert_rows[] = [
5561  'up_user' => $userId,
5562  'up_property' => $key,
5563  'up_value' => $value,
5564  ];
5565  }
5566  }
5567 
5568  $dbw = wfGetDB( DB_MASTER );
5569 
5570  $res = $dbw->select( 'user_properties',
5571  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5572 
5573  // Find prior rows that need to be removed or updated. These rows will
5574  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5575  $keysDelete = [];
5576  foreach ( $res as $row ) {
5577  if ( !isset( $saveOptions[$row->up_property] )
5578  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5579  ) {
5580  $keysDelete[] = $row->up_property;
5581  }
5582  }
5583 
5584  if ( count( $keysDelete ) ) {
5585  // Do the DELETE by PRIMARY KEY for prior rows.
5586  // In the past a very large portion of calls to this function are for setting
5587  // 'rememberpassword' for new accounts (a preference that has since been removed).
5588  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5589  // caused gap locks on [max user ID,+infinity) which caused high contention since
5590  // updates would pile up on each other as they are for higher (newer) user IDs.
5591  // It might not be necessary these days, but it shouldn't hurt either.
5592  $dbw->delete( 'user_properties',
5593  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5594  }
5595  // Insert the new preference rows
5596  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5597  }
5598 
5605  public static function selectFields() {
5606  wfDeprecated( __METHOD__, '1.31' );
5607  return [
5608  'user_id',
5609  'user_name',
5610  'user_real_name',
5611  'user_email',
5612  'user_touched',
5613  'user_token',
5614  'user_email_authenticated',
5615  'user_email_token',
5616  'user_email_token_expires',
5617  'user_registration',
5618  'user_editcount',
5619  ];
5620  }
5621 
5631  public static function getQueryInfo() {
5633 
5634  $ret = [
5635  'tables' => [ 'user' ],
5636  'fields' => [
5637  'user_id',
5638  'user_name',
5639  'user_real_name',
5640  'user_email',
5641  'user_touched',
5642  'user_token',
5643  'user_email_authenticated',
5644  'user_email_token',
5645  'user_email_token_expires',
5646  'user_registration',
5647  'user_editcount',
5648  ],
5649  'joins' => [],
5650  ];
5651 
5652  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
5653  // but it does little harm and might be needed for write callers loading a User.
5654  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
5655  $ret['tables']['user_actor'] = 'actor';
5656  $ret['fields'][] = 'user_actor.actor_id';
5657  $ret['joins']['user_actor'] = [
5658  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
5659  [ 'user_actor.actor_user = user_id' ]
5660  ];
5661  }
5662 
5663  return $ret;
5664  }
5665 
5673  static function newFatalPermissionDeniedStatus( $permission ) {
5674  global $wgLang;
5675 
5676  $groups = [];
5677  foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5678  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5679  }
5680 
5681  if ( $groups ) {
5682  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5683  } else {
5684  return Status::newFatal( 'badaccess-group0' );
5685  }
5686  }
5687 
5697  public function getInstanceForUpdate() {
5698  if ( !$this->getId() ) {
5699  return null; // anon
5700  }
5701 
5702  $user = self::newFromId( $this->getId() );
5703  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5704  return null;
5705  }
5706 
5707  return $user;
5708  }
5709 
5717  public function equals( UserIdentity $user ) {
5718  // XXX it's not clear whether central ID providers are supposed to obey this
5719  return $this->getName() === $user->getName();
5720  }
5721 }
getEmail()
Get the user&#39;s e-mail address.
Definition: User.php:3052
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
static randomPassword()
Return a random password.
Definition: User.php:1296
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
isHidden()
Check if user account is hidden.
Definition: User.php:2418
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition: User.php:1694
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition: User.php:5162
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:2908
string $mBlockedby
Definition: User.php:272
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:138
const VERSION
int Serialized record version.
Definition: User.php:68
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
static getMainWANInstance()
Get the main WAN cache object.
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:2627
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:3156
$wgMaxArticleSize
Maximum article size in kilobytes.
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1358
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2607
isAllowedAll()
Definition: User.php:3841
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1750
string $mDatePreference
Definition: User.php:270
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4568
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:891
static newFromID( $id)
Load a blocked user from their block id.
Definition: Block.php:194
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:101
either a plain
Definition: hooks.txt:2058
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3934
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:4059
$property
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1384
$wgMaxNameChars
Maximum number of bytes in username.
UserGroupMembership [] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:236
const TYPE_RANGE
Definition: Block.php:93
static getGroupName( $group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:5095
$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:1369
const NS_MAIN
Definition: Defines.php:64
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:5226
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4540
$success
Block $mBlockedFromCreateAccount
Definition: User.php:304
logout()
Log this user out.
Definition: User.php:4140
clearNotification(&$title, $oldid=0)
Clear the user&#39;s notification timestamp for the given title.
Definition: User.php:3968
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4975
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:5150
saveSettings()
Save this user&#39;s settings into the database.
Definition: User.php:4188
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:2453
prevents( $action, $x=null)
Get/set whether the Block prevents a given action.
Definition: Block.php:1149
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:4947
static $mCoreRights
Array of Strings Core rights.
Definition: User.php:119
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:1997
static generateRandomPasswordString( $minLength=10)
Generate a random string suitable for a password.
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:5045
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:973
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:2839
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:74
initEditCountInternal()
Initialize user_editcount from data out of the revision table.
Definition: User.php:5365
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3826
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4521
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3616
const TOKEN_LENGTH
int Number of characters in user_token field.
Definition: User.php:51
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2824
Handles increment the edit count for a given set of users.
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
static getWikiIdFromDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:254
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:2354
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target&#39;s type, get an existing Block object if possible.
Definition: Block.php:1264
setName( $str)
Set the user name.
Definition: User.php:2489
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 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object '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:1277
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3171
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2323
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:222
$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.
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:296
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:4065
__toString()
Definition: User.php:329
static $idCacheByName
Definition: User.php:309
Exception thrown when an actor can&#39;t be created.
null for the local wiki Added in
Definition: hooks.txt:1599
getRealName()
Get the user&#39;s real name.
Definition: User.php:3144
$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:2665
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:4735
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:1712
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5299
clearAllNotifications()
Resets all of the given user&#39;s page-change notification timestamps.
Definition: User.php:4036
static getCurrentWikiDomain()
Definition: WikiMap.php:276
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:287
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4684
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3793
static clearCookie(WebResponse $response)
Unset the &#39;BlockID&#39; cookie.
Definition: Block.php:1644
const TYPE_IP
Definition: Block.php:92
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
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:3024
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:911
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:732
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:4531
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4613
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
isBlocked( $bFromReplica=true)
Check if user is blocked.
Definition: User.php:2279
$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:5631
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:953
array $mFormerGroups
Definition: User.php:284
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2873
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:630
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3258
static getGroupMember( $group, $username='#')
Get the localized descriptive name for a member of a group, if it exists.
Definition: User.php:5108
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2368
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1999
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1814
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:742
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user&#39;s session (e.g.
Definition: User.php:4104
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2462
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5605
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4449
string $mName
Cache variables.
Definition: User.php:211
string $mRegistration
Cache variables.
Definition: User.php:232
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
$wgDnsBlacklistUrls
List of DNS blacklists to use, if $wgEnableDnsBlacklist is true.
int $mEditCount
Cache variables.
Definition: User.php:234
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2959
int null $mActorId
Cache variables.
Definition: User.php:213
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3951
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:1995
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4578
getTitleKey()
Get the user&#39;s name escaped by underscores.
Definition: User.php:2561
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:5119
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3341
$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:1083
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:2100
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:47
string $mEmailTokenExpires
Cache variables.
Definition: User.php:230
static getAllRights()
Get a list of all available permissions.
Definition: User.php:5131
isEmailConfirmed()
Is this user&#39;s e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4892
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:1997
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:4654
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1240
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4499
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:464
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:901
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:245
makeGlobalKey( $class, $component=null)
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2680
string $mEmailToken
Cache variables.
Definition: User.php:228
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:3279
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:1642
$wgPasswordPolicy
Password policy for local wiki users.
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4933
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects...
Definition: User.php:3603
$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:902
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5400
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:1123
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4779
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:517
static newFromIDs( $ids)
Definition: UserArray.php:45
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5451
$wgExperiencedUserEdits
Name of the external diff engine to use.
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1153
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:5002
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:152
getBoolOption( $oname)
Get the user&#39;s current setting for a given option, as a boolean value.
Definition: User.php:3230
Block $mBlock
Definition: User.php:298
string $mBlockreason
Definition: User.php:278
isAnon()
Get whether the user is anonymous.
Definition: User.php:3801
static getGroupMemberName( $group, $username)
Gets the localized name for a member of a group, if it exists.
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4806
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:2066
Stores a single person&#39;s name and email address.
Definition: MailAddress.php:32
static purge( $wikiId, $userId)
Definition: User.php:494
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:286
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5413
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3307
static getGroupName( $group)
Gets the localized friendly name for a group, if it exists.
$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:307
$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:4630
doLogout()
Clear the user&#39;s session, and reset the instance cache.
Definition: User.php:4152
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:364
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5717
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:529
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4918
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:54
incEditCount()
Schedule a deferred update to update the user&#39;s edit count.
Definition: User.php:5338
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2757
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:2020
$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:3364
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1438
$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:220
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4861
array $mEffectiveGroups
Definition: User.php:280
$cache
Definition: mcc.php:33
$wgAvailableRights
A list of available rights, in addition to the ones defined by the core.
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2921
const IGNORE_USER_RIGHTS
Definition: User.php:84
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
$params
$wgProxyWhitelist
Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other methods mi...
string $mEmailAuthenticated
Cache variables.
Definition: User.php:226
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3856
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:1997
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:
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3920
WebRequest $mRequest
Definition: User.php:295
$wgRemoveGroups
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format...
Definition: MWCryptRand.php:74
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1348
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:1048
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:1404
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:936
requiresHTTPS()
Determine based on the wiki configuration and the user&#39;s options, whether this user must be over HTTP...
Definition: User.php:3514
$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:2301
array $mImplicitGroups
Definition: User.php:282
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:204
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1608
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition: User.php:1164
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they&#39;ve successfully logged in from...
Definition: User.php:4480
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
static hasFlags( $bitfield, $flags)
$wgClockSkewFudge
Clock skew or the one-second resolution of time() can occasionally cause cache problems when the user...
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
isBot()
Definition: User.php:3809
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4850
$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:1781
static getGroupPage( $group)
Gets the title of a page describing a particular user group.
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:5354
$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 probably a stub 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:785
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:658
clearSharedCache( $mode='changed')
Clear user data from memcached.
Definition: User.php:2778
string $mToken
Cache variables.
Definition: User.php:224
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:121
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:5025
getToken( $forceCreation=true)
Get the user&#39;s current token.
Definition: User.php:2986
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:956
static newInvalidPassword()
Create an InvalidPassword.
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3869
$wgPasswordSender
Sender email address for e-mail notifications.
$wgLearnerMemberSince
Name of the external diff engine to use.
loadDefaults( $name=false)
Set cached properties to default.
Definition: User.php:1309
array $mOptions
Definition: User.php:292
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1820
string [] $wgSoftBlockRanges
IP ranges that should be considered soft-blocked (anon-only, account creation allowed).
$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:3044
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:268
invalidateEmail()
Invalidate the user&#39;s e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4836
loadGroups()
Load the groups from the database if they aren&#39;t already loaded.
Definition: User.php:1618
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5539
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:573
isIPRange()
Is the user an IP range?
Definition: User.php:984
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:545
$wgApplyIpBlocksToXff
Whether to look at the X-Forwarded-For header&#39;s list of (potentially spoofed) IPs and apply IP blocks...
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3533
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3903
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:218
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition: Block.php:1303
this hook is for auditing only $req
Definition: hooks.txt:990
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the &#39;BlockID&#39; cookie.
Definition: Block.php:1680
getNewtalk()
Check if the user has new messages.
Definition: User.php:2569
bool $mLocked
Definition: User.php:288
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:785
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:615
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1490
$wgUseEnotif
Definition: Setup.php:440
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:3590
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:682
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:322
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:3242
getBlockFromCookieValue( $blockCookieVal)
Try to load a Block from an ID given in a cookie value.
Definition: User.php:1948
idForName( $flags=0)
If only this user&#39;s username is known, and it exists, return the user ID.
Definition: User.php:4266
getOptions( $flags=0)
Get all user&#39;s options.
Definition: User.php:3199
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
$wgMinimalPasswordLength
Specifies the minimal length of a user password.
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they&#39;ve successfully logged in from...
Definition: User.php:4467
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:310
getBlock( $bFromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2289
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:999
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3890
getId()
Get the user&#39;s ID.
Definition: User.php:2437
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4670
getEmailAuthenticationTimestamp()
Get the timestamp of the user&#39;s e-mail authentication.
Definition: User.php:3062
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:2137
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5462
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition: User.php:5177
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name. ...
Definition: User.php:5202
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4377
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:2500
Relational database abstraction object.
Definition: Database.php:48
$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:778
bool $mAllowUsertalk
Definition: User.php:301
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2807
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:4819
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5673
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
bool $mHideName
Definition: User.php:290
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:756
getEditCount()
Get the user&#39;s edit count.
Definition: User.php:3692
getUserPage()
Get this user&#39;s personal page title.
Definition: User.php:4549
setNewtalk( $val, $curRev=null)
Update the &#39;You have new messages!&#39; status.
Definition: User.php:2725
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:5697
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3878
const SCHEMA_COMPAT_NEW
Definition: Defines.php:291
the value of this variable comes from LanguageConverter indexed by page_id indexed by prefixed DB keys on which the links will be shown can modify can modify can modify this should be populated with an alert message to that effect to be fed to an HTMLForm object $context
Definition: hooks.txt:1690
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
const TYPE_USER
Definition: Block.php:91
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3494
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2341
array $mRights
Definition: User.php:276
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:668
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:4696
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2638
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:263
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:276
isLocked()
Check if user account is locked.
Definition: User.php:2401
$messages
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:747
string $mHash
Definition: User.php:274
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2705
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4878
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:4788
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1200
setEmailWithConfirmation( $str)
Set the user&#39;s e-mail address and a confirmation mail if needed.
Definition: User.php:3089
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4301
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3639
array $mOptionOverrides
Cache variables.
Definition: User.php:238
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:592
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1775
const INVALID_TOKEN
string An invalid value for user_token
Definition: User.php:56
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2895
string $mRealName
Cache variables.
Definition: User.php:215
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3727
isSafeToLoad()
Test if it&#39;s safe to load this User object.
Definition: User.php:347
getBlockedStatus( $bFromReplica=true)
Get blocking information.
Definition: User.php:1831
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:119
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:505
setEmail( $str)
Set the user&#39;s e-mail address.
Definition: User.php:3072
const NS_USER_TALK
Definition: Defines.php:67
static array $languagesWithVariants
languages supporting variants
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5439
getTalkPage()
Get this user&#39;s talk page title.
Definition: User.php:4558
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
const EDIT_TOKEN_SUFFIX
Global constant made accessible as class constants so that autoloader magic can be used...
Definition: User.php:63
Definition: Block.php:29
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:2112
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:2627
static singleton()
Definition: UserCache.php:34
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3763
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:92
const CHECK_USER_RIGHTS
Definition: User.php:79
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:1487
getRights()
Get the permissions this user has.
Definition: User.php:3548
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3668
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2332
$wgEnableDnsBlacklist
Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies.
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:3449
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:819
int $mId
Cache variables.
Definition: User.php:209
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:4761
getTouched()
Get the user touched timestamp.
Definition: User.php:2851
Block $mGlobalBlock
Definition: User.php:286
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:250
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:280