MediaWiki  1.32.0
User.php
Go to the documentation of this file.
1 <?php
31 use Wikimedia\IPSet;
32 use Wikimedia\ScopedCallback;
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.
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 ) {
913  if ( is_null( $nt ) ) {
914  // Illegal name
915  return null;
916  }
917 
918  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
919  return self::$idCacheByName[$name];
920  }
921 
922  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
923  $db = wfGetDB( $index );
924 
925  $s = $db->selectRow(
926  'user',
927  [ 'user_id' ],
928  [ 'user_name' => $nt->getText() ],
929  __METHOD__,
930  $options
931  );
932 
933  if ( $s === false ) {
934  $result = null;
935  } else {
936  $result = $s->user_id;
937  }
938 
939  self::$idCacheByName[$name] = $result;
940 
941  if ( count( self::$idCacheByName ) > 1000 ) {
942  self::$idCacheByName = [];
943  }
944 
945  return $result;
946  }
947 
951  public static function resetIdByNameCache() {
952  self::$idCacheByName = [];
953  }
954 
971  public static function isIP( $name ) {
972  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
973  || IP::isIPv6( $name );
974  }
975 
982  public function isIPRange() {
983  return IP::isValidRange( $this->mName );
984  }
985 
997  public static function isValidUserName( $name ) {
998  global $wgMaxNameChars;
999 
1000  if ( $name == ''
1001  || self::isIP( $name )
1002  || strpos( $name, '/' ) !== false
1003  || strlen( $name ) > $wgMaxNameChars
1004  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
1005  ) {
1006  return false;
1007  }
1008 
1009  // Ensure that the name can't be misresolved as a different title,
1010  // such as with extra namespace keys at the start.
1011  $parsed = Title::newFromText( $name );
1012  if ( is_null( $parsed )
1013  || $parsed->getNamespace()
1014  || strcmp( $name, $parsed->getPrefixedText() ) ) {
1015  return false;
1016  }
1017 
1018  // Check an additional blacklist of troublemaker characters.
1019  // Should these be merged into the title char list?
1020  $unicodeBlacklist = '/[' .
1021  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
1022  '\x{00a0}' . # non-breaking space
1023  '\x{2000}-\x{200f}' . # various whitespace
1024  '\x{2028}-\x{202f}' . # breaks and control chars
1025  '\x{3000}' . # ideographic space
1026  '\x{e000}-\x{f8ff}' . # private use
1027  ']/u';
1028  if ( preg_match( $unicodeBlacklist, $name ) ) {
1029  return false;
1030  }
1031 
1032  return true;
1033  }
1034 
1046  public static function isUsableName( $name ) {
1047  global $wgReservedUsernames;
1048  // Must be a valid username, obviously ;)
1049  if ( !self::isValidUserName( $name ) ) {
1050  return false;
1051  }
1052 
1053  static $reservedUsernames = false;
1054  if ( !$reservedUsernames ) {
1055  $reservedUsernames = $wgReservedUsernames;
1056  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
1057  }
1058 
1059  // Certain names may be reserved for batch processes.
1060  foreach ( $reservedUsernames as $reserved ) {
1061  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1062  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1063  }
1064  if ( $reserved == $name ) {
1065  return false;
1066  }
1067  }
1068  return true;
1069  }
1070 
1081  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1082  if ( $groups === [] ) {
1083  return UserArrayFromResult::newFromIDs( [] );
1084  }
1085 
1086  $groups = array_unique( (array)$groups );
1087  $limit = min( 5000, $limit );
1088 
1089  $conds = [ 'ug_group' => $groups ];
1090  if ( $after !== null ) {
1091  $conds[] = 'ug_user > ' . (int)$after;
1092  }
1093 
1094  $dbr = wfGetDB( DB_REPLICA );
1095  $ids = $dbr->selectFieldValues(
1096  'user_groups',
1097  'ug_user',
1098  $conds,
1099  __METHOD__,
1100  [
1101  'DISTINCT' => true,
1102  'ORDER BY' => 'ug_user',
1103  'LIMIT' => $limit,
1104  ]
1105  ) ?: [];
1106  return UserArray::newFromIDs( $ids );
1107  }
1108 
1121  public static function isCreatableName( $name ) {
1123 
1124  // Ensure that the username isn't longer than 235 bytes, so that
1125  // (at least for the builtin skins) user javascript and css files
1126  // will work. (T25080)
1127  if ( strlen( $name ) > 235 ) {
1128  wfDebugLog( 'username', __METHOD__ .
1129  ": '$name' invalid due to length" );
1130  return false;
1131  }
1132 
1133  // Preg yells if you try to give it an empty string
1134  if ( $wgInvalidUsernameCharacters !== '' ) {
1135  if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
1136  wfDebugLog( 'username', __METHOD__ .
1137  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1138  return false;
1139  }
1140  }
1141 
1142  return self::isUsableName( $name );
1143  }
1144 
1151  public function isValidPassword( $password ) {
1152  // simple boolean wrapper for getPasswordValidity
1153  return $this->getPasswordValidity( $password ) === true;
1154  }
1155 
1162  public function getPasswordValidity( $password ) {
1163  $result = $this->checkPasswordValidity( $password );
1164  if ( $result->isGood() ) {
1165  return true;
1166  } else {
1167  $messages = [];
1168  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1169  $messages[] = $error['message'];
1170  }
1171  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1172  $messages[] = $warning['message'];
1173  }
1174  if ( count( $messages ) === 1 ) {
1175  return $messages[0];
1176  }
1177  return $messages;
1178  }
1179  }
1180 
1198  public function checkPasswordValidity( $password ) {
1199  global $wgPasswordPolicy;
1200 
1201  $upp = new UserPasswordPolicy(
1202  $wgPasswordPolicy['policies'],
1203  $wgPasswordPolicy['checks']
1204  );
1205 
1207  $result = false; // init $result to false for the internal checks
1208 
1209  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1210  $status->error( $result );
1211  return $status;
1212  }
1213 
1214  if ( $result === false ) {
1215  $status->merge( $upp->checkUserPassword( $this, $password ) );
1216  return $status;
1217  } elseif ( $result === true ) {
1218  return $status;
1219  } else {
1220  $status->error( $result );
1221  return $status; // the isValidPassword hook set a string $result and returned true
1222  }
1223  }
1224 
1238  public static function getCanonicalName( $name, $validate = 'valid' ) {
1239  // Force usernames to capital
1240  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1241 
1242  # Reject names containing '#'; these will be cleaned up
1243  # with title normalisation, but then it's too late to
1244  # check elsewhere
1245  if ( strpos( $name, '#' ) !== false ) {
1246  return false;
1247  }
1248 
1249  // Clean up name according to title rules,
1250  // but only when validation is requested (T14654)
1251  $t = ( $validate !== false ) ?
1253  // Check for invalid titles
1254  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1255  return false;
1256  }
1257 
1258  // Reject various classes of invalid names
1259  $name = AuthManager::callLegacyAuthPlugin(
1260  'getCanonicalName', [ $t->getText() ], $t->getText()
1261  );
1262 
1263  switch ( $validate ) {
1264  case false:
1265  break;
1266  case 'valid':
1267  if ( !self::isValidUserName( $name ) ) {
1268  $name = false;
1269  }
1270  break;
1271  case 'usable':
1272  if ( !self::isUsableName( $name ) ) {
1273  $name = false;
1274  }
1275  break;
1276  case 'creatable':
1277  if ( !self::isCreatableName( $name ) ) {
1278  $name = false;
1279  }
1280  break;
1281  default:
1282  throw new InvalidArgumentException(
1283  'Invalid parameter value for $validate in ' . __METHOD__ );
1284  }
1285  return $name;
1286  }
1287 
1294  public static function randomPassword() {
1295  global $wgMinimalPasswordLength;
1297  }
1298 
1307  public function loadDefaults( $name = false ) {
1308  $this->mId = 0;
1309  $this->mName = $name;
1310  $this->mActorId = null;
1311  $this->mRealName = '';
1312  $this->mEmail = '';
1313  $this->mOptionOverrides = null;
1314  $this->mOptionsLoaded = false;
1315 
1316  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1317  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1318  if ( $loggedOut !== 0 ) {
1319  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1320  } else {
1321  $this->mTouched = '1'; # Allow any pages to be cached
1322  }
1323 
1324  $this->mToken = null; // Don't run cryptographic functions till we need a token
1325  $this->mEmailAuthenticated = null;
1326  $this->mEmailToken = '';
1327  $this->mEmailTokenExpires = null;
1328  $this->mRegistration = wfTimestamp( TS_MW );
1329  $this->mGroupMemberships = [];
1330 
1331  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1332  }
1333 
1346  public function isItemLoaded( $item, $all = 'all' ) {
1347  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1348  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1349  }
1350 
1356  protected function setItemLoaded( $item ) {
1357  if ( is_array( $this->mLoadedItems ) ) {
1358  $this->mLoadedItems[$item] = true;
1359  }
1360  }
1361 
1367  private function loadFromSession() {
1368  // Deprecated hook
1369  $result = null;
1370  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1371  if ( $result !== null ) {
1372  return $result;
1373  }
1374 
1375  // MediaWiki\Session\Session already did the necessary authentication of the user
1376  // returned here, so just use it if applicable.
1377  $session = $this->getRequest()->getSession();
1378  $user = $session->getUser();
1379  if ( $user->isLoggedIn() ) {
1380  $this->loadFromUserObject( $user );
1381  if ( $user->isBlocked() ) {
1382  // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1383  // every session load, because an autoblocked editor might not edit again from the same
1384  // IP address after being blocked.
1385  $this->trackBlockWithCookie();
1386  }
1387 
1388  // Other code expects these to be set in the session, so set them.
1389  $session->set( 'wsUserID', $this->getId() );
1390  $session->set( 'wsUserName', $this->getName() );
1391  $session->set( 'wsToken', $this->getToken() );
1392 
1393  return true;
1394  }
1395 
1396  return false;
1397  }
1398 
1402  public function trackBlockWithCookie() {
1403  $block = $this->getBlock();
1404  if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null ) {
1405  $config = RequestContext::getMain()->getConfig();
1406  $shouldSetCookie = false;
1407 
1408  if ( $this->isAnon() && $config->get( 'CookieSetOnIpBlock' ) ) {
1409  // If user is logged-out, set a cookie to track the Block
1410  $shouldSetCookie = in_array( $block->getType(), [
1412  ] );
1413  if ( $shouldSetCookie ) {
1414  $block->setCookie( $this->getRequest()->response() );
1415 
1416  // temporary measure the use of cookies on ip blocks
1417  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1418  $stats->increment( 'block.ipblock.setCookie.success' );
1419  }
1420  } elseif ( $this->isLoggedIn() && $config->get( 'CookieSetOnAutoblock' ) ) {
1421  $shouldSetCookie = $block->getType() === Block::TYPE_USER && $block->isAutoblocking();
1422  if ( $shouldSetCookie ) {
1423  $block->setCookie( $this->getRequest()->response() );
1424  }
1425  }
1426  }
1427  }
1428 
1436  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1437  // Paranoia
1438  $this->mId = intval( $this->mId );
1439 
1440  if ( !$this->mId ) {
1441  // Anonymous users are not in the database
1442  $this->loadDefaults();
1443  return false;
1444  }
1445 
1446  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1447  $db = wfGetDB( $index );
1448 
1449  $userQuery = self::getQueryInfo();
1450  $s = $db->selectRow(
1451  $userQuery['tables'],
1452  $userQuery['fields'],
1453  [ 'user_id' => $this->mId ],
1454  __METHOD__,
1455  $options,
1456  $userQuery['joins']
1457  );
1458 
1459  $this->queryFlagsUsed = $flags;
1460  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1461 
1462  if ( $s !== false ) {
1463  // Initialise user table data
1464  $this->loadFromRow( $s );
1465  $this->mGroupMemberships = null; // deferred
1466  $this->getEditCount(); // revalidation for nulls
1467  return true;
1468  } else {
1469  // Invalid user_id
1470  $this->mId = 0;
1471  $this->loadDefaults();
1472  return false;
1473  }
1474  }
1475 
1488  protected function loadFromRow( $row, $data = null ) {
1490 
1491  if ( !is_object( $row ) ) {
1492  throw new InvalidArgumentException( '$row must be an object' );
1493  }
1494 
1495  $all = true;
1496 
1497  $this->mGroupMemberships = null; // deferred
1498 
1499  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
1500  // but it does little harm and might be needed for write callers loading a User.
1502  if ( isset( $row->actor_id ) ) {
1503  $this->mActorId = (int)$row->actor_id;
1504  if ( $this->mActorId !== 0 ) {
1505  $this->mFrom = 'actor';
1506  }
1507  $this->setItemLoaded( 'actor' );
1508  } else {
1509  $all = false;
1510  }
1511  }
1512 
1513  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1514  $this->mName = $row->user_name;
1515  $this->mFrom = 'name';
1516  $this->setItemLoaded( 'name' );
1517  } else {
1518  $all = false;
1519  }
1520 
1521  if ( isset( $row->user_real_name ) ) {
1522  $this->mRealName = $row->user_real_name;
1523  $this->setItemLoaded( 'realname' );
1524  } else {
1525  $all = false;
1526  }
1527 
1528  if ( isset( $row->user_id ) ) {
1529  $this->mId = intval( $row->user_id );
1530  if ( $this->mId !== 0 ) {
1531  $this->mFrom = 'id';
1532  }
1533  $this->setItemLoaded( 'id' );
1534  } else {
1535  $all = false;
1536  }
1537 
1538  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1539  self::$idCacheByName[$row->user_name] = $row->user_id;
1540  }
1541 
1542  if ( isset( $row->user_editcount ) ) {
1543  $this->mEditCount = $row->user_editcount;
1544  } else {
1545  $all = false;
1546  }
1547 
1548  if ( isset( $row->user_touched ) ) {
1549  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1550  } else {
1551  $all = false;
1552  }
1553 
1554  if ( isset( $row->user_token ) ) {
1555  // The definition for the column is binary(32), so trim the NULs
1556  // that appends. The previous definition was char(32), so trim
1557  // spaces too.
1558  $this->mToken = rtrim( $row->user_token, " \0" );
1559  if ( $this->mToken === '' ) {
1560  $this->mToken = null;
1561  }
1562  } else {
1563  $all = false;
1564  }
1565 
1566  if ( isset( $row->user_email ) ) {
1567  $this->mEmail = $row->user_email;
1568  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1569  $this->mEmailToken = $row->user_email_token;
1570  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1571  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1572  } else {
1573  $all = false;
1574  }
1575 
1576  if ( $all ) {
1577  $this->mLoadedItems = true;
1578  }
1579 
1580  if ( is_array( $data ) ) {
1581  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1582  if ( !count( $data['user_groups'] ) ) {
1583  $this->mGroupMemberships = [];
1584  } else {
1585  $firstGroup = reset( $data['user_groups'] );
1586  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1587  $this->mGroupMemberships = [];
1588  foreach ( $data['user_groups'] as $row ) {
1589  $ugm = UserGroupMembership::newFromRow( (object)$row );
1590  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1591  }
1592  }
1593  }
1594  }
1595  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1596  $this->loadOptions( $data['user_properties'] );
1597  }
1598  }
1599  }
1600 
1606  protected function loadFromUserObject( $user ) {
1607  $user->load();
1608  foreach ( self::$mCacheVars as $var ) {
1609  $this->$var = $user->$var;
1610  }
1611  }
1612 
1616  private function loadGroups() {
1617  if ( is_null( $this->mGroupMemberships ) ) {
1618  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1619  ? wfGetDB( DB_MASTER )
1620  : wfGetDB( DB_REPLICA );
1621  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1622  $this->mId, $db );
1623  }
1624  }
1625 
1640  public function addAutopromoteOnceGroups( $event ) {
1642 
1643  if ( wfReadOnly() || !$this->getId() ) {
1644  return [];
1645  }
1646 
1647  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1648  if ( !count( $toPromote ) ) {
1649  return [];
1650  }
1651 
1652  if ( !$this->checkAndSetTouched() ) {
1653  return []; // raced out (bug T48834)
1654  }
1655 
1656  $oldGroups = $this->getGroups(); // previous groups
1657  $oldUGMs = $this->getGroupMemberships();
1658  foreach ( $toPromote as $group ) {
1659  $this->addGroup( $group );
1660  }
1661  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1662  $newUGMs = $this->getGroupMemberships();
1663 
1664  // update groups in external authentication database
1665  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1666  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1667 
1668  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1669  $logEntry->setPerformer( $this );
1670  $logEntry->setTarget( $this->getUserPage() );
1671  $logEntry->setParameters( [
1672  '4::oldgroups' => $oldGroups,
1673  '5::newgroups' => $newGroups,
1674  ] );
1675  $logid = $logEntry->insert();
1676  if ( $wgAutopromoteOnceLogInRC ) {
1677  $logEntry->publish( $logid );
1678  }
1679 
1680  return $toPromote;
1681  }
1682 
1692  protected function makeUpdateConditions( Database $db, array $conditions ) {
1693  if ( $this->mTouched ) {
1694  // CAS check: only update if the row wasn't changed sicne it was loaded.
1695  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1696  }
1697 
1698  return $conditions;
1699  }
1700 
1710  protected function checkAndSetTouched() {
1711  $this->load();
1712 
1713  if ( !$this->mId ) {
1714  return false; // anon
1715  }
1716 
1717  // Get a new user_touched that is higher than the old one
1718  $newTouched = $this->newTouchedTimestamp();
1719 
1720  $dbw = wfGetDB( DB_MASTER );
1721  $dbw->update( 'user',
1722  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1723  $this->makeUpdateConditions( $dbw, [
1724  'user_id' => $this->mId,
1725  ] ),
1726  __METHOD__
1727  );
1728  $success = ( $dbw->affectedRows() > 0 );
1729 
1730  if ( $success ) {
1731  $this->mTouched = $newTouched;
1732  $this->clearSharedCache();
1733  } else {
1734  // Clears on failure too since that is desired if the cache is stale
1735  $this->clearSharedCache( 'refresh' );
1736  }
1737 
1738  return $success;
1739  }
1740 
1748  public function clearInstanceCache( $reloadFrom = false ) {
1749  $this->mNewtalk = -1;
1750  $this->mDatePreference = null;
1751  $this->mBlockedby = -1; # Unset
1752  $this->mHash = false;
1753  $this->mRights = null;
1754  $this->mEffectiveGroups = null;
1755  $this->mImplicitGroups = null;
1756  $this->mGroupMemberships = null;
1757  $this->mOptions = null;
1758  $this->mOptionsLoaded = false;
1759  $this->mEditCount = null;
1760 
1761  if ( $reloadFrom ) {
1762  $this->mLoadedItems = [];
1763  $this->mFrom = $reloadFrom;
1764  }
1765  }
1766 
1773  public static function getDefaultOptions() {
1775 
1776  static $defOpt = null;
1777  static $defOptLang = null;
1778 
1779  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1780  if ( $defOpt !== null && $defOptLang === $contLang->getCode() ) {
1781  // The content language does not change (and should not change) mid-request, but the
1782  // unit tests change it anyway, and expect this method to return values relevant to the
1783  // current content language.
1784  return $defOpt;
1785  }
1786 
1787  $defOpt = $wgDefaultUserOptions;
1788  // Default language setting
1789  $defOptLang = $contLang->getCode();
1790  $defOpt['language'] = $defOptLang;
1791  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1792  if ( $langCode === $contLang->getCode() ) {
1793  $defOpt['variant'] = $langCode;
1794  } else {
1795  $defOpt["variant-$langCode"] = $langCode;
1796  }
1797  }
1798 
1799  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1800  // since extensions may change the set of searchable namespaces depending
1801  // on user groups/permissions.
1802  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1803  $defOpt['searchNs' . $nsnum] = (bool)$val;
1804  }
1805  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1806 
1807  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1808 
1809  return $defOpt;
1810  }
1811 
1818  public static function getDefaultOption( $opt ) {
1819  $defOpts = self::getDefaultOptions();
1820  if ( isset( $defOpts[$opt] ) ) {
1821  return $defOpts[$opt];
1822  } else {
1823  return null;
1824  }
1825  }
1826 
1833  private function getBlockedStatus( $bFromSlave = true ) {
1835 
1836  if ( -1 != $this->mBlockedby ) {
1837  return;
1838  }
1839 
1840  wfDebug( __METHOD__ . ": checking...\n" );
1841 
1842  // Initialize data...
1843  // Otherwise something ends up stomping on $this->mBlockedby when
1844  // things get lazy-loaded later, causing false positive block hits
1845  // due to -1 !== 0. Probably session-related... Nothing should be
1846  // overwriting mBlockedby, surely?
1847  $this->load();
1848 
1849  # We only need to worry about passing the IP address to the Block generator if the
1850  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1851  # know which IP address they're actually coming from
1852  $ip = null;
1853  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1854  // $wgUser->getName() only works after the end of Setup.php. Until
1855  // then, assume it's a logged-out user.
1856  $globalUserName = $wgUser->isSafeToLoad()
1857  ? $wgUser->getName()
1858  : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1859  if ( $this->getName() === $globalUserName ) {
1860  $ip = $this->getRequest()->getIP();
1861  }
1862  }
1863 
1864  // User/IP blocking
1865  $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1866 
1867  // Cookie blocking
1868  if ( !$block instanceof Block ) {
1869  $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
1870  }
1871 
1872  // Proxy blocking
1873  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1874  // Local list
1875  if ( self::isLocallyBlockedProxy( $ip ) ) {
1876  $block = new Block( [
1877  'byText' => wfMessage( 'proxyblocker' )->text(),
1878  'reason' => wfMessage( 'proxyblockreason' )->plain(),
1879  'address' => $ip,
1880  'systemBlock' => 'proxy',
1881  ] );
1882  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1883  $block = new Block( [
1884  'byText' => wfMessage( 'sorbs' )->text(),
1885  'reason' => wfMessage( 'sorbsreason' )->plain(),
1886  'address' => $ip,
1887  'systemBlock' => 'dnsbl',
1888  ] );
1889  }
1890  }
1891 
1892  // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
1893  if ( !$block instanceof Block
1895  && $ip !== null
1896  && !in_array( $ip, $wgProxyWhitelist )
1897  ) {
1898  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1899  $xff = array_map( 'trim', explode( ',', $xff ) );
1900  $xff = array_diff( $xff, [ $ip ] );
1901  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1902  $block = Block::chooseBlock( $xffblocks, $xff );
1903  if ( $block instanceof Block ) {
1904  # Mangle the reason to alert the user that the block
1905  # originated from matching the X-Forwarded-For header.
1906  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->plain();
1907  }
1908  }
1909 
1910  if ( !$block instanceof Block
1911  && $ip !== null
1912  && $this->isAnon()
1914  ) {
1915  $block = new Block( [
1916  'address' => $ip,
1917  'byText' => 'MediaWiki default',
1918  'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
1919  'anonOnly' => true,
1920  'systemBlock' => 'wgSoftBlockRanges',
1921  ] );
1922  }
1923 
1924  if ( $block instanceof Block ) {
1925  wfDebug( __METHOD__ . ": Found block.\n" );
1926  $this->mBlock = $block;
1927  $this->mBlockedby = $block->getByName();
1928  $this->mBlockreason = $block->mReason;
1929  $this->mHideName = $block->mHideName;
1930  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1931  } else {
1932  $this->mBlock = null;
1933  $this->mBlockedby = '';
1934  $this->mBlockreason = '';
1935  $this->mHideName = 0;
1936  $this->mAllowUsertalk = false;
1937  }
1938 
1939  // Avoid PHP 7.1 warning of passing $this by reference
1940  $user = $this;
1941  // Extensions
1942  Hooks::run( 'GetBlockedStatus', [ &$user ] );
1943  }
1944 
1950  protected function getBlockFromCookieValue( $blockCookieVal ) {
1951  // Make sure there's something to check. The cookie value must start with a number.
1952  if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
1953  return false;
1954  }
1955  // Load the Block from the ID in the cookie.
1956  $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
1957  if ( $blockCookieId !== null ) {
1958  // An ID was found in the cookie.
1959  $tmpBlock = Block::newFromID( $blockCookieId );
1960  if ( $tmpBlock instanceof Block ) {
1961  $config = RequestContext::getMain()->getConfig();
1962 
1963  switch ( $tmpBlock->getType() ) {
1964  case Block::TYPE_USER:
1965  $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
1966  $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
1967  break;
1968  case Block::TYPE_IP:
1969  case Block::TYPE_RANGE:
1970  // If block is type IP or IP range, load only if user is not logged in (T152462)
1971  $blockIsValid = !$tmpBlock->isExpired() && !$this->isLoggedIn();
1972  $useBlockCookie = ( $config->get( 'CookieSetOnIpBlock' ) === true );
1973  break;
1974  default:
1975  $blockIsValid = false;
1976  $useBlockCookie = false;
1977  }
1978 
1979  if ( $blockIsValid && $useBlockCookie ) {
1980  // Use the block.
1981  return $tmpBlock;
1982  } else {
1983  // If the block is not valid, remove the cookie.
1984  Block::clearCookie( $this->getRequest()->response() );
1985  }
1986  } else {
1987  // If the block doesn't exist, remove the cookie.
1988  Block::clearCookie( $this->getRequest()->response() );
1989  }
1990  }
1991  return false;
1992  }
1993 
2001  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
2003 
2004  if ( !$wgEnableDnsBlacklist ) {
2005  return false;
2006  }
2007 
2008  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
2009  return false;
2010  }
2011 
2012  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
2013  }
2014 
2022  public function inDnsBlacklist( $ip, $bases ) {
2023  $found = false;
2024  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
2025  if ( IP::isIPv4( $ip ) ) {
2026  // Reverse IP, T23255
2027  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
2028 
2029  foreach ( (array)$bases as $base ) {
2030  // Make hostname
2031  // If we have an access key, use that too (ProjectHoneypot, etc.)
2032  $basename = $base;
2033  if ( is_array( $base ) ) {
2034  if ( count( $base ) >= 2 ) {
2035  // Access key is 1, base URL is 0
2036  $host = "{$base[1]}.$ipReversed.{$base[0]}";
2037  } else {
2038  $host = "$ipReversed.{$base[0]}";
2039  }
2040  $basename = $base[0];
2041  } else {
2042  $host = "$ipReversed.$base";
2043  }
2044 
2045  // Send query
2046  $ipList = gethostbynamel( $host );
2047 
2048  if ( $ipList ) {
2049  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
2050  $found = true;
2051  break;
2052  } else {
2053  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
2054  }
2055  }
2056  }
2057 
2058  return $found;
2059  }
2060 
2068  public static function isLocallyBlockedProxy( $ip ) {
2069  global $wgProxyList;
2070 
2071  if ( !$wgProxyList ) {
2072  return false;
2073  }
2074 
2075  if ( !is_array( $wgProxyList ) ) {
2076  // Load values from the specified file
2077  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
2078  }
2079 
2080  $resultProxyList = [];
2081  $deprecatedIPEntries = [];
2082 
2083  // backward compatibility: move all ip addresses in keys to values
2084  foreach ( $wgProxyList as $key => $value ) {
2085  $keyIsIP = IP::isIPAddress( $key );
2086  $valueIsIP = IP::isIPAddress( $value );
2087  if ( $keyIsIP && !$valueIsIP ) {
2088  $deprecatedIPEntries[] = $key;
2089  $resultProxyList[] = $key;
2090  } elseif ( $keyIsIP && $valueIsIP ) {
2091  $deprecatedIPEntries[] = $key;
2092  $resultProxyList[] = $key;
2093  $resultProxyList[] = $value;
2094  } else {
2095  $resultProxyList[] = $value;
2096  }
2097  }
2098 
2099  if ( $deprecatedIPEntries ) {
2100  wfDeprecated(
2101  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
2102  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
2103  }
2104 
2105  $proxyListIPSet = new IPSet( $resultProxyList );
2106  return $proxyListIPSet->match( $ip );
2107  }
2108 
2114  public function isPingLimitable() {
2115  global $wgRateLimitsExcludedIPs;
2116  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
2117  // No other good way currently to disable rate limits
2118  // for specific IPs. :P
2119  // But this is a crappy hack and should die.
2120  return false;
2121  }
2122  return !$this->isAllowed( 'noratelimit' );
2123  }
2124 
2139  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
2140  // Avoid PHP 7.1 warning of passing $this by reference
2141  $user = $this;
2142  // Call the 'PingLimiter' hook
2143  $result = false;
2144  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
2145  return $result;
2146  }
2147 
2148  global $wgRateLimits;
2149  if ( !isset( $wgRateLimits[$action] ) ) {
2150  return false;
2151  }
2152 
2153  $limits = array_merge(
2154  [ '&can-bypass' => true ],
2155  $wgRateLimits[$action]
2156  );
2157 
2158  // Some groups shouldn't trigger the ping limiter, ever
2159  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
2160  return false;
2161  }
2162 
2163  $keys = [];
2164  $id = $this->getId();
2165  $userLimit = false;
2166  $isNewbie = $this->isNewbie();
2168 
2169  if ( $id == 0 ) {
2170  // limits for anons
2171  if ( isset( $limits['anon'] ) ) {
2172  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
2173  }
2174  } else {
2175  // limits for logged-in users
2176  if ( isset( $limits['user'] ) ) {
2177  $userLimit = $limits['user'];
2178  }
2179  }
2180 
2181  // limits for anons and for newbie logged-in users
2182  if ( $isNewbie ) {
2183  // ip-based limits
2184  if ( isset( $limits['ip'] ) ) {
2185  $ip = $this->getRequest()->getIP();
2186  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
2187  }
2188  // subnet-based limits
2189  if ( isset( $limits['subnet'] ) ) {
2190  $ip = $this->getRequest()->getIP();
2191  $subnet = IP::getSubnet( $ip );
2192  if ( $subnet !== false ) {
2193  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
2194  }
2195  }
2196  }
2197 
2198  // Check for group-specific permissions
2199  // If more than one group applies, use the group with the highest limit ratio (max/period)
2200  foreach ( $this->getGroups() as $group ) {
2201  if ( isset( $limits[$group] ) ) {
2202  if ( $userLimit === false
2203  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2204  ) {
2205  $userLimit = $limits[$group];
2206  }
2207  }
2208  }
2209 
2210  // limits for newbie logged-in users (override all the normal user limits)
2211  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2212  $userLimit = $limits['newbie'];
2213  }
2214 
2215  // Set the user limit key
2216  if ( $userLimit !== false ) {
2217  list( $max, $period ) = $userLimit;
2218  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2219  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2220  }
2221 
2222  // ip-based limits for all ping-limitable users
2223  if ( isset( $limits['ip-all'] ) ) {
2224  $ip = $this->getRequest()->getIP();
2225  // ignore if user limit is more permissive
2226  if ( $isNewbie || $userLimit === false
2227  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2228  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2229  }
2230  }
2231 
2232  // subnet-based limits for all ping-limitable users
2233  if ( isset( $limits['subnet-all'] ) ) {
2234  $ip = $this->getRequest()->getIP();
2235  $subnet = IP::getSubnet( $ip );
2236  if ( $subnet !== false ) {
2237  // ignore if user limit is more permissive
2238  if ( $isNewbie || $userLimit === false
2239  || $limits['ip-all'][0] / $limits['ip-all'][1]
2240  > $userLimit[0] / $userLimit[1] ) {
2241  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2242  }
2243  }
2244  }
2245 
2246  $triggered = false;
2247  foreach ( $keys as $key => $limit ) {
2248  list( $max, $period ) = $limit;
2249  $summary = "(limit $max in {$period}s)";
2250  $count = $cache->get( $key );
2251  // Already pinged?
2252  if ( $count ) {
2253  if ( $count >= $max ) {
2254  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2255  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2256  $triggered = true;
2257  } else {
2258  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2259  }
2260  } else {
2261  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2262  if ( $incrBy > 0 ) {
2263  $cache->add( $key, 0, intval( $period ) ); // first ping
2264  }
2265  }
2266  if ( $incrBy > 0 ) {
2267  $cache->incr( $key, $incrBy );
2268  }
2269  }
2270 
2271  return $triggered;
2272  }
2273 
2281  public function isBlocked( $bFromSlave = true ) {
2282  return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
2283  }
2284 
2291  public function getBlock( $bFromSlave = true ) {
2292  $this->getBlockedStatus( $bFromSlave );
2293  return $this->mBlock instanceof Block ? $this->mBlock : null;
2294  }
2295 
2303  public function isBlockedFrom( $title, $bFromSlave = false ) {
2304  global $wgBlockAllowsUTEdit;
2305 
2306  $blocked = $this->isBlocked( $bFromSlave );
2307  $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
2308  // If a user's name is suppressed, they cannot make edits anywhere
2309  if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
2310  && $title->getNamespace() == NS_USER_TALK ) {
2311  $blocked = false;
2312  wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
2313  }
2314 
2315  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
2316 
2317  return $blocked;
2318  }
2319 
2324  public function blockedBy() {
2325  $this->getBlockedStatus();
2326  return $this->mBlockedby;
2327  }
2328 
2333  public function blockedFor() {
2334  $this->getBlockedStatus();
2335  return $this->mBlockreason;
2336  }
2337 
2342  public function getBlockId() {
2343  $this->getBlockedStatus();
2344  return ( $this->mBlock ? $this->mBlock->getId() : false );
2345  }
2346 
2355  public function isBlockedGlobally( $ip = '' ) {
2356  return $this->getGlobalBlock( $ip ) instanceof Block;
2357  }
2358 
2369  public function getGlobalBlock( $ip = '' ) {
2370  if ( $this->mGlobalBlock !== null ) {
2371  return $this->mGlobalBlock ?: null;
2372  }
2373  // User is already an IP?
2374  if ( IP::isIPAddress( $this->getName() ) ) {
2375  $ip = $this->getName();
2376  } elseif ( !$ip ) {
2377  $ip = $this->getRequest()->getIP();
2378  }
2379  // Avoid PHP 7.1 warning of passing $this by reference
2380  $user = $this;
2381  $blocked = false;
2382  $block = null;
2383  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2384 
2385  if ( $blocked && $block === null ) {
2386  // back-compat: UserIsBlockedGlobally didn't have $block param first
2387  $block = new Block( [
2388  'address' => $ip,
2389  'systemBlock' => 'global-block'
2390  ] );
2391  }
2392 
2393  $this->mGlobalBlock = $blocked ? $block : false;
2394  return $this->mGlobalBlock ?: null;
2395  }
2396 
2402  public function isLocked() {
2403  if ( $this->mLocked !== null ) {
2404  return $this->mLocked;
2405  }
2406  // Avoid PHP 7.1 warning of passing $this by reference
2407  $user = $this;
2408  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2409  $this->mLocked = $authUser && $authUser->isLocked();
2410  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2411  return $this->mLocked;
2412  }
2413 
2419  public function isHidden() {
2420  if ( $this->mHideName !== null ) {
2421  return $this->mHideName;
2422  }
2423  $this->getBlockedStatus();
2424  if ( !$this->mHideName ) {
2425  // Avoid PHP 7.1 warning of passing $this by reference
2426  $user = $this;
2427  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2428  $this->mHideName = $authUser && $authUser->isHidden();
2429  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2430  }
2431  return $this->mHideName;
2432  }
2433 
2438  public function getId() {
2439  if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2440  // Special case, we know the user is anonymous
2441  return 0;
2442  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2443  // Don't load if this was initialized from an ID
2444  $this->load();
2445  }
2446 
2447  return (int)$this->mId;
2448  }
2449 
2454  public function setId( $v ) {
2455  $this->mId = $v;
2456  $this->clearInstanceCache( 'id' );
2457  }
2458 
2463  public function getName() {
2464  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2465  // Special case optimisation
2466  return $this->mName;
2467  } else {
2468  $this->load();
2469  if ( $this->mName === false ) {
2470  // Clean up IPs
2471  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2472  }
2473  return $this->mName;
2474  }
2475  }
2476 
2490  public function setName( $str ) {
2491  $this->load();
2492  $this->mName = $str;
2493  }
2494 
2501  public function getActorId( IDatabase $dbw = null ) {
2503 
2504  // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
2505  // but it does little harm and might be needed for write callers loading a User.
2507  return 0;
2508  }
2509 
2510  if ( !$this->isItemLoaded( 'actor' ) ) {
2511  $this->load();
2512  }
2513 
2514  // Currently $this->mActorId might be null if $this was loaded from a
2515  // cache entry that was written when $wgActorTableSchemaMigrationStage
2516  // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
2517  // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
2518  // has been removed), that condition may be removed.
2519  if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2520  $q = [
2521  'actor_user' => $this->getId() ?: null,
2522  'actor_name' => (string)$this->getName(),
2523  ];
2524  if ( $dbw ) {
2525  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2526  throw new CannotCreateActorException(
2527  'Cannot create an actor for a usable name that is not an existing user'
2528  );
2529  }
2530  if ( $q['actor_name'] === '' ) {
2531  throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2532  }
2533  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2534  if ( $dbw->affectedRows() ) {
2535  $this->mActorId = (int)$dbw->insertId();
2536  } else {
2537  // Outdated cache?
2538  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2539  $this->mActorId = (int)$dbw->selectField(
2540  'actor',
2541  'actor_id',
2542  $q,
2543  __METHOD__,
2544  [ 'LOCK IN SHARE MODE' ]
2545  );
2546  if ( !$this->mActorId ) {
2547  throw new CannotCreateActorException(
2548  "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2549  );
2550  }
2551  }
2552  $this->invalidateCache();
2553  } else {
2554  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2555  $db = wfGetDB( $index );
2556  $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2557  }
2558  $this->setItemLoaded( 'actor' );
2559  }
2560 
2561  return (int)$this->mActorId;
2562  }
2563 
2568  public function getTitleKey() {
2569  return str_replace( ' ', '_', $this->getName() );
2570  }
2571 
2576  public function getNewtalk() {
2577  $this->load();
2578 
2579  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2580  if ( $this->mNewtalk === -1 ) {
2581  $this->mNewtalk = false; # reset talk page status
2582 
2583  // Check memcached separately for anons, who have no
2584  // entire User object stored in there.
2585  if ( !$this->mId ) {
2586  global $wgDisableAnonTalk;
2587  if ( $wgDisableAnonTalk ) {
2588  // Anon newtalk disabled by configuration.
2589  $this->mNewtalk = false;
2590  } else {
2591  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2592  }
2593  } else {
2594  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2595  }
2596  }
2597 
2598  return (bool)$this->mNewtalk;
2599  }
2600 
2614  public function getNewMessageLinks() {
2615  // Avoid PHP 7.1 warning of passing $this by reference
2616  $user = $this;
2617  $talks = [];
2618  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2619  return $talks;
2620  } elseif ( !$this->getNewtalk() ) {
2621  return [];
2622  }
2623  $utp = $this->getTalkPage();
2624  $dbr = wfGetDB( DB_REPLICA );
2625  // Get the "last viewed rev" timestamp from the oldest message notification
2626  $timestamp = $dbr->selectField( 'user_newtalk',
2627  'MIN(user_last_timestamp)',
2628  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2629  __METHOD__ );
2630  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2631  return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2632  }
2633 
2639  public function getNewMessageRevisionId() {
2640  $newMessageRevisionId = null;
2641  $newMessageLinks = $this->getNewMessageLinks();
2642  if ( $newMessageLinks ) {
2643  // Note: getNewMessageLinks() never returns more than a single link
2644  // and it is always for the same wiki, but we double-check here in
2645  // case that changes some time in the future.
2646  if ( count( $newMessageLinks ) === 1
2647  && $newMessageLinks[0]['wiki'] === wfWikiID()
2648  && $newMessageLinks[0]['rev']
2649  ) {
2651  $newMessageRevision = $newMessageLinks[0]['rev'];
2652  $newMessageRevisionId = $newMessageRevision->getId();
2653  }
2654  }
2655  return $newMessageRevisionId;
2656  }
2657 
2666  protected function checkNewtalk( $field, $id ) {
2667  $dbr = wfGetDB( DB_REPLICA );
2668 
2669  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2670 
2671  return $ok !== false;
2672  }
2673 
2681  protected function updateNewtalk( $field, $id, $curRev = null ) {
2682  // Get timestamp of the talk page revision prior to the current one
2683  $prevRev = $curRev ? $curRev->getPrevious() : false;
2684  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2685  // Mark the user as having new messages since this revision
2686  $dbw = wfGetDB( DB_MASTER );
2687  $dbw->insert( 'user_newtalk',
2688  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2689  __METHOD__,
2690  'IGNORE' );
2691  if ( $dbw->affectedRows() ) {
2692  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2693  return true;
2694  } else {
2695  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2696  return false;
2697  }
2698  }
2699 
2706  protected function deleteNewtalk( $field, $id ) {
2707  $dbw = wfGetDB( DB_MASTER );
2708  $dbw->delete( 'user_newtalk',
2709  [ $field => $id ],
2710  __METHOD__ );
2711  if ( $dbw->affectedRows() ) {
2712  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2713  return true;
2714  } else {
2715  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2716  return false;
2717  }
2718  }
2719 
2726  public function setNewtalk( $val, $curRev = null ) {
2727  if ( wfReadOnly() ) {
2728  return;
2729  }
2730 
2731  $this->load();
2732  $this->mNewtalk = $val;
2733 
2734  if ( $this->isAnon() ) {
2735  $field = 'user_ip';
2736  $id = $this->getName();
2737  } else {
2738  $field = 'user_id';
2739  $id = $this->getId();
2740  }
2741 
2742  if ( $val ) {
2743  $changed = $this->updateNewtalk( $field, $id, $curRev );
2744  } else {
2745  $changed = $this->deleteNewtalk( $field, $id );
2746  }
2747 
2748  if ( $changed ) {
2749  $this->invalidateCache();
2750  }
2751  }
2752 
2758  private function newTouchedTimestamp() {
2759  global $wgClockSkewFudge;
2760 
2761  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2762  if ( $this->mTouched && $time <= $this->mTouched ) {
2763  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2764  }
2765 
2766  return $time;
2767  }
2768 
2779  public function clearSharedCache( $mode = 'changed' ) {
2780  if ( !$this->getId() ) {
2781  return;
2782  }
2783 
2785  $key = $this->getCacheKey( $cache );
2786  if ( $mode === 'refresh' ) {
2787  $cache->delete( $key, 1 );
2788  } else {
2789  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2790  if ( $lb->hasOrMadeRecentMasterChanges() ) {
2791  $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
2792  function () use ( $cache, $key ) {
2793  $cache->delete( $key );
2794  },
2795  __METHOD__
2796  );
2797  } else {
2798  $cache->delete( $key );
2799  }
2800  }
2801  }
2802 
2808  public function invalidateCache() {
2809  $this->touch();
2810  $this->clearSharedCache();
2811  }
2812 
2825  public function touch() {
2826  $id = $this->getId();
2827  if ( $id ) {
2828  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2829  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2830  $cache->touchCheckKey( $key );
2831  $this->mQuickTouched = null;
2832  }
2833  }
2834 
2840  public function validateCache( $timestamp ) {
2841  return ( $timestamp >= $this->getTouched() );
2842  }
2843 
2852  public function getTouched() {
2853  $this->load();
2854 
2855  if ( $this->mId ) {
2856  if ( $this->mQuickTouched === null ) {
2857  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2858  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2859 
2860  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2861  }
2862 
2863  return max( $this->mTouched, $this->mQuickTouched );
2864  }
2865 
2866  return $this->mTouched;
2867  }
2868 
2874  public function getDBTouched() {
2875  $this->load();
2876 
2877  return $this->mTouched;
2878  }
2879 
2896  public function setPassword( $str ) {
2897  wfDeprecated( __METHOD__, '1.27' );
2898  return $this->setPasswordInternal( $str );
2899  }
2900 
2909  public function setInternalPassword( $str ) {
2910  wfDeprecated( __METHOD__, '1.27' );
2911  $this->setPasswordInternal( $str );
2912  }
2913 
2922  private function setPasswordInternal( $str ) {
2923  $manager = AuthManager::singleton();
2924 
2925  // If the user doesn't exist yet, fail
2926  if ( !$manager->userExists( $this->getName() ) ) {
2927  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2928  }
2929 
2930  $status = $this->changeAuthenticationData( [
2931  'username' => $this->getName(),
2932  'password' => $str,
2933  'retype' => $str,
2934  ] );
2935  if ( !$status->isGood() ) {
2937  ->info( __METHOD__ . ': Password change rejected: '
2938  . $status->getWikiText( null, null, 'en' ) );
2939  return false;
2940  }
2941 
2942  $this->setOption( 'watchlisttoken', false );
2943  SessionManager::singleton()->invalidateSessionsForUser( $this );
2944 
2945  return true;
2946  }
2947 
2960  public function changeAuthenticationData( array $data ) {
2961  $manager = AuthManager::singleton();
2962  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2963  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2964 
2965  $status = Status::newGood( 'ignored' );
2966  foreach ( $reqs as $req ) {
2967  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2968  }
2969  if ( $status->getValue() === 'ignored' ) {
2970  $status->warning( 'authenticationdatachange-ignored' );
2971  }
2972 
2973  if ( $status->isGood() ) {
2974  foreach ( $reqs as $req ) {
2975  $manager->changeAuthenticationData( $req );
2976  }
2977  }
2978  return $status;
2979  }
2980 
2987  public function getToken( $forceCreation = true ) {
2989 
2990  $this->load();
2991  if ( !$this->mToken && $forceCreation ) {
2992  $this->setToken();
2993  }
2994 
2995  if ( !$this->mToken ) {
2996  // The user doesn't have a token, return null to indicate that.
2997  return null;
2998  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2999  // We return a random value here so existing token checks are very
3000  // likely to fail.
3001  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
3002  } elseif ( $wgAuthenticationTokenVersion === null ) {
3003  // $wgAuthenticationTokenVersion not in use, so return the raw secret
3004  return $this->mToken;
3005  } else {
3006  // $wgAuthenticationTokenVersion in use, so hmac it.
3007  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
3008 
3009  // The raw hash can be overly long. Shorten it up.
3010  $len = max( 32, self::TOKEN_LENGTH );
3011  if ( strlen( $ret ) < $len ) {
3012  // Should never happen, even md5 is 128 bits
3013  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
3014  }
3015  return substr( $ret, -$len );
3016  }
3017  }
3018 
3025  public function setToken( $token = false ) {
3026  $this->load();
3027  if ( $this->mToken === self::INVALID_TOKEN ) {
3029  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
3030  } elseif ( !$token ) {
3031  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
3032  } else {
3033  $this->mToken = $token;
3034  }
3035  }
3036 
3045  public function setNewpassword( $str, $throttle = true ) {
3046  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
3047  }
3048 
3053  public function getEmail() {
3054  $this->load();
3055  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
3056  return $this->mEmail;
3057  }
3058 
3064  $this->load();
3065  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
3067  }
3068 
3073  public function setEmail( $str ) {
3074  $this->load();
3075  if ( $str == $this->mEmail ) {
3076  return;
3077  }
3078  $this->invalidateEmail();
3079  $this->mEmail = $str;
3080  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
3081  }
3082 
3090  public function setEmailWithConfirmation( $str ) {
3092 
3093  if ( !$wgEnableEmail ) {
3094  return Status::newFatal( 'emaildisabled' );
3095  }
3096 
3097  $oldaddr = $this->getEmail();
3098  if ( $str === $oldaddr ) {
3099  return Status::newGood( true );
3100  }
3101 
3102  $type = $oldaddr != '' ? 'changed' : 'set';
3103  $notificationResult = null;
3104 
3105  if ( $wgEmailAuthentication ) {
3106  // Send the user an email notifying the user of the change in registered
3107  // email address on their previous email address
3108  if ( $type == 'changed' ) {
3109  $change = $str != '' ? 'changed' : 'removed';
3110  $notificationResult = $this->sendMail(
3111  wfMessage( 'notificationemail_subject_' . $change )->text(),
3112  wfMessage( 'notificationemail_body_' . $change,
3113  $this->getRequest()->getIP(),
3114  $this->getName(),
3115  $str )->text()
3116  );
3117  }
3118  }
3119 
3120  $this->setEmail( $str );
3121 
3122  if ( $str !== '' && $wgEmailAuthentication ) {
3123  // Send a confirmation request to the new address if needed
3124  $result = $this->sendConfirmationMail( $type );
3125 
3126  if ( $notificationResult !== null ) {
3127  $result->merge( $notificationResult );
3128  }
3129 
3130  if ( $result->isGood() ) {
3131  // Say to the caller that a confirmation and notification mail has been sent
3132  $result->value = 'eauth';
3133  }
3134  } else {
3135  $result = Status::newGood( true );
3136  }
3137 
3138  return $result;
3139  }
3140 
3145  public function getRealName() {
3146  if ( !$this->isItemLoaded( 'realname' ) ) {
3147  $this->load();
3148  }
3149 
3150  return $this->mRealName;
3151  }
3152 
3157  public function setRealName( $str ) {
3158  $this->load();
3159  $this->mRealName = $str;
3160  }
3161 
3172  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3173  global $wgHiddenPrefs;
3174  $this->loadOptions();
3175 
3176  # We want 'disabled' preferences to always behave as the default value for
3177  # users, even if they have set the option explicitly in their settings (ie they
3178  # set it, and then it was disabled removing their ability to change it). But
3179  # we don't want to erase the preferences in the database in case the preference
3180  # is re-enabled again. So don't touch $mOptions, just override the returned value
3181  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3182  return self::getDefaultOption( $oname );
3183  }
3184 
3185  if ( array_key_exists( $oname, $this->mOptions ) ) {
3186  return $this->mOptions[$oname];
3187  } else {
3188  return $defaultOverride;
3189  }
3190  }
3191 
3200  public function getOptions( $flags = 0 ) {
3201  global $wgHiddenPrefs;
3202  $this->loadOptions();
3204 
3205  # We want 'disabled' preferences to always behave as the default value for
3206  # users, even if they have set the option explicitly in their settings (ie they
3207  # set it, and then it was disabled removing their ability to change it). But
3208  # we don't want to erase the preferences in the database in case the preference
3209  # is re-enabled again. So don't touch $mOptions, just override the returned value
3210  foreach ( $wgHiddenPrefs as $pref ) {
3211  $default = self::getDefaultOption( $pref );
3212  if ( $default !== null ) {
3213  $options[$pref] = $default;
3214  }
3215  }
3216 
3217  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3218  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3219  }
3220 
3221  return $options;
3222  }
3223 
3231  public function getBoolOption( $oname ) {
3232  return (bool)$this->getOption( $oname );
3233  }
3234 
3243  public function getIntOption( $oname, $defaultOverride = 0 ) {
3244  $val = $this->getOption( $oname );
3245  if ( $val == '' ) {
3246  $val = $defaultOverride;
3247  }
3248  return intval( $val );
3249  }
3250 
3259  public function setOption( $oname, $val ) {
3260  $this->loadOptions();
3261 
3262  // Explicitly NULL values should refer to defaults
3263  if ( is_null( $val ) ) {
3264  $val = self::getDefaultOption( $oname );
3265  }
3266 
3267  $this->mOptions[$oname] = $val;
3268  }
3269 
3280  public function getTokenFromOption( $oname ) {
3281  global $wgHiddenPrefs;
3282 
3283  $id = $this->getId();
3284  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3285  return false;
3286  }
3287 
3288  $token = $this->getOption( $oname );
3289  if ( !$token ) {
3290  // Default to a value based on the user token to avoid space
3291  // wasted on storing tokens for all users. When this option
3292  // is set manually by the user, only then is it stored.
3293  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3294  }
3295 
3296  return $token;
3297  }
3298 
3308  public function resetTokenFromOption( $oname ) {
3309  global $wgHiddenPrefs;
3310  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3311  return false;
3312  }
3313 
3314  $token = MWCryptRand::generateHex( 40 );
3315  $this->setOption( $oname, $token );
3316  return $token;
3317  }
3318 
3342  public static function listOptionKinds() {
3343  return [
3344  'registered',
3345  'registered-multiselect',
3346  'registered-checkmatrix',
3347  'userjs',
3348  'special',
3349  'unused'
3350  ];
3351  }
3352 
3365  public function getOptionKinds( IContextSource $context, $options = null ) {
3366  $this->loadOptions();
3367  if ( $options === null ) {
3369  }
3370 
3371  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3372  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3373  $mapping = [];
3374 
3375  // Pull out the "special" options, so they don't get converted as
3376  // multiselect or checkmatrix.
3377  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3378  foreach ( $specialOptions as $name => $value ) {
3379  unset( $prefs[$name] );
3380  }
3381 
3382  // Multiselect and checkmatrix options are stored in the database with
3383  // one key per option, each having a boolean value. Extract those keys.
3384  $multiselectOptions = [];
3385  foreach ( $prefs as $name => $info ) {
3386  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3387  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3388  $opts = HTMLFormField::flattenOptions( $info['options'] );
3389  $prefix = $info['prefix'] ?? $name;
3390 
3391  foreach ( $opts as $value ) {
3392  $multiselectOptions["$prefix$value"] = true;
3393  }
3394 
3395  unset( $prefs[$name] );
3396  }
3397  }
3398  $checkmatrixOptions = [];
3399  foreach ( $prefs as $name => $info ) {
3400  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3401  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3402  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3403  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3404  $prefix = $info['prefix'] ?? $name;
3405 
3406  foreach ( $columns as $column ) {
3407  foreach ( $rows as $row ) {
3408  $checkmatrixOptions["$prefix$column-$row"] = true;
3409  }
3410  }
3411 
3412  unset( $prefs[$name] );
3413  }
3414  }
3415 
3416  // $value is ignored
3417  foreach ( $options as $key => $value ) {
3418  if ( isset( $prefs[$key] ) ) {
3419  $mapping[$key] = 'registered';
3420  } elseif ( isset( $multiselectOptions[$key] ) ) {
3421  $mapping[$key] = 'registered-multiselect';
3422  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3423  $mapping[$key] = 'registered-checkmatrix';
3424  } elseif ( isset( $specialOptions[$key] ) ) {
3425  $mapping[$key] = 'special';
3426  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3427  $mapping[$key] = 'userjs';
3428  } else {
3429  $mapping[$key] = 'unused';
3430  }
3431  }
3432 
3433  return $mapping;
3434  }
3435 
3450  public function resetOptions(
3451  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3452  IContextSource $context = null
3453  ) {
3454  $this->load();
3455  $defaultOptions = self::getDefaultOptions();
3456 
3457  if ( !is_array( $resetKinds ) ) {
3458  $resetKinds = [ $resetKinds ];
3459  }
3460 
3461  if ( in_array( 'all', $resetKinds ) ) {
3462  $newOptions = $defaultOptions;
3463  } else {
3464  if ( $context === null ) {
3466  }
3467 
3468  $optionKinds = $this->getOptionKinds( $context );
3469  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3470  $newOptions = [];
3471 
3472  // Use default values for the options that should be deleted, and
3473  // copy old values for the ones that shouldn't.
3474  foreach ( $this->mOptions as $key => $value ) {
3475  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3476  if ( array_key_exists( $key, $defaultOptions ) ) {
3477  $newOptions[$key] = $defaultOptions[$key];
3478  }
3479  } else {
3480  $newOptions[$key] = $value;
3481  }
3482  }
3483  }
3484 
3485  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3486 
3487  $this->mOptions = $newOptions;
3488  $this->mOptionsLoaded = true;
3489  }
3490 
3495  public function getDatePreference() {
3496  // Important migration for old data rows
3497  if ( is_null( $this->mDatePreference ) ) {
3498  global $wgLang;
3499  $value = $this->getOption( 'date' );
3500  $map = $wgLang->getDatePreferenceMigrationMap();
3501  if ( isset( $map[$value] ) ) {
3502  $value = $map[$value];
3503  }
3504  $this->mDatePreference = $value;
3505  }
3506  return $this->mDatePreference;
3507  }
3508 
3515  public function requiresHTTPS() {
3516  global $wgSecureLogin;
3517  if ( !$wgSecureLogin ) {
3518  return false;
3519  } else {
3520  $https = $this->getBoolOption( 'prefershttps' );
3521  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3522  if ( $https ) {
3523  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3524  }
3525  return $https;
3526  }
3527  }
3528 
3534  public function getStubThreshold() {
3535  global $wgMaxArticleSize; # Maximum article size, in Kb
3536  $threshold = $this->getIntOption( 'stubthreshold' );
3537  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3538  // If they have set an impossible value, disable the preference
3539  // so we can use the parser cache again.
3540  $threshold = 0;
3541  }
3542  return $threshold;
3543  }
3544 
3549  public function getRights() {
3550  if ( is_null( $this->mRights ) ) {
3551  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3552  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3553 
3554  // Deny any rights denied by the user's session, unless this
3555  // endpoint has no sessions.
3556  if ( !defined( 'MW_NO_SESSION' ) ) {
3557  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3558  if ( $allowedRights !== null ) {
3559  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3560  }
3561  }
3562 
3563  Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
3564  // Force reindexation of rights when a hook has unset one of them
3565  $this->mRights = array_values( array_unique( $this->mRights ) );
3566 
3567  // If block disables login, we should also remove any
3568  // extra rights blocked users might have, in case the
3569  // blocked user has a pre-existing session (T129738).
3570  // This is checked here for cases where people only call
3571  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3572  // to give a better error message in the common case.
3573  $config = RequestContext::getMain()->getConfig();
3574  if (
3575  $this->isLoggedIn() &&
3576  $config->get( 'BlockDisablesLogin' ) &&
3577  $this->isBlocked()
3578  ) {
3579  $anon = new User;
3580  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3581  }
3582  }
3583  return $this->mRights;
3584  }
3585 
3591  public function getGroups() {
3592  $this->load();
3593  $this->loadGroups();
3594  return array_keys( $this->mGroupMemberships );
3595  }
3596 
3604  public function getGroupMemberships() {
3605  $this->load();
3606  $this->loadGroups();
3607  return $this->mGroupMemberships;
3608  }
3609 
3617  public function getEffectiveGroups( $recache = false ) {
3618  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3619  $this->mEffectiveGroups = array_unique( array_merge(
3620  $this->getGroups(), // explicit groups
3621  $this->getAutomaticGroups( $recache ) // implicit groups
3622  ) );
3623  // Avoid PHP 7.1 warning of passing $this by reference
3624  $user = $this;
3625  // Hook for additional groups
3626  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3627  // Force reindexation of groups when a hook has unset one of them
3628  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3629  }
3630  return $this->mEffectiveGroups;
3631  }
3632 
3640  public function getAutomaticGroups( $recache = false ) {
3641  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3642  $this->mImplicitGroups = [ '*' ];
3643  if ( $this->getId() ) {
3644  $this->mImplicitGroups[] = 'user';
3645 
3646  $this->mImplicitGroups = array_unique( array_merge(
3647  $this->mImplicitGroups,
3649  ) );
3650  }
3651  if ( $recache ) {
3652  // Assure data consistency with rights/groups,
3653  // as getEffectiveGroups() depends on this function
3654  $this->mEffectiveGroups = null;
3655  }
3656  }
3657  return $this->mImplicitGroups;
3658  }
3659 
3669  public function getFormerGroups() {
3670  $this->load();
3671 
3672  if ( is_null( $this->mFormerGroups ) ) {
3673  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3674  ? wfGetDB( DB_MASTER )
3675  : wfGetDB( DB_REPLICA );
3676  $res = $db->select( 'user_former_groups',
3677  [ 'ufg_group' ],
3678  [ 'ufg_user' => $this->mId ],
3679  __METHOD__ );
3680  $this->mFormerGroups = [];
3681  foreach ( $res as $row ) {
3682  $this->mFormerGroups[] = $row->ufg_group;
3683  }
3684  }
3685 
3686  return $this->mFormerGroups;
3687  }
3688 
3693  public function getEditCount() {
3694  if ( !$this->getId() ) {
3695  return null;
3696  }
3697 
3698  if ( $this->mEditCount === null ) {
3699  /* Populate the count, if it has not been populated yet */
3700  $dbr = wfGetDB( DB_REPLICA );
3701  // check if the user_editcount field has been initialized
3702  $count = $dbr->selectField(
3703  'user', 'user_editcount',
3704  [ 'user_id' => $this->mId ],
3705  __METHOD__
3706  );
3707 
3708  if ( $count === null ) {
3709  // it has not been initialized. do so.
3710  $count = $this->initEditCount();
3711  }
3712  $this->mEditCount = $count;
3713  }
3714  return (int)$this->mEditCount;
3715  }
3716 
3728  public function addGroup( $group, $expiry = null ) {
3729  $this->load();
3730  $this->loadGroups();
3731 
3732  if ( $expiry ) {
3733  $expiry = wfTimestamp( TS_MW, $expiry );
3734  }
3735 
3736  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3737  return false;
3738  }
3739 
3740  // create the new UserGroupMembership and put it in the DB
3741  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3742  if ( !$ugm->insert( true ) ) {
3743  return false;
3744  }
3745 
3746  $this->mGroupMemberships[$group] = $ugm;
3747 
3748  // Refresh the groups caches, and clear the rights cache so it will be
3749  // refreshed on the next call to $this->getRights().
3750  $this->getEffectiveGroups( true );
3751  $this->mRights = null;
3752 
3753  $this->invalidateCache();
3754 
3755  return true;
3756  }
3757 
3764  public function removeGroup( $group ) {
3765  $this->load();
3766 
3767  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3768  return false;
3769  }
3770 
3771  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3772  // delete the membership entry
3773  if ( !$ugm || !$ugm->delete() ) {
3774  return false;
3775  }
3776 
3777  $this->loadGroups();
3778  unset( $this->mGroupMemberships[$group] );
3779 
3780  // Refresh the groups caches, and clear the rights cache so it will be
3781  // refreshed on the next call to $this->getRights().
3782  $this->getEffectiveGroups( true );
3783  $this->mRights = null;
3784 
3785  $this->invalidateCache();
3786 
3787  return true;
3788  }
3789 
3794  public function isLoggedIn() {
3795  return $this->getId() != 0;
3796  }
3797 
3802  public function isAnon() {
3803  return !$this->isLoggedIn();
3804  }
3805 
3810  public function isBot() {
3811  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3812  return true;
3813  }
3814 
3815  $isBot = false;
3816  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3817 
3818  return $isBot;
3819  }
3820 
3827  public function isAllowedAny() {
3828  $permissions = func_get_args();
3829  foreach ( $permissions as $permission ) {
3830  if ( $this->isAllowed( $permission ) ) {
3831  return true;
3832  }
3833  }
3834  return false;
3835  }
3836 
3842  public function isAllowedAll() {
3843  $permissions = func_get_args();
3844  foreach ( $permissions as $permission ) {
3845  if ( !$this->isAllowed( $permission ) ) {
3846  return false;
3847  }
3848  }
3849  return true;
3850  }
3851 
3857  public function isAllowed( $action = '' ) {
3858  if ( $action === '' ) {
3859  return true; // In the spirit of DWIM
3860  }
3861  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3862  // by misconfiguration: 0 == 'foo'
3863  return in_array( $action, $this->getRights(), true );
3864  }
3865 
3870  public function useRCPatrol() {
3871  global $wgUseRCPatrol;
3872  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3873  }
3874 
3879  public function useNPPatrol() {
3881  return (
3883  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3884  );
3885  }
3886 
3891  public function useFilePatrol() {
3893  return (
3895  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3896  );
3897  }
3898 
3904  public function getRequest() {
3905  if ( $this->mRequest ) {
3906  return $this->mRequest;
3907  } else {
3908  global $wgRequest;
3909  return $wgRequest;
3910  }
3911  }
3912 
3921  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3922  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3923  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3924  }
3925  return false;
3926  }
3927 
3935  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3936  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3937  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3938  $this,
3939  [ $title->getSubjectPage(), $title->getTalkPage() ]
3940  );
3941  }
3942  $this->invalidateCache();
3943  }
3944 
3952  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3953  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3954  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3955  $store->removeWatch( $this, $title->getSubjectPage() );
3956  $store->removeWatch( $this, $title->getTalkPage() );
3957  }
3958  $this->invalidateCache();
3959  }
3960 
3969  public function clearNotification( &$title, $oldid = 0 ) {
3971 
3972  // Do nothing if the database is locked to writes
3973  if ( wfReadOnly() ) {
3974  return;
3975  }
3976 
3977  // Do nothing if not allowed to edit the watchlist
3978  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3979  return;
3980  }
3981 
3982  // If we're working on user's talk page, we should update the talk page message indicator
3983  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3984  // Avoid PHP 7.1 warning of passing $this by reference
3985  $user = $this;
3986  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3987  return;
3988  }
3989 
3990  // Try to update the DB post-send and only if needed...
3991  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3992  if ( !$this->getNewtalk() ) {
3993  return; // no notifications to clear
3994  }
3995 
3996  // Delete the last notifications (they stack up)
3997  $this->setNewtalk( false );
3998 
3999  // If there is a new, unseen, revision, use its timestamp
4000  $nextid = $oldid
4001  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
4002  : null;
4003  if ( $nextid ) {
4004  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
4005  }
4006  } );
4007  }
4008 
4009  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4010  return;
4011  }
4012 
4013  if ( $this->isAnon() ) {
4014  // Nothing else to do...
4015  return;
4016  }
4017 
4018  // Only update the timestamp if the page is being watched.
4019  // The query to find out if it is watched is cached both in memcached and per-invocation,
4020  // and when it does have to be executed, it can be on a replica DB
4021  // If this is the user's newtalk page, we always update the timestamp
4022  $force = '';
4023  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
4024  $force = 'force';
4025  }
4026 
4027  MediaWikiServices::getInstance()->getWatchedItemStore()
4028  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
4029  }
4030 
4037  public function clearAllNotifications() {
4039  // Do nothing if not allowed to edit the watchlist
4040  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
4041  return;
4042  }
4043 
4044  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4045  $this->setNewtalk( false );
4046  return;
4047  }
4048 
4049  $id = $this->getId();
4050  if ( !$id ) {
4051  return;
4052  }
4053 
4054  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
4055  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
4056 
4057  // We also need to clear here the "you have new message" notification for the own
4058  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
4059  }
4060 
4066  public function getExperienceLevel() {
4067  global $wgLearnerEdits,
4071 
4072  if ( $this->isAnon() ) {
4073  return false;
4074  }
4075 
4076  $editCount = $this->getEditCount();
4077  $registration = $this->getRegistration();
4078  $now = time();
4079  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
4080  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
4081 
4082  if (
4083  $editCount < $wgLearnerEdits ||
4084  $registration > $learnerRegistration
4085  ) {
4086  return 'newcomer';
4087  } elseif (
4088  $editCount > $wgExperiencedUserEdits &&
4089  $registration <= $experiencedRegistration
4090  ) {
4091  return 'experienced';
4092  } else {
4093  return 'learner';
4094  }
4095  }
4096 
4105  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
4106  $this->load();
4107  if ( 0 == $this->mId ) {
4108  return;
4109  }
4110 
4111  $session = $this->getRequest()->getSession();
4112  if ( $request && $session->getRequest() !== $request ) {
4113  $session = $session->sessionWithRequest( $request );
4114  }
4115  $delay = $session->delaySave();
4116 
4117  if ( !$session->getUser()->equals( $this ) ) {
4118  if ( !$session->canSetUser() ) {
4120  ->warning( __METHOD__ .
4121  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
4122  );
4123  return;
4124  }
4125  $session->setUser( $this );
4126  }
4127 
4128  $session->setRememberUser( $rememberMe );
4129  if ( $secure !== null ) {
4130  $session->setForceHTTPS( $secure );
4131  }
4132 
4133  $session->persist();
4134 
4135  ScopedCallback::consume( $delay );
4136  }
4137 
4141  public function logout() {
4142  // Avoid PHP 7.1 warning of passing $this by reference
4143  $user = $this;
4144  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
4145  $this->doLogout();
4146  }
4147  }
4148 
4153  public function doLogout() {
4154  $session = $this->getRequest()->getSession();
4155  if ( !$session->canSetUser() ) {
4157  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
4158  $error = 'immutable';
4159  } elseif ( !$session->getUser()->equals( $this ) ) {
4161  ->warning( __METHOD__ .
4162  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4163  );
4164  // But we still may as well make this user object anon
4165  $this->clearInstanceCache( 'defaults' );
4166  $error = 'wronguser';
4167  } else {
4168  $this->clearInstanceCache( 'defaults' );
4169  $delay = $session->delaySave();
4170  $session->unpersist(); // Clear cookies (T127436)
4171  $session->setLoggedOutTimestamp( time() );
4172  $session->setUser( new User );
4173  $session->set( 'wsUserID', 0 ); // Other code expects this
4174  $session->resetAllTokens();
4175  ScopedCallback::consume( $delay );
4176  $error = false;
4177  }
4178  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4179  'event' => 'logout',
4180  'successful' => $error === false,
4181  'status' => $error ?: 'success',
4182  ] );
4183  }
4184 
4189  public function saveSettings() {
4190  if ( wfReadOnly() ) {
4191  // @TODO: caller should deal with this instead!
4192  // This should really just be an exception.
4194  null,
4195  "Could not update user with ID '{$this->mId}'; DB is read-only."
4196  ) );
4197  return;
4198  }
4199 
4200  $this->load();
4201  if ( 0 == $this->mId ) {
4202  return; // anon
4203  }
4204 
4205  // Get a new user_touched that is higher than the old one.
4206  // This will be used for a CAS check as a last-resort safety
4207  // check against race conditions and replica DB lag.
4208  $newTouched = $this->newTouchedTimestamp();
4209 
4210  $dbw = wfGetDB( DB_MASTER );
4211  $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
4213 
4214  $dbw->update( 'user',
4215  [ /* SET */
4216  'user_name' => $this->mName,
4217  'user_real_name' => $this->mRealName,
4218  'user_email' => $this->mEmail,
4219  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4220  'user_touched' => $dbw->timestamp( $newTouched ),
4221  'user_token' => strval( $this->mToken ),
4222  'user_email_token' => $this->mEmailToken,
4223  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4224  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4225  'user_id' => $this->mId,
4226  ] ), $fname
4227  );
4228 
4229  if ( !$dbw->affectedRows() ) {
4230  // Maybe the problem was a missed cache update; clear it to be safe
4231  $this->clearSharedCache( 'refresh' );
4232  // User was changed in the meantime or loaded with stale data
4233  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4234  LoggerFactory::getInstance( 'preferences' )->warning(
4235  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4236  [ 'user_id' => $this->mId, 'db_flag' => $from ]
4237  );
4238  throw new MWException( "CAS update failed on user_touched. " .
4239  "The version of the user to be saved is older than the current version."
4240  );
4241  }
4242 
4244  $dbw->update(
4245  'actor',
4246  [ 'actor_name' => $this->mName ],
4247  [ 'actor_user' => $this->mId ],
4248  $fname
4249  );
4250  }
4251  } );
4252 
4253  $this->mTouched = $newTouched;
4254  $this->saveOptions();
4255 
4256  Hooks::run( 'UserSaveSettings', [ $this ] );
4257  $this->clearSharedCache();
4258  $this->getUserPage()->invalidateCache();
4259  }
4260 
4267  public function idForName( $flags = 0 ) {
4268  $s = trim( $this->getName() );
4269  if ( $s === '' ) {
4270  return 0;
4271  }
4272 
4273  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4274  ? wfGetDB( DB_MASTER )
4275  : wfGetDB( DB_REPLICA );
4276 
4277  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4278  ? [ 'LOCK IN SHARE MODE' ]
4279  : [];
4280 
4281  $id = $db->selectField( 'user',
4282  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4283 
4284  return (int)$id;
4285  }
4286 
4302  public static function createNew( $name, $params = [] ) {
4303  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4304  if ( isset( $params[$field] ) ) {
4305  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4306  unset( $params[$field] );
4307  }
4308  }
4309 
4310  $user = new User;
4311  $user->load();
4312  $user->setToken(); // init token
4313  if ( isset( $params['options'] ) ) {
4314  $user->mOptions = $params['options'] + (array)$user->mOptions;
4315  unset( $params['options'] );
4316  }
4317  $dbw = wfGetDB( DB_MASTER );
4318 
4319  $noPass = PasswordFactory::newInvalidPassword()->toString();
4320 
4321  $fields = [
4322  'user_name' => $name,
4323  'user_password' => $noPass,
4324  'user_newpassword' => $noPass,
4325  'user_email' => $user->mEmail,
4326  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4327  'user_real_name' => $user->mRealName,
4328  'user_token' => strval( $user->mToken ),
4329  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4330  'user_editcount' => 0,
4331  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4332  ];
4333  foreach ( $params as $name => $value ) {
4334  $fields["user_$name"] = $value;
4335  }
4336 
4337  return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
4338  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4339  if ( $dbw->affectedRows() ) {
4340  $newUser = self::newFromId( $dbw->insertId() );
4341  $newUser->mName = $fields['user_name'];
4342  $newUser->updateActorId( $dbw );
4343  // Load the user from master to avoid replica lag
4344  $newUser->load( self::READ_LATEST );
4345  } else {
4346  $newUser = null;
4347  }
4348  return $newUser;
4349  } );
4350  }
4351 
4378  public function addToDatabase() {
4379  $this->load();
4380  if ( !$this->mToken ) {
4381  $this->setToken(); // init token
4382  }
4383 
4384  if ( !is_string( $this->mName ) ) {
4385  throw new RuntimeException( "User name field is not set." );
4386  }
4387 
4388  $this->mTouched = $this->newTouchedTimestamp();
4389 
4390  $dbw = wfGetDB( DB_MASTER );
4391  $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
4392  $noPass = PasswordFactory::newInvalidPassword()->toString();
4393  $dbw->insert( 'user',
4394  [
4395  'user_name' => $this->mName,
4396  'user_password' => $noPass,
4397  'user_newpassword' => $noPass,
4398  'user_email' => $this->mEmail,
4399  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4400  'user_real_name' => $this->mRealName,
4401  'user_token' => strval( $this->mToken ),
4402  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4403  'user_editcount' => 0,
4404  'user_touched' => $dbw->timestamp( $this->mTouched ),
4405  ], $fname,
4406  [ 'IGNORE' ]
4407  );
4408  if ( !$dbw->affectedRows() ) {
4409  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4410  $this->mId = $dbw->selectField(
4411  'user',
4412  'user_id',
4413  [ 'user_name' => $this->mName ],
4414  $fname,
4415  [ 'LOCK IN SHARE MODE' ]
4416  );
4417  $loaded = false;
4418  if ( $this->mId ) {
4419  if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4420  $loaded = true;
4421  }
4422  }
4423  if ( !$loaded ) {
4424  throw new MWException( $fname . ": hit a key conflict attempting " .
4425  "to insert user '{$this->mName}' row, but it was not present in select!" );
4426  }
4427  return Status::newFatal( 'userexists' );
4428  }
4429  $this->mId = $dbw->insertId();
4430  self::$idCacheByName[$this->mName] = $this->mId;
4431  $this->updateActorId( $dbw );
4432 
4433  return Status::newGood();
4434  } );
4435  if ( !$status->isGood() ) {
4436  return $status;
4437  }
4438 
4439  // Clear instance cache other than user table data and actor, which is already accurate
4440  $this->clearInstanceCache();
4441 
4442  $this->saveOptions();
4443  return Status::newGood();
4444  }
4445 
4450  private function updateActorId( IDatabase $dbw ) {
4452 
4454  $dbw->insert(
4455  'actor',
4456  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4457  __METHOD__
4458  );
4459  $this->mActorId = (int)$dbw->insertId();
4460  }
4461  }
4462 
4468  public function spreadAnyEditBlock() {
4469  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4470  return $this->spreadBlock();
4471  }
4472 
4473  return false;
4474  }
4475 
4481  protected function spreadBlock() {
4482  wfDebug( __METHOD__ . "()\n" );
4483  $this->load();
4484  if ( $this->mId == 0 ) {
4485  return false;
4486  }
4487 
4488  $userblock = Block::newFromTarget( $this->getName() );
4489  if ( !$userblock ) {
4490  return false;
4491  }
4492 
4493  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4494  }
4495 
4500  public function isBlockedFromCreateAccount() {
4501  $this->getBlockedStatus();
4502  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4503  return $this->mBlock;
4504  }
4505 
4506  # T15611: if the IP address the user is trying to create an account from is
4507  # blocked with createaccount disabled, prevent new account creation there even
4508  # when the user is logged in
4509  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4510  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4511  }
4512  return $this->mBlockedFromCreateAccount instanceof Block
4513  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4514  ? $this->mBlockedFromCreateAccount
4515  : false;
4516  }
4517 
4522  public function isBlockedFromEmailuser() {
4523  $this->getBlockedStatus();
4524  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4525  }
4526 
4531  public function isAllowedToCreateAccount() {
4532  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4533  }
4534 
4540  public function getUserPage() {
4541  return Title::makeTitle( NS_USER, $this->getName() );
4542  }
4543 
4549  public function getTalkPage() {
4550  $title = $this->getUserPage();
4551  return $title->getTalkPage();
4552  }
4553 
4559  public function isNewbie() {
4560  return !$this->isAllowed( 'autoconfirmed' );
4561  }
4562 
4569  public function checkPassword( $password ) {
4570  wfDeprecated( __METHOD__, '1.27' );
4571 
4572  $manager = AuthManager::singleton();
4573  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4574  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4575  [
4576  'username' => $this->getName(),
4577  'password' => $password,
4578  ]
4579  );
4580  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4581  switch ( $res->status ) {
4582  case AuthenticationResponse::PASS:
4583  return true;
4584  case AuthenticationResponse::FAIL:
4585  // Hope it's not a PreAuthenticationProvider that failed...
4587  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4588  return false;
4589  default:
4590  throw new BadMethodCallException(
4591  'AuthManager returned a response unsupported by ' . __METHOD__
4592  );
4593  }
4594  }
4595 
4604  public function checkTemporaryPassword( $plaintext ) {
4605  wfDeprecated( __METHOD__, '1.27' );
4606  // Can't check the temporary password individually.
4607  return $this->checkPassword( $plaintext );
4608  }
4609 
4621  public function getEditTokenObject( $salt = '', $request = null ) {
4622  if ( $this->isAnon() ) {
4623  return new LoggedOutEditToken();
4624  }
4625 
4626  if ( !$request ) {
4627  $request = $this->getRequest();
4628  }
4629  return $request->getSession()->getToken( $salt );
4630  }
4631 
4645  public function getEditToken( $salt = '', $request = null ) {
4646  return $this->getEditTokenObject( $salt, $request )->toString();
4647  }
4648 
4661  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4662  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4663  }
4664 
4675  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4676  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4677  return $this->matchEditToken( $val, $salt, $request, $maxage );
4678  }
4679 
4687  public function sendConfirmationMail( $type = 'created' ) {
4688  global $wgLang;
4689  $expiration = null; // gets passed-by-ref and defined in next line.
4690  $token = $this->confirmationToken( $expiration );
4691  $url = $this->confirmationTokenUrl( $token );
4692  $invalidateURL = $this->invalidationTokenUrl( $token );
4693  $this->saveSettings();
4694 
4695  if ( $type == 'created' || $type === false ) {
4696  $message = 'confirmemail_body';
4697  } elseif ( $type === true ) {
4698  $message = 'confirmemail_body_changed';
4699  } else {
4700  // Messages: confirmemail_body_changed, confirmemail_body_set
4701  $message = 'confirmemail_body_' . $type;
4702  }
4703 
4704  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4705  wfMessage( $message,
4706  $this->getRequest()->getIP(),
4707  $this->getName(),
4708  $url,
4709  $wgLang->userTimeAndDate( $expiration, $this ),
4710  $invalidateURL,
4711  $wgLang->userDate( $expiration, $this ),
4712  $wgLang->userTime( $expiration, $this ) )->text() );
4713  }
4714 
4726  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4727  global $wgPasswordSender;
4728 
4729  if ( $from instanceof User ) {
4730  $sender = MailAddress::newFromUser( $from );
4731  } else {
4732  $sender = new MailAddress( $wgPasswordSender,
4733  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4734  }
4735  $to = MailAddress::newFromUser( $this );
4736 
4737  return UserMailer::send( $to, $sender, $subject, $body, [
4738  'replyTo' => $replyto,
4739  ] );
4740  }
4741 
4752  protected function confirmationToken( &$expiration ) {
4754  $now = time();
4755  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4756  $expiration = wfTimestamp( TS_MW, $expires );
4757  $this->load();
4758  $token = MWCryptRand::generateHex( 32 );
4759  $hash = md5( $token );
4760  $this->mEmailToken = $hash;
4761  $this->mEmailTokenExpires = $expiration;
4762  return $token;
4763  }
4764 
4770  protected function confirmationTokenUrl( $token ) {
4771  return $this->getTokenUrl( 'ConfirmEmail', $token );
4772  }
4773 
4779  protected function invalidationTokenUrl( $token ) {
4780  return $this->getTokenUrl( 'InvalidateEmail', $token );
4781  }
4782 
4797  protected function getTokenUrl( $page, $token ) {
4798  // Hack to bypass localization of 'Special:'
4799  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4800  return $title->getCanonicalURL();
4801  }
4802 
4810  public function confirmEmail() {
4811  // Check if it's already confirmed, so we don't touch the database
4812  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4813  if ( !$this->isEmailConfirmed() ) {
4815  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4816  }
4817  return true;
4818  }
4819 
4827  public function invalidateEmail() {
4828  $this->load();
4829  $this->mEmailToken = null;
4830  $this->mEmailTokenExpires = null;
4831  $this->setEmailAuthenticationTimestamp( null );
4832  $this->mEmail = '';
4833  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4834  return true;
4835  }
4836 
4841  public function setEmailAuthenticationTimestamp( $timestamp ) {
4842  $this->load();
4843  $this->mEmailAuthenticated = $timestamp;
4844  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4845  }
4846 
4852  public function canSendEmail() {
4854  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4855  return false;
4856  }
4857  $canSend = $this->isEmailConfirmed();
4858  // Avoid PHP 7.1 warning of passing $this by reference
4859  $user = $this;
4860  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4861  return $canSend;
4862  }
4863 
4869  public function canReceiveEmail() {
4870  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4871  }
4872 
4883  public function isEmailConfirmed() {
4884  global $wgEmailAuthentication;
4885  $this->load();
4886  // Avoid PHP 7.1 warning of passing $this by reference
4887  $user = $this;
4888  $confirmed = true;
4889  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4890  if ( $this->isAnon() ) {
4891  return false;
4892  }
4893  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4894  return false;
4895  }
4897  return false;
4898  }
4899  return true;
4900  } else {
4901  return $confirmed;
4902  }
4903  }
4904 
4909  public function isEmailConfirmationPending() {
4910  global $wgEmailAuthentication;
4911  return $wgEmailAuthentication &&
4912  !$this->isEmailConfirmed() &&
4913  $this->mEmailToken &&
4914  $this->mEmailTokenExpires > wfTimestamp();
4915  }
4916 
4924  public function getRegistration() {
4925  if ( $this->isAnon() ) {
4926  return false;
4927  }
4928  $this->load();
4929  return $this->mRegistration;
4930  }
4931 
4938  public function getFirstEditTimestamp() {
4939  if ( $this->getId() == 0 ) {
4940  return false; // anons
4941  }
4942  $dbr = wfGetDB( DB_REPLICA );
4943  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4944  $time = $dbr->selectField(
4945  [ 'revision' ] + $actorWhere['tables'],
4946  'rev_timestamp',
4947  [ $actorWhere['conds'] ],
4948  __METHOD__,
4949  [ 'ORDER BY' => 'rev_timestamp ASC' ],
4950  $actorWhere['joins']
4951  );
4952  if ( !$time ) {
4953  return false; // no edits
4954  }
4955  return wfTimestamp( TS_MW, $time );
4956  }
4957 
4964  public static function getGroupPermissions( $groups ) {
4966  $rights = [];
4967  // grant every granted permission first
4968  foreach ( $groups as $group ) {
4969  if ( isset( $wgGroupPermissions[$group] ) ) {
4970  $rights = array_merge( $rights,
4971  // array_filter removes empty items
4972  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4973  }
4974  }
4975  // now revoke the revoked permissions
4976  foreach ( $groups as $group ) {
4977  if ( isset( $wgRevokePermissions[$group] ) ) {
4978  $rights = array_diff( $rights,
4979  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4980  }
4981  }
4982  return array_unique( $rights );
4983  }
4984 
4991  public static function getGroupsWithPermission( $role ) {
4992  global $wgGroupPermissions;
4993  $allowedGroups = [];
4994  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4995  if ( self::groupHasPermission( $group, $role ) ) {
4996  $allowedGroups[] = $group;
4997  }
4998  }
4999  return $allowedGroups;
5000  }
5001 
5014  public static function groupHasPermission( $group, $role ) {
5016  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
5017  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
5018  }
5019 
5034  public static function isEveryoneAllowed( $right ) {
5036  static $cache = [];
5037 
5038  // Use the cached results, except in unit tests which rely on
5039  // being able change the permission mid-request
5040  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
5041  return $cache[$right];
5042  }
5043 
5044  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
5045  $cache[$right] = false;
5046  return false;
5047  }
5048 
5049  // If it's revoked anywhere, then everyone doesn't have it
5050  foreach ( $wgRevokePermissions as $rights ) {
5051  if ( isset( $rights[$right] ) && $rights[$right] ) {
5052  $cache[$right] = false;
5053  return false;
5054  }
5055  }
5056 
5057  // Remove any rights that aren't allowed to the global-session user,
5058  // unless there are no sessions for this endpoint.
5059  if ( !defined( 'MW_NO_SESSION' ) ) {
5060  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
5061  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
5062  $cache[$right] = false;
5063  return false;
5064  }
5065  }
5066 
5067  // Allow extensions to say false
5068  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
5069  $cache[$right] = false;
5070  return false;
5071  }
5072 
5073  $cache[$right] = true;
5074  return true;
5075  }
5076 
5084  public static function getGroupName( $group ) {
5085  wfDeprecated( __METHOD__, '1.29' );
5086  return UserGroupMembership::getGroupName( $group );
5087  }
5088 
5097  public static function getGroupMember( $group, $username = '#' ) {
5098  wfDeprecated( __METHOD__, '1.29' );
5100  }
5101 
5108  public static function getAllGroups() {
5110  return array_values( array_diff(
5111  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
5112  self::getImplicitGroups()
5113  ) );
5114  }
5115 
5120  public static function getAllRights() {
5121  if ( self::$mAllRights === false ) {
5122  global $wgAvailableRights;
5123  if ( count( $wgAvailableRights ) ) {
5124  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
5125  } else {
5126  self::$mAllRights = self::$mCoreRights;
5127  }
5128  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
5129  }
5130  return self::$mAllRights;
5131  }
5132 
5139  public static function getImplicitGroups() {
5140  global $wgImplicitGroups;
5141  return $wgImplicitGroups;
5142  }
5143 
5151  public static function getGroupPage( $group ) {
5152  wfDeprecated( __METHOD__, '1.29' );
5153  return UserGroupMembership::getGroupPage( $group );
5154  }
5155 
5166  public static function makeGroupLinkHTML( $group, $text = '' ) {
5167  wfDeprecated( __METHOD__, '1.29' );
5168 
5169  if ( $text == '' ) {
5170  $text = UserGroupMembership::getGroupName( $group );
5171  }
5173  if ( $title ) {
5174  return MediaWikiServices::getInstance()
5175  ->getLinkRenderer()->makeLink( $title, $text );
5176  } else {
5177  return htmlspecialchars( $text );
5178  }
5179  }
5180 
5191  public static function makeGroupLinkWiki( $group, $text = '' ) {
5192  wfDeprecated( __METHOD__, '1.29' );
5193 
5194  if ( $text == '' ) {
5195  $text = UserGroupMembership::getGroupName( $group );
5196  }
5198  if ( $title ) {
5199  $page = $title->getFullText();
5200  return "[[$page|$text]]";
5201  } else {
5202  return $text;
5203  }
5204  }
5205 
5215  public static function changeableByGroup( $group ) {
5217 
5218  $groups = [
5219  'add' => [],
5220  'remove' => [],
5221  'add-self' => [],
5222  'remove-self' => []
5223  ];
5224 
5225  if ( empty( $wgAddGroups[$group] ) ) {
5226  // Don't add anything to $groups
5227  } elseif ( $wgAddGroups[$group] === true ) {
5228  // You get everything
5229  $groups['add'] = self::getAllGroups();
5230  } elseif ( is_array( $wgAddGroups[$group] ) ) {
5231  $groups['add'] = $wgAddGroups[$group];
5232  }
5233 
5234  // Same thing for remove
5235  if ( empty( $wgRemoveGroups[$group] ) ) {
5236  // Do nothing
5237  } elseif ( $wgRemoveGroups[$group] === true ) {
5238  $groups['remove'] = self::getAllGroups();
5239  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5240  $groups['remove'] = $wgRemoveGroups[$group];
5241  }
5242 
5243  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5244  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5245  foreach ( $wgGroupsAddToSelf as $key => $value ) {
5246  if ( is_int( $key ) ) {
5247  $wgGroupsAddToSelf['user'][] = $value;
5248  }
5249  }
5250  }
5251 
5252  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5253  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5254  if ( is_int( $key ) ) {
5255  $wgGroupsRemoveFromSelf['user'][] = $value;
5256  }
5257  }
5258  }
5259 
5260  // Now figure out what groups the user can add to him/herself
5261  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5262  // Do nothing
5263  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5264  // No idea WHY this would be used, but it's there
5265  $groups['add-self'] = self::getAllGroups();
5266  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5267  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5268  }
5269 
5270  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5271  // Do nothing
5272  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5273  $groups['remove-self'] = self::getAllGroups();
5274  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5275  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5276  }
5277 
5278  return $groups;
5279  }
5280 
5288  public function changeableGroups() {
5289  if ( $this->isAllowed( 'userrights' ) ) {
5290  // This group gives the right to modify everything (reverse-
5291  // compatibility with old "userrights lets you change
5292  // everything")
5293  // Using array_merge to make the groups reindexed
5294  $all = array_merge( self::getAllGroups() );
5295  return [
5296  'add' => $all,
5297  'remove' => $all,
5298  'add-self' => [],
5299  'remove-self' => []
5300  ];
5301  }
5302 
5303  // Okay, it's not so simple, we will have to go through the arrays
5304  $groups = [
5305  'add' => [],
5306  'remove' => [],
5307  'add-self' => [],
5308  'remove-self' => []
5309  ];
5310  $addergroups = $this->getEffectiveGroups();
5311 
5312  foreach ( $addergroups as $addergroup ) {
5313  $groups = array_merge_recursive(
5314  $groups, $this->changeableByGroup( $addergroup )
5315  );
5316  $groups['add'] = array_unique( $groups['add'] );
5317  $groups['remove'] = array_unique( $groups['remove'] );
5318  $groups['add-self'] = array_unique( $groups['add-self'] );
5319  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5320  }
5321  return $groups;
5322  }
5323 
5330  public function incEditCount() {
5331  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
5332  function () {
5333  $this->incEditCountImmediate();
5334  },
5335  __METHOD__
5336  );
5337  }
5338 
5344  public function incEditCountImmediate() {
5345  if ( $this->isAnon() ) {
5346  return;
5347  }
5348 
5349  $dbw = wfGetDB( DB_MASTER );
5350  // No rows will be "affected" if user_editcount is NULL
5351  $dbw->update(
5352  'user',
5353  [ 'user_editcount=user_editcount+1' ],
5354  [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
5355  __METHOD__
5356  );
5357  // Lazy initialization check...
5358  if ( $dbw->affectedRows() == 0 ) {
5359  // Now here's a goddamn hack...
5360  $dbr = wfGetDB( DB_REPLICA );
5361  if ( $dbr !== $dbw ) {
5362  // If we actually have a replica DB server, the count is
5363  // at least one behind because the current transaction
5364  // has not been committed and replicated.
5365  $this->mEditCount = $this->initEditCount( 1 );
5366  } else {
5367  // But if DB_REPLICA is selecting the master, then the
5368  // count we just read includes the revision that was
5369  // just added in the working transaction.
5370  $this->mEditCount = $this->initEditCount();
5371  }
5372  } else {
5373  if ( $this->mEditCount === null ) {
5374  $this->getEditCount();
5375  $dbr = wfGetDB( DB_REPLICA );
5376  $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
5377  } else {
5378  $this->mEditCount++;
5379  }
5380  }
5381  // Edit count in user cache too
5382  $this->invalidateCache();
5383  }
5384 
5391  protected function initEditCount( $add = 0 ) {
5392  // Pull from a replica DB to be less cruel to servers
5393  // Accuracy isn't the point anyway here
5394  $dbr = wfGetDB( DB_REPLICA );
5395  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5396  $count = (int)$dbr->selectField(
5397  [ 'revision' ] + $actorWhere['tables'],
5398  'COUNT(*)',
5399  [ $actorWhere['conds'] ],
5400  __METHOD__,
5401  [],
5402  $actorWhere['joins']
5403  );
5404  $count = $count + $add;
5405 
5406  $dbw = wfGetDB( DB_MASTER );
5407  $dbw->update(
5408  'user',
5409  [ 'user_editcount' => $count ],
5410  [ 'user_id' => $this->getId() ],
5411  __METHOD__
5412  );
5413 
5414  return $count;
5415  }
5416 
5424  public static function getRightDescription( $right ) {
5425  $key = "right-$right";
5426  $msg = wfMessage( $key );
5427  return $msg->isDisabled() ? $right : $msg->text();
5428  }
5429 
5437  public static function getGrantName( $grant ) {
5438  $key = "grant-$grant";
5439  $msg = wfMessage( $key );
5440  return $msg->isDisabled() ? $grant : $msg->text();
5441  }
5442 
5463  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5464  return true; // disabled
5465  }
5466 
5475  public function addNewUserLogEntryAutoCreate() {
5476  $this->addNewUserLogEntry( 'autocreate' );
5477 
5478  return true;
5479  }
5480 
5486  protected function loadOptions( $data = null ) {
5487  $this->load();
5488 
5489  if ( $this->mOptionsLoaded ) {
5490  return;
5491  }
5492 
5493  $this->mOptions = self::getDefaultOptions();
5494 
5495  if ( !$this->getId() ) {
5496  // For unlogged-in users, load language/variant options from request.
5497  // There's no need to do it for logged-in users: they can set preferences,
5498  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5499  // so don't override user's choice (especially when the user chooses site default).
5500  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5501  $this->mOptions['variant'] = $variant;
5502  $this->mOptions['language'] = $variant;
5503  $this->mOptionsLoaded = true;
5504  return;
5505  }
5506 
5507  // Maybe load from the object
5508  if ( !is_null( $this->mOptionOverrides ) ) {
5509  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5510  foreach ( $this->mOptionOverrides as $key => $value ) {
5511  $this->mOptions[$key] = $value;
5512  }
5513  } else {
5514  if ( !is_array( $data ) ) {
5515  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5516  // Load from database
5517  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5518  ? wfGetDB( DB_MASTER )
5519  : wfGetDB( DB_REPLICA );
5520 
5521  $res = $dbr->select(
5522  'user_properties',
5523  [ 'up_property', 'up_value' ],
5524  [ 'up_user' => $this->getId() ],
5525  __METHOD__
5526  );
5527 
5528  $this->mOptionOverrides = [];
5529  $data = [];
5530  foreach ( $res as $row ) {
5531  // Convert '0' to 0. PHP's boolean conversion considers them both
5532  // false, but e.g. JavaScript considers the former as true.
5533  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5534  // and convert all values here.
5535  if ( $row->up_value === '0' ) {
5536  $row->up_value = 0;
5537  }
5538  $data[$row->up_property] = $row->up_value;
5539  }
5540  }
5541 
5542  foreach ( $data as $property => $value ) {
5543  $this->mOptionOverrides[$property] = $value;
5544  $this->mOptions[$property] = $value;
5545  }
5546  }
5547 
5548  // Replace deprecated language codes
5549  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5550  $this->mOptions['language']
5551  );
5552 
5553  $this->mOptionsLoaded = true;
5554 
5555  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5556  }
5557 
5563  protected function saveOptions() {
5564  $this->loadOptions();
5565 
5566  // Not using getOptions(), to keep hidden preferences in database
5567  $saveOptions = $this->mOptions;
5568 
5569  // Allow hooks to abort, for instance to save to a global profile.
5570  // Reset options to default state before saving.
5571  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5572  return;
5573  }
5574 
5575  $userId = $this->getId();
5576 
5577  $insert_rows = []; // all the new preference rows
5578  foreach ( $saveOptions as $key => $value ) {
5579  // Don't bother storing default values
5580  $defaultOption = self::getDefaultOption( $key );
5581  if ( ( $defaultOption === null && $value !== false && $value !== null )
5582  || $value != $defaultOption
5583  ) {
5584  $insert_rows[] = [
5585  'up_user' => $userId,
5586  'up_property' => $key,
5587  'up_value' => $value,
5588  ];
5589  }
5590  }
5591 
5592  $dbw = wfGetDB( DB_MASTER );
5593 
5594  $res = $dbw->select( 'user_properties',
5595  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5596 
5597  // Find prior rows that need to be removed or updated. These rows will
5598  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5599  $keysDelete = [];
5600  foreach ( $res as $row ) {
5601  if ( !isset( $saveOptions[$row->up_property] )
5602  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5603  ) {
5604  $keysDelete[] = $row->up_property;
5605  }
5606  }
5607 
5608  if ( count( $keysDelete ) ) {
5609  // Do the DELETE by PRIMARY KEY for prior rows.
5610  // In the past a very large portion of calls to this function are for setting
5611  // 'rememberpassword' for new accounts (a preference that has since been removed).
5612  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5613  // caused gap locks on [max user ID,+infinity) which caused high contention since
5614  // updates would pile up on each other as they are for higher (newer) user IDs.
5615  // It might not be necessary these days, but it shouldn't hurt either.
5616  $dbw->delete( 'user_properties',
5617  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5618  }
5619  // Insert the new preference rows
5620  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5621  }
5622 
5629  public static function selectFields() {
5630  wfDeprecated( __METHOD__, '1.31' );
5631  return [
5632  'user_id',
5633  'user_name',
5634  'user_real_name',
5635  'user_email',
5636  'user_touched',
5637  'user_token',
5638  'user_email_authenticated',
5639  'user_email_token',
5640  'user_email_token_expires',
5641  'user_registration',
5642  'user_editcount',
5643  ];
5644  }
5645 
5655  public static function getQueryInfo() {
5657 
5658  $ret = [
5659  'tables' => [ 'user' ],
5660  'fields' => [
5661  'user_id',
5662  'user_name',
5663  'user_real_name',
5664  'user_email',
5665  'user_touched',
5666  'user_token',
5667  'user_email_authenticated',
5668  'user_email_token',
5669  'user_email_token_expires',
5670  'user_registration',
5671  'user_editcount',
5672  ],
5673  'joins' => [],
5674  ];
5675 
5676  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
5677  // but it does little harm and might be needed for write callers loading a User.
5679  $ret['tables']['user_actor'] = 'actor';
5680  $ret['fields'][] = 'user_actor.actor_id';
5681  $ret['joins']['user_actor'] = [
5682  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
5683  [ 'user_actor.actor_user = user_id' ]
5684  ];
5685  }
5686 
5687  return $ret;
5688  }
5689 
5697  static function newFatalPermissionDeniedStatus( $permission ) {
5698  global $wgLang;
5699 
5700  $groups = [];
5701  foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5702  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5703  }
5704 
5705  if ( $groups ) {
5706  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5707  } else {
5708  return Status::newFatal( 'badaccess-group0' );
5709  }
5710  }
5711 
5721  public function getInstanceForUpdate() {
5722  if ( !$this->getId() ) {
5723  return null; // anon
5724  }
5725 
5726  $user = self::newFromId( $this->getId() );
5727  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5728  return null;
5729  }
5730 
5731  return $user;
5732  }
5733 
5741  public function equals( UserIdentity $user ) {
5742  // XXX it's not clear whether central ID providers are supposed to obey this
5743  return $this->getName() === $user->getName();
5744  }
5745 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1818
User\saveOptions
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5563
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:4926
Block\prevents
prevents( $action, $x=null)
Get/set whether the Block prevents a given action.
Definition: Block.php:1071
LanguageCode\replaceDeprecatedCodes
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date,...
Definition: LanguageCode.php:165
User\updateNewtalk
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2681
$status
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:1305
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:464
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:364
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2576
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:48
User\$mOptionsLoaded
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:245
$wgProxyWhitelist
$wgProxyWhitelist
Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other methods mi...
Definition: DefaultSettings.php:5637
User\inDnsBlacklist
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:2022
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:244
if
if($IP===false)
Definition: cleanupArchiveUserText.php:4
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:615
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:280
User\confirmationTokenUrl
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4770
file
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
$wgProxyList
$wgProxyList
Big list of banned IP addresses.
Definition: DefaultSettings.php:5992
User\clearSharedCache
clearSharedCache( $mode='changed')
Clear user data from memcached.
Definition: User.php:2779
SCHEMA_COMPAT_READ_NEW
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:287
wfCanIPUseHTTPS
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
Definition: GlobalFunctions.php:3163
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
User\$mToken
string $mToken
Definition: User.php:224
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:365
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1151
User\getId
getId()
Get the user's ID.
Definition: User.php:2438
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:114
User\makeGroupLinkWiki
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name.
Definition: User.php:5191
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3891
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2286
MWCryptHash\hmac
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
Definition: MWCryptHash.php:106
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:3802
$context
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2675
User\$mBlock
Block $mBlock
Definition: User.php:298
Block\clearCookie
static clearCookie(WebResponse $response)
Unset the 'BlockID' cookie.
Definition: Block.php:1553
User\$mBlockreason
string $mBlockreason
Definition: User.php:278
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4797
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2501
User\$mImplicitGroups
array $mImplicitGroups
Definition: User.php:282
User\isLocallyBlockedProxy
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:2068
$wgRevokePermissions
$wgRevokePermissions
Permission keys revoked from users in each group.
Definition: DefaultSettings.php:5274
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3308
$wgBlockAllowsUTEdit
$wgBlockAllowsUTEdit
Set this to true to allow blocked users to edit their own user talk page.
Definition: DefaultSettings.php:5005
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1606
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5697
User\getEditTokenObject
getEditTokenObject( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4621
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:7040
IP\isInRanges
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:668
$opt
$opt
Definition: postprocess-phan.php:115
User\isBot
isBot()
Definition: User.php:3810
Block\newFromID
static newFromID( $id)
Load a blocked user from their block id.
Definition: Block.php:184
User\newTouchedTimestamp
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2758
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8941
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3693
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:4481
Block\TYPE_IP
const TYPE_IP
Definition: Block.php:84
User\incEditCount
incEditCount()
Deferred version of incEditCountImmediate()
Definition: User.php:5330
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:756
captcha-old.count
count
Definition: captcha-old.py:249
Block\TYPE_RANGE
const TYPE_RANGE
Definition: Block.php:85
UserMailer\send
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Definition: UserMailer.php:114
User\getOptionKinds
getOptionKinds(IContextSource $context, $options=null)
Return an associative array mapping preferences keys to the kind of a preference they're used for.
Definition: User.php:3365
Block\chooseBlock
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1294
User\getBlock
getBlock( $bFromSlave=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2291
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4909
MediaWiki\Logger\LoggerFactory\getInstance
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
Definition: LoggerFactory.php:92
User\getBlockId
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2342
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:3243
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:3200
UserGroupMembership\getGroupName
static getGroupName( $group)
Gets the localized friendly name for a group, if it exists.
Definition: UserGroupMembership.php:431
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:220
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:322
$result
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. 'LanguageGetMagic':DEPRECATED since 1.16! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) '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 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) '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:2034
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1954
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2987
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:4468
$wgSoftBlockRanges
string[] $wgSoftBlockRanges
IP ranges that should be considered soft-blocked (anon-only, account creation allowed).
Definition: DefaultSettings.php:5646
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2639
User\loadDefaults
loadDefaults( $name=false)
Set cached properties to default.
Definition: User.php:1307
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:301
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1769
User\$mOptions
array $mOptions
Definition: User.php:292
User\$mNewtalk
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:268
User\loadOptions
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5486
User\makeGroupLinkHTML
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition: User.php:5166
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:4859
$req
this hook is for auditing only $req
Definition: hooks.txt:1018
User\setNewpassword
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:3045
Autopromote\getAutopromoteGroups
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:35
StatusValue\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
User\setEmailWithConfirmation
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:3090
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1667
User\$mHideName
bool $mHideName
Definition: User.php:290
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3534
$params
$params
Definition: styleTest.css.php:44
Block\newFromTarget
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing Block object if possible.
Definition: Block.php:1174
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1237
User\$mLocked
bool $mLocked
Definition: User.php:288
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:592
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1488
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4841
IP\isIPv6
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
User\$mEmail
string $mEmail
Definition: User.php:218
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:4540
User\$mOptionOverrides
array $mOptionOverrides
Definition: User.php:238
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3591
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:682
$s
$s
Definition: mergeMessageFileList.php:187
page
target page
Definition: All_system_messages.txt:1267
User\getBlockFromCookieValue
getBlockFromCookieValue( $blockCookieVal)
Try to load a Block from an ID given in a cookie value.
Definition: User.php:1950
PasswordFactory\generateRandomPasswordString
static generateRandomPasswordString( $minLength=10)
Generate a random string suitable for a password.
Definition: PasswordFactory.php:224
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:658
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3879
$res
$res
Definition: database.txt:21
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
User\getDatePreference
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3495
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:347
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:3073
User\idForName
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:4267
$wgAvailableRights
$wgAvailableRights[]
Definition: ReplaceText.php:55
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:997
User
User
Definition: All_system_messages.txt:425
User\sendConfirmationMail
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4687
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:5014
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:3063
User\pingLimiter
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:2139
User\$mFrom
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:263
User\initEditCount
initEditCount( $add=0)
Initialize user_editcount from data out of the revision table.
Definition: User.php:5391
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3870
$base
$base
Definition: generateLocalAutoload.php:11
User\invalidateEmail
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4827
$messages
$messages
Definition: LogTests.i18n.php:8
UserGroupMembership\getGroupPage
static getGroupPage( $group)
Gets the title of a page describing a particular user group.
Definition: UserGroupMembership.php:456
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:778
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6933
User\loadGroups
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1616
User\$mHash
string $mHash
Definition: User.php:274
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:6949
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1082
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
User\isIPRange
isIPRange()
Is the user an IP range?
Definition: User.php:982
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
User\deleteNewtalk
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2706
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:111
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
MediaWiki\User\UserIdentity\getActorId
getActorId()
User\$mCacheVars
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:92
php
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
pages
The ContentHandler facility adds support for arbitrary content types on wiki pages
Definition: contenthandler.txt:1
User\getRights
getRights()
Get the permissions this user has.
Definition: User.php:3549
$wgClockSkewFudge
$wgClockSkewFudge
Clock skew or the one-second resolution of time() can occasionally cause cache problems when the user...
Definition: DefaultSettings.php:2686
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4302
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3904
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
User\getAutomaticGroups
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3640
User\INVALID_TOKEN
const INVALID_TOKEN
@const string An invalid value for user_token
Definition: User.php:56
User\setPassword
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2896
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1773
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5721
$dbr
$dbr
Definition: testCompression.php:50
$wgMaxNameChars
$wgMaxNameChars
Maximum number of bytes in username.
Definition: DefaultSettings.php:4832
IDBAccessObject\READ_LOCKING
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:62
User\isBlockedFrom
isBlockedFrom( $title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2303
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Name of the external diff engine to use.
Definition: DefaultSettings.php:8940
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:819
UserGroupMembership\getMembershipsForUser
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
Definition: UserGroupMembership.php:309
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3728
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:32
LoggedOutEditToken
Value object representing a logged-out user's edit token.
Definition: LoggedOutEditToken.php:35
User\matchEditToken
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4661
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:3053
User\$mRights
array $mRights
Definition: User.php:276
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:4549
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4378
User\makeUpdateConditions
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition: User.php:1692
IP\isValidRange
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:138
User\invalidateCache
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2808
User\setInternalPassword
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2909
MWException
MediaWiki exception.
Definition: MWException.php:26
User\invalidationTokenUrl
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4779
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2402
User\$mDatePreference
string $mDatePreference
Definition: User.php:270
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:4964
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1118
$property
$property
Definition: styleTest.css.php:48
User\checkPasswordValidity
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1198
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:101
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4810
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1356
UserGroupMembership\getLink
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
Definition: UserGroupMembership.php:373
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2333
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2726
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2693
User\$mBlockedFromCreateAccount
Block $mBlockedFromCreateAccount
Definition: User.php:304
in
null for the wiki Added in
Definition: hooks.txt:1627
User\$mGlobalBlock
Block $mGlobalBlock
Definition: User.php:286
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4531
MediaWiki\Auth\AuthenticationResponse
This is a value object to hold authentication response data.
Definition: AuthenticationResponse.php:37
User\logout
logout()
Log this user out.
Definition: User.php:4141
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4752
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:505
$wgLang
$wgLang
Definition: Setup.php:902
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1970
User\getImplicitGroups
static getImplicitGroups()
Get a list of implicit groups TODO: Should we deprecate this? It's trivial, but we don't want to enco...
Definition: User.php:5139
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2419
User\randomPassword
static randomPassword()
Return a random password.
Definition: User.php:1294
User\$mBlockedby
string $mBlockedby
Definition: User.php:272
UserGroupMembership\newFromRow
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
Definition: UserGroupMembership.php:93
IP\getSubnet
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition: IP.php:742
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4522
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:971
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2840
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3617
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:2114
$code
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:813
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3764
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4869
$wgImplicitGroups
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
Definition: DefaultSettings.php:5279
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4559
User\isAllowedAll
isAllowedAll()
Definition: User.php:3842
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2825
MediaWiki\User\UserIdentity\getName
getName()
User\clearInstanceCache
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1748
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1661
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:79
$wgEnableDnsBlacklist
$wgEnableDnsBlacklist
Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies.
Definition: DefaultSettings.php:5606
User\TOKEN_LENGTH
const TOKEN_LENGTH
@const int Number of characters in user_token field.
Definition: User.php:51
$wgDefaultSkin
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
Definition: DefaultSettings.php:3327
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1841
$wgReservedUsernames
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
Definition: DefaultSettings.php:4838
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:545
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1983
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3935
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:222
User\setName
setName( $str)
Set the user name.
Definition: User.php:2490
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:215
User\resetOptions
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3450
array
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))
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:45
User\$mGroupMemberships
UserGroupMembership[] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:236
UserPasswordPolicy
Check if a user's password complies with any password policies that apply to that user,...
Definition: UserPasswordPolicy.php:28
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:6960
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:988
string
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
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5528
User\$mLoadedItems
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:250
list
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
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3969
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:4189
User\addNewUserLogEntry
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5463
Block\getBlocksForIPList
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition: Block.php:1213
User\$mCoreRights
static $mCoreRights
Array of Strings Core rights.
Definition: User.php:119
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:956
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4938
$fname
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition: Setup.php:121
$wgAutopromoteOnceLogInRC
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
Definition: DefaultSettings.php:5499
$request
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:2675
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:74
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:3157
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
User\EDIT_TOKEN_SUFFIX
const EDIT_TOKEN_SUFFIX
Global constant made accessible as class constants so that autoloader magic can be used.
Definition: User.php:63
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2614
any
they could even be mouse clicks or menu items whatever suits your program You should also get your if any
Definition: COPYING.txt:326
User\getBlockedStatus
getBlockedStatus( $bFromSlave=true)
Get blocking information.
Definition: User.php:1833
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:573
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:2644
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3669
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:891
Block\getIdFromCookieValue
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the 'BlockID' cookie.
Definition: Block.php:1589
$value
$value
Definition: styleTest.css.php:49
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\incEditCountImmediate
incEditCountImmediate()
Increment the user's edit-count field.
Definition: User.php:5344
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1367
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:5690
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2355
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:3172
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
User\isDnsBlacklisted
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:2001
User\getTouched
getTouched()
Get the user touched timestamp.
Definition: User.php:2852
User\$mId
int $mId
Cache variables.
Definition: User.php:209
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2369
User\__toString
__toString()
Definition: User.php:329
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
Title\GAID_FOR_UPDATE
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:54
CannotCreateActorException
Exception thrown when an actor can't be created.
Definition: CannotCreateActorException.php:28
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1710
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:118
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:3145
User\$idCacheByName
static $idCacheByName
Definition: User.php:309
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4450
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:4105
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4964
User\getGroupPage
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition: User.php:5151
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5288
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:4037
User\VERSION
const VERSION
@const int Serialized record version.
Definition: User.php:68
SCHEMA_COMPAT_WRITE_NEW
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:286
$ret
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:2036
User\matchEditTokenNoSuffix
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4675
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5523
User\checkPassword
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4569
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:5108
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3952
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:5120
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:71
$wgInvalidUsernameCharacters
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
Definition: DefaultSettings.php:4933
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:432
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:5655
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2324
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:8938
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2874
plain
either a plain
Definition: hooks.txt:2097
Wikimedia\Rdbms\Database\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:4057
User\getGroupMember
static getGroupMember( $group, $username='#')
Get the localized descriptive name for a member of a group, if it exists.
Definition: User.php:5097
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:83
IP\isIPv4
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:41
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:5215
Block\TYPE_USER
const TYPE_USER
Definition: Block.php:83
User\getGroupName
static getGroupName( $group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:5084
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:440
Autopromote\getAutopromoteOnceGroups
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
text
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
Definition: All_system_messages.txt:1267
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2454
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:4066
article
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 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 patches for two options in MediaWiki One reverses the order of a title before displaying the article
Definition: hooks.txt:23
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1701
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4924
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:240
IP\sanitizeIP
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:152
User\isEveryoneAllowed
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:5034
and
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
MediaWiki\User\UserIdentity\getId
getId()
User\isAllowedAny
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3827
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5424
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2960
$cache
$cache
Definition: mcc.php:33
$rows
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:2675
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:5763
$options
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:2036
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:378
$wgApplyIpBlocksToXff
$wgApplyIpBlocksToXff
Whether to look at the X-Forwarded-For header's list of (potentially spoofed) IPs and apply IP blocks...
Definition: DefaultSettings.php:5653
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3794
User\checkTemporaryPassword
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4604
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:32
User\$mActorId
int null $mActorId
Definition: User.php:213
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2568
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:3025
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:911
SCHEMA_COMPAT_NEW
const SCHEMA_COMPAT_NEW
Definition: Defines.php:291
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:951
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:230
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1081
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1238
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4883
$rev
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:1808
as
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
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5437
Block
Definition: Block.php:27
UserCache\singleton
static singleton()
Definition: UserCache.php:34
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:291
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:630
$wgDnsBlacklistUrls
$wgDnsBlacklistUrls
List of DNS blacklists to use, if $wgEnableDnsBlacklist is true.
Definition: DefaultSettings.php:5631
$keys
$keys
Definition: testCompression.php:67
NS_USER
const NS_USER
Definition: Defines.php:66
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:529
$wgLearnerMemberSince
$wgLearnerMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8939
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1640
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4726
LoggerFactory
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
User\getTokenFromOption
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:3280
true
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:2036
User\$mEmailToken
string $mEmailToken
Definition: User.php:228
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: LogEntry.php:437
$wgGroupsRemoveFromSelf
$wgGroupsRemoveFromSelf
Definition: DefaultSettings.php:5307
User\equals
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5741
User\checkNewtalk
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2666
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1436
HTMLFormField\flattenOptions
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
Definition: HTMLFormField.php:1139
User\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5629
User\$mName
string $mName
Definition: User.php:211
User\$mRegistration
string $mRegistration
Definition: User.php:232
$wgGroupPermissions
$wgGroupPermissions['sysop']['replacetext']
Definition: ReplaceText.php:56
class
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
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for local wiki users.
Definition: DefaultSettings.php:4506
$t
$t
Definition: testCompression.php:69
User\addNewUserLogEntryAutoCreate
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5475
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:732
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:84
User\$mRequest
WebRequest $mRequest
Definition: User.php:295
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:747
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3921
MediaWikiServices
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
UserGroupMembership\getMembership
static getMembership( $userId, $group, IDatabase $db=null)
Returns a UserGroupMembership object that pertains to the given user and group, or false if the user ...
Definition: UserGroupMembership.php:340
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:3231
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
$wgMinimalPasswordLength
$wgMinimalPasswordLength
Specifies the minimal length of a user password.
Definition: DefaultSettings.php:4731
$wgGroupsAddToSelf
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
Definition: DefaultSettings.php:5302
User\isUsableName
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:1046
User\isBlocked
isBlocked( $bFromSlave=true)
Check if user is blocked.
Definition: User.php:2281
User\$mFormerGroups
array $mFormerGroups
Definition: User.php:284
wfMessage
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
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1647
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4500
User\getEditToken
getEditToken( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4645
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3515
User\$mAllRights
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:204
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1346
User\purge
static purge( $wikiId, $userId)
Definition: User.php:494
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:47
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:118
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3259
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:307
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:813
User\getPasswordValidity
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition: User.php:1162
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:901
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2463
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:4952
UserGroupMembership\getGroupMemberName
static getGroupMemberName( $group, $username)
Gets the localized name for a member of a group, if it exists.
Definition: UserGroupMembership.php:444
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:4153
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:3604
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4852
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1121
$wgNamespacesToBeSearchedDefault
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
Definition: DefaultSettings.php:6608
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:517
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
User\$mEffectiveGroups
array $mEffectiveGroups
Definition: User.php:280
User\setPasswordInternal
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2922
$wgDisableAnonTalk
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
Definition: DefaultSettings.php:7046
UserGroupMembership
Represents a "user group membership" – a specific instance of a user belonging to a group.
Definition: UserGroupMembership.php:37
IP\isIPAddress
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
User\$mEditCount
int $mEditCount
Definition: User.php:234
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:226
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4991
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3857
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:683
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3342
$type
$type
Definition: testCompression.php:48
$wgActorTableSchemaMigrationStage
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
Definition: DefaultSettings.php:9006
User\trackBlockWithCookie
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition: User.php:1402