MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
39 
50 class User implements IDBAccessObject, UserIdentity {
51 
55  const TOKEN_LENGTH = 32;
56 
60  const INVALID_TOKEN = '*** INVALID ***';
61 
66  const VERSION = 13;
67 
73 
77  const CHECK_USER_RIGHTS = true;
78 
82  const IGNORE_USER_RIGHTS = false;
83 
90  protected static $mCacheVars = [
91  // user table
92  'mId',
93  'mName',
94  'mRealName',
95  'mEmail',
96  'mTouched',
97  'mToken',
98  'mEmailAuthenticated',
99  'mEmailToken',
100  'mEmailTokenExpires',
101  'mRegistration',
102  'mEditCount',
103  // user_groups table
104  'mGroupMemberships',
105  // user_properties table
106  'mOptionOverrides',
107  // actor table
108  'mActorId',
109  ];
110 
117  protected static $mCoreRights = [
118  'apihighlimits',
119  'applychangetags',
120  'autoconfirmed',
121  'autocreateaccount',
122  'autopatrol',
123  'bigdelete',
124  'block',
125  'blockemail',
126  'bot',
127  'browsearchive',
128  'changetags',
129  'createaccount',
130  'createpage',
131  'createtalk',
132  'delete',
133  'deletechangetags',
134  'deletedhistory',
135  'deletedtext',
136  'deletelogentry',
137  'deleterevision',
138  'edit',
139  'editcontentmodel',
140  'editinterface',
141  'editprotected',
142  'editmyoptions',
143  'editmyprivateinfo',
144  'editmyusercss',
145  'editmyuserjson',
146  'editmyuserjs',
147  'editmywatchlist',
148  'editsemiprotected',
149  'editsitecss',
150  'editsitejson',
151  'editsitejs',
152  'editusercss',
153  'edituserjson',
154  'edituserjs',
155  'hideuser',
156  'import',
157  'importupload',
158  'ipblock-exempt',
159  'managechangetags',
160  'markbotedits',
161  'mergehistory',
162  'minoredit',
163  'move',
164  'movefile',
165  'move-categorypages',
166  'move-rootuserpages',
167  'move-subpages',
168  'nominornewtalk',
169  'noratelimit',
170  'override-export-depth',
171  'pagelang',
172  'patrol',
173  'patrolmarks',
174  'protect',
175  'purge',
176  'read',
177  'reupload',
178  'reupload-own',
179  'reupload-shared',
180  'rollback',
181  'sendemail',
182  'siteadmin',
183  'suppressionlog',
184  'suppressredirect',
185  'suppressrevision',
186  'unblockself',
187  'undelete',
188  'unwatchedpages',
189  'upload',
190  'upload_by_url',
191  'userrights',
192  'userrights-interwiki',
193  'viewmyprivateinfo',
194  'viewmywatchlist',
195  'viewsuppressed',
196  'writeapi',
197  ];
198 
202  protected static $mAllRights = false;
203 
205  // @{
207  public $mId;
209  public $mName;
211  protected $mActorId;
213  public $mRealName;
214 
216  public $mEmail;
218  public $mTouched;
220  protected $mQuickTouched;
222  protected $mToken;
226  protected $mEmailToken;
230  protected $mRegistration;
232  protected $mEditCount;
236  protected $mOptionOverrides;
237  // @}
238 
242  // @{
244 
248  protected $mLoadedItems = [];
249  // @}
250 
261  public $mFrom;
262 
266  protected $mNewtalk;
268  protected $mDatePreference;
270  public $mBlockedby;
272  protected $mHash;
274  public $mRights;
276  protected $mBlockreason;
278  protected $mEffectiveGroups;
280  protected $mImplicitGroups;
282  protected $mFormerGroups;
284  protected $mGlobalBlock;
286  protected $mLocked;
288  public $mHideName;
290  public $mOptions;
291 
293  private $mRequest;
294 
296  public $mBlock;
297 
299  protected $mAllowUsertalk;
300 
302  private $mBlockedFromCreateAccount = false;
303 
305  protected $queryFlagsUsed = self::READ_NORMAL;
306 
307  public static $idCacheByName = [];
308 
320  public function __construct() {
321  $this->clearInstanceCache( 'defaults' );
322  }
323 
327  public function __toString() {
328  return (string)$this->getName();
329  }
330 
345  public function isSafeToLoad() {
346  global $wgFullyInitialised;
347 
348  // The user is safe to load if:
349  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
350  // * mLoadedItems === true (already loaded)
351  // * mFrom !== 'session' (sessions not involved at all)
352 
353  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
354  $this->mLoadedItems === true || $this->mFrom !== 'session';
355  }
356 
362  public function load( $flags = self::READ_NORMAL ) {
363  global $wgFullyInitialised;
364 
365  if ( $this->mLoadedItems === true ) {
366  return;
367  }
368 
369  // Set it now to avoid infinite recursion in accessors
370  $oldLoadedItems = $this->mLoadedItems;
371  $this->mLoadedItems = true;
372  $this->queryFlagsUsed = $flags;
373 
374  // If this is called too early, things are likely to break.
375  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
377  ->warning( 'User::loadFromSession called before the end of Setup.php', [
378  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
379  ] );
380  $this->loadDefaults();
381  $this->mLoadedItems = $oldLoadedItems;
382  return;
383  }
384 
385  switch ( $this->mFrom ) {
386  case 'defaults':
387  $this->loadDefaults();
388  break;
389  case 'name':
390  // Make sure this thread sees its own changes
391  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
392  if ( $lb->hasOrMadeRecentMasterChanges() ) {
393  $flags |= self::READ_LATEST;
394  $this->queryFlagsUsed = $flags;
395  }
396 
397  $this->mId = self::idFromName( $this->mName, $flags );
398  if ( !$this->mId ) {
399  // Nonexistent user placeholder object
400  $this->loadDefaults( $this->mName );
401  } else {
402  $this->loadFromId( $flags );
403  }
404  break;
405  case 'id':
406  // Make sure this thread sees its own changes, if the ID isn't 0
407  if ( $this->mId != 0 ) {
408  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
409  if ( $lb->hasOrMadeRecentMasterChanges() ) {
410  $flags |= self::READ_LATEST;
411  $this->queryFlagsUsed = $flags;
412  }
413  }
414 
415  $this->loadFromId( $flags );
416  break;
417  case 'actor':
418  // Make sure this thread sees its own changes
419  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
420  if ( $lb->hasOrMadeRecentMasterChanges() ) {
421  $flags |= self::READ_LATEST;
422  $this->queryFlagsUsed = $flags;
423  }
424 
425  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
426  $row = wfGetDB( $index )->selectRow(
427  'actor',
428  [ 'actor_user', 'actor_name' ],
429  [ 'actor_id' => $this->mActorId ],
430  __METHOD__,
431  $options
432  );
433 
434  if ( !$row ) {
435  // Ugh.
436  $this->loadDefaults();
437  } elseif ( $row->actor_user ) {
438  $this->mId = $row->actor_user;
439  $this->loadFromId( $flags );
440  } else {
441  $this->loadDefaults( $row->actor_name );
442  }
443  break;
444  case 'session':
445  if ( !$this->loadFromSession() ) {
446  // Loading from session failed. Load defaults.
447  $this->loadDefaults();
448  }
449  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
450  break;
451  default:
452  throw new UnexpectedValueException(
453  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
454  }
455  }
456 
462  public function loadFromId( $flags = self::READ_NORMAL ) {
463  if ( $this->mId == 0 ) {
464  // Anonymous users are not in the database (don't need cache)
465  $this->loadDefaults();
466  return false;
467  }
468 
469  // Try cache (unless this needs data from the master DB).
470  // NOTE: if this thread called saveSettings(), the cache was cleared.
471  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
472  if ( $latest ) {
473  if ( !$this->loadFromDatabase( $flags ) ) {
474  // Can't load from ID
475  return false;
476  }
477  } else {
478  $this->loadFromCache();
479  }
480 
481  $this->mLoadedItems = true;
482  $this->queryFlagsUsed = $flags;
483 
484  return true;
485  }
486 
492  public static function purge( $wikiId, $userId ) {
493  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
494  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
495  $cache->delete( $key );
496  }
497 
503  protected function getCacheKey( WANObjectCache $cache ) {
504  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
505 
506  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
507  }
508 
515  $id = $this->getId();
516 
517  return $id ? [ $this->getCacheKey( $cache ) ] : [];
518  }
519 
526  protected function loadFromCache() {
527  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
528  $data = $cache->getWithSetCallback(
529  $this->getCacheKey( $cache ),
530  $cache::TTL_HOUR,
531  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
532  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
533  wfDebug( "User: cache miss for user {$this->mId}\n" );
534 
535  $this->loadFromDatabase( self::READ_NORMAL );
536  $this->loadGroups();
537  $this->loadOptions();
538 
539  $data = [];
540  foreach ( self::$mCacheVars as $name ) {
541  $data[$name] = $this->$name;
542  }
543 
544  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
545 
546  // if a user group membership is about to expire, the cache needs to
547  // expire at that time (T163691)
548  foreach ( $this->mGroupMemberships as $ugm ) {
549  if ( $ugm->getExpiry() ) {
550  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
551  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
552  $ttl = $secondsUntilExpiry;
553  }
554  }
555  }
556 
557  return $data;
558  },
559  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
560  );
561 
562  // Restore from cache
563  foreach ( self::$mCacheVars as $name ) {
564  $this->$name = $data[$name];
565  }
566 
567  return true;
568  }
569 
571  // @{
572 
589  public static function newFromName( $name, $validate = 'valid' ) {
590  if ( $validate === true ) {
591  $validate = 'valid';
592  }
593  $name = self::getCanonicalName( $name, $validate );
594  if ( $name === false ) {
595  return false;
596  }
597 
598  // Create unloaded user object
599  $u = new User;
600  $u->mName = $name;
601  $u->mFrom = 'name';
602  $u->setItemLoaded( 'name' );
603 
604  return $u;
605  }
606 
613  public static function newFromId( $id ) {
614  $u = new User;
615  $u->mId = $id;
616  $u->mFrom = 'id';
617  $u->setItemLoaded( 'id' );
618  return $u;
619  }
620 
628  public static function newFromActorId( $id ) {
630 
631  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
632  // but it does little harm and might be needed for write callers loading a User.
633  if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) ) {
634  throw new BadMethodCallException(
635  'Cannot use ' . __METHOD__
636  . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
637  );
638  }
639 
640  $u = new User;
641  $u->mActorId = $id;
642  $u->mFrom = 'actor';
643  $u->setItemLoaded( 'actor' );
644  return $u;
645  }
646 
656  public static function newFromIdentity( UserIdentity $identity ) {
657  if ( $identity instanceof User ) {
658  return $identity;
659  }
660 
661  return self::newFromAnyId(
662  $identity->getId() === 0 ? null : $identity->getId(),
663  $identity->getName() === '' ? null : $identity->getName(),
664  $identity->getActorId() === 0 ? null : $identity->getActorId()
665  );
666  }
667 
681  public static function newFromAnyId( $userId, $userName, $actorId, $wikiId = false ) {
683 
684  // Stop-gap solution for the problem described in T222212.
685  // Force the User ID and Actor ID to zero for users loaded from the database
686  // of another wiki, to prevent subtle data corruption and confusing failure modes.
687  if ( $wikiId !== false ) {
688  $userId = 0;
689  $actorId = 0;
690  }
691 
692  $user = new User;
693  $user->mFrom = 'defaults';
694 
695  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
696  // but it does little harm and might be needed for write callers loading a User.
697  if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
698  $user->mActorId = (int)$actorId;
699  if ( $user->mActorId !== 0 ) {
700  $user->mFrom = 'actor';
701  }
702  $user->setItemLoaded( 'actor' );
703  }
704 
705  if ( $userName !== null && $userName !== '' ) {
706  $user->mName = $userName;
707  $user->mFrom = 'name';
708  $user->setItemLoaded( 'name' );
709  }
710 
711  if ( $userId !== null ) {
712  $user->mId = (int)$userId;
713  if ( $user->mId !== 0 ) {
714  $user->mFrom = 'id';
715  }
716  $user->setItemLoaded( 'id' );
717  }
718 
719  if ( $user->mFrom === 'defaults' ) {
720  throw new InvalidArgumentException(
721  'Cannot create a user with no name, no ID, and no actor ID'
722  );
723  }
724 
725  return $user;
726  }
727 
739  public static function newFromConfirmationCode( $code, $flags = 0 ) {
740  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
741  ? wfGetDB( DB_MASTER )
742  : wfGetDB( DB_REPLICA );
743 
744  $id = $db->selectField(
745  'user',
746  'user_id',
747  [
748  'user_email_token' => md5( $code ),
749  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
750  ]
751  );
752 
753  return $id ? self::newFromId( $id ) : null;
754  }
755 
763  public static function newFromSession( WebRequest $request = null ) {
764  $user = new User;
765  $user->mFrom = 'session';
766  $user->mRequest = $request;
767  return $user;
768  }
769 
785  public static function newFromRow( $row, $data = null ) {
786  $user = new User;
787  $user->loadFromRow( $row, $data );
788  return $user;
789  }
790 
826  public static function newSystemUser( $name, $options = [] ) {
827  $options += [
828  'validate' => 'valid',
829  'create' => true,
830  'steal' => false,
831  ];
832 
833  $name = self::getCanonicalName( $name, $options['validate'] );
834  if ( $name === false ) {
835  return null;
836  }
837 
838  $dbr = wfGetDB( DB_REPLICA );
839  $userQuery = self::getQueryInfo();
840  $row = $dbr->selectRow(
841  $userQuery['tables'],
842  $userQuery['fields'],
843  [ 'user_name' => $name ],
844  __METHOD__,
845  [],
846  $userQuery['joins']
847  );
848  if ( !$row ) {
849  // Try the master database...
850  $dbw = wfGetDB( DB_MASTER );
851  $row = $dbw->selectRow(
852  $userQuery['tables'],
853  $userQuery['fields'],
854  [ 'user_name' => $name ],
855  __METHOD__,
856  [],
857  $userQuery['joins']
858  );
859  }
860 
861  if ( !$row ) {
862  // No user. Create it?
863  return $options['create']
864  ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
865  : null;
866  }
867 
868  $user = self::newFromRow( $row );
869 
870  // A user is considered to exist as a non-system user if it can
871  // authenticate, or has an email set, or has a non-invalid token.
872  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
873  AuthManager::singleton()->userCanAuthenticate( $name )
874  ) {
875  // User exists. Steal it?
876  if ( !$options['steal'] ) {
877  return null;
878  }
879 
880  AuthManager::singleton()->revokeAccessForUser( $name );
881 
882  $user->invalidateEmail();
883  $user->mToken = self::INVALID_TOKEN;
884  $user->saveSettings();
885  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
886  }
887 
888  return $user;
889  }
890 
891  // @}
892 
898  public static function whoIs( $id ) {
899  return UserCache::singleton()->getProp( $id, 'name' );
900  }
901 
908  public static function whoIsReal( $id ) {
909  return UserCache::singleton()->getProp( $id, 'real_name' );
910  }
911 
918  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
919  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
920  $name = (string)$name;
922  if ( is_null( $nt ) ) {
923  // Illegal name
924  return null;
925  }
926 
927  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
928  return is_null( self::$idCacheByName[$name] ) ? null : (int)self::$idCacheByName[$name];
929  }
930 
931  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
932  $db = wfGetDB( $index );
933 
934  $s = $db->selectRow(
935  'user',
936  [ 'user_id' ],
937  [ 'user_name' => $nt->getText() ],
938  __METHOD__,
939  $options
940  );
941 
942  if ( $s === false ) {
943  $result = null;
944  } else {
945  $result = (int)$s->user_id;
946  }
947 
948  self::$idCacheByName[$name] = $result;
949 
950  if ( count( self::$idCacheByName ) > 1000 ) {
951  self::$idCacheByName = [];
952  }
953 
954  return $result;
955  }
956 
960  public static function resetIdByNameCache() {
961  self::$idCacheByName = [];
962  }
963 
980  public static function isIP( $name ) {
981  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
982  || IP::isIPv6( $name );
983  }
984 
991  public function isIPRange() {
992  return IP::isValidRange( $this->mName );
993  }
994 
1006  public static function isValidUserName( $name ) {
1007  global $wgMaxNameChars;
1008 
1009  if ( $name == ''
1010  || self::isIP( $name )
1011  || strpos( $name, '/' ) !== false
1012  || strlen( $name ) > $wgMaxNameChars
1013  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
1014  ) {
1015  return false;
1016  }
1017 
1018  // Ensure that the name can't be misresolved as a different title,
1019  // such as with extra namespace keys at the start.
1020  $parsed = Title::newFromText( $name );
1021  if ( is_null( $parsed )
1022  || $parsed->getNamespace()
1023  || strcmp( $name, $parsed->getPrefixedText() ) ) {
1024  return false;
1025  }
1026 
1027  // Check an additional blacklist of troublemaker characters.
1028  // Should these be merged into the title char list?
1029  $unicodeBlacklist = '/[' .
1030  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
1031  '\x{00a0}' . # non-breaking space
1032  '\x{2000}-\x{200f}' . # various whitespace
1033  '\x{2028}-\x{202f}' . # breaks and control chars
1034  '\x{3000}' . # ideographic space
1035  '\x{e000}-\x{f8ff}' . # private use
1036  ']/u';
1037  if ( preg_match( $unicodeBlacklist, $name ) ) {
1038  return false;
1039  }
1040 
1041  return true;
1042  }
1043 
1055  public static function isUsableName( $name ) {
1056  global $wgReservedUsernames;
1057  // Must be a valid username, obviously ;)
1058  if ( !self::isValidUserName( $name ) ) {
1059  return false;
1060  }
1061 
1062  static $reservedUsernames = false;
1063  if ( !$reservedUsernames ) {
1064  $reservedUsernames = $wgReservedUsernames;
1065  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
1066  }
1067 
1068  // Certain names may be reserved for batch processes.
1069  foreach ( $reservedUsernames as $reserved ) {
1070  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1071  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1072  }
1073  if ( $reserved == $name ) {
1074  return false;
1075  }
1076  }
1077  return true;
1078  }
1079 
1090  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1091  if ( $groups === [] ) {
1092  return UserArrayFromResult::newFromIDs( [] );
1093  }
1094 
1095  $groups = array_unique( (array)$groups );
1096  $limit = min( 5000, $limit );
1097 
1098  $conds = [ 'ug_group' => $groups ];
1099  if ( $after !== null ) {
1100  $conds[] = 'ug_user > ' . (int)$after;
1101  }
1102 
1103  $dbr = wfGetDB( DB_REPLICA );
1104  $ids = $dbr->selectFieldValues(
1105  'user_groups',
1106  'ug_user',
1107  $conds,
1108  __METHOD__,
1109  [
1110  'DISTINCT' => true,
1111  'ORDER BY' => 'ug_user',
1112  'LIMIT' => $limit,
1113  ]
1114  ) ?: [];
1115  return UserArray::newFromIDs( $ids );
1116  }
1117 
1130  public static function isCreatableName( $name ) {
1132 
1133  // Ensure that the username isn't longer than 235 bytes, so that
1134  // (at least for the builtin skins) user javascript and css files
1135  // will work. (T25080)
1136  if ( strlen( $name ) > 235 ) {
1137  wfDebugLog( 'username', __METHOD__ .
1138  ": '$name' invalid due to length" );
1139  return false;
1140  }
1141 
1142  // Preg yells if you try to give it an empty string
1143  if ( $wgInvalidUsernameCharacters !== '' &&
1144  preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name )
1145  ) {
1146  wfDebugLog( 'username', __METHOD__ .
1147  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1148  return false;
1149  }
1150 
1151  return self::isUsableName( $name );
1152  }
1153 
1160  public function isValidPassword( $password ) {
1161  // simple boolean wrapper for checkPasswordValidity
1162  return $this->checkPasswordValidity( $password )->isGood();
1163  }
1164 
1186  public function checkPasswordValidity( $password ) {
1187  global $wgPasswordPolicy;
1188 
1189  $upp = new UserPasswordPolicy(
1190  $wgPasswordPolicy['policies'],
1191  $wgPasswordPolicy['checks']
1192  );
1193 
1194  $status = Status::newGood( [] );
1195  $result = false; // init $result to false for the internal checks
1196 
1197  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1198  $status->error( $result );
1199  return $status;
1200  }
1201 
1202  if ( $result === false ) {
1203  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1204  return $status;
1205  }
1206 
1207  if ( $result === true ) {
1208  return $status;
1209  }
1210 
1211  $status->error( $result );
1212  return $status; // the isValidPassword hook set a string $result and returned true
1213  }
1214 
1228  public static function getCanonicalName( $name, $validate = 'valid' ) {
1229  // Force usernames to capital
1230  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1231 
1232  # Reject names containing '#'; these will be cleaned up
1233  # with title normalisation, but then it's too late to
1234  # check elsewhere
1235  if ( strpos( $name, '#' ) !== false ) {
1236  return false;
1237  }
1238 
1239  // Clean up name according to title rules,
1240  // but only when validation is requested (T14654)
1241  $t = ( $validate !== false ) ?
1243  // Check for invalid titles
1244  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1245  return false;
1246  }
1247 
1248  $name = $t->getText();
1249 
1250  switch ( $validate ) {
1251  case false:
1252  break;
1253  case 'valid':
1254  if ( !self::isValidUserName( $name ) ) {
1255  $name = false;
1256  }
1257  break;
1258  case 'usable':
1259  if ( !self::isUsableName( $name ) ) {
1260  $name = false;
1261  }
1262  break;
1263  case 'creatable':
1264  if ( !self::isCreatableName( $name ) ) {
1265  $name = false;
1266  }
1267  break;
1268  default:
1269  throw new InvalidArgumentException(
1270  'Invalid parameter value for $validate in ' . __METHOD__ );
1271  }
1272  return $name;
1273  }
1274 
1283  public function loadDefaults( $name = false ) {
1284  $this->mId = 0;
1285  $this->mName = $name;
1286  $this->mActorId = null;
1287  $this->mRealName = '';
1288  $this->mEmail = '';
1289  $this->mOptionOverrides = null;
1290  $this->mOptionsLoaded = false;
1291 
1292  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1293  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1294  if ( $loggedOut !== 0 ) {
1295  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1296  } else {
1297  $this->mTouched = '1'; # Allow any pages to be cached
1298  }
1299 
1300  $this->mToken = null; // Don't run cryptographic functions till we need a token
1301  $this->mEmailAuthenticated = null;
1302  $this->mEmailToken = '';
1303  $this->mEmailTokenExpires = null;
1304  $this->mRegistration = wfTimestamp( TS_MW );
1305  $this->mGroupMemberships = [];
1306 
1307  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1308  }
1309 
1322  public function isItemLoaded( $item, $all = 'all' ) {
1323  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1324  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1325  }
1326 
1332  protected function setItemLoaded( $item ) {
1333  if ( is_array( $this->mLoadedItems ) ) {
1334  $this->mLoadedItems[$item] = true;
1335  }
1336  }
1337 
1343  private function loadFromSession() {
1344  // Deprecated hook
1345  $result = null;
1346  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1347  if ( $result !== null ) {
1348  return $result;
1349  }
1350 
1351  // MediaWiki\Session\Session already did the necessary authentication of the user
1352  // returned here, so just use it if applicable.
1353  $session = $this->getRequest()->getSession();
1354  $user = $session->getUser();
1355  if ( $user->isLoggedIn() ) {
1356  $this->loadFromUserObject( $user );
1357  if ( $user->getBlock() ) {
1358  // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1359  // every session load, because an autoblocked editor might not edit again from the same
1360  // IP address after being blocked.
1361  $this->trackBlockWithCookie();
1362  }
1363 
1364  // Other code expects these to be set in the session, so set them.
1365  $session->set( 'wsUserID', $this->getId() );
1366  $session->set( 'wsUserName', $this->getName() );
1367  $session->set( 'wsToken', $this->getToken() );
1368 
1369  return true;
1370  }
1371 
1372  return false;
1373  }
1374 
1378  public function trackBlockWithCookie() {
1379  $block = $this->getBlock();
1380 
1381  if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null
1382  && $block->shouldTrackWithCookie( $this->isAnon() )
1383  ) {
1384  $block->setCookie( $this->getRequest()->response() );
1385  }
1386  }
1387 
1395  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1396  // Paranoia
1397  $this->mId = intval( $this->mId );
1398 
1399  if ( !$this->mId ) {
1400  // Anonymous users are not in the database
1401  $this->loadDefaults();
1402  return false;
1403  }
1404 
1405  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1406  $db = wfGetDB( $index );
1407 
1408  $userQuery = self::getQueryInfo();
1409  $s = $db->selectRow(
1410  $userQuery['tables'],
1411  $userQuery['fields'],
1412  [ 'user_id' => $this->mId ],
1413  __METHOD__,
1414  $options,
1415  $userQuery['joins']
1416  );
1417 
1418  $this->queryFlagsUsed = $flags;
1419  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1420 
1421  if ( $s !== false ) {
1422  // Initialise user table data
1423  $this->loadFromRow( $s );
1424  $this->mGroupMemberships = null; // deferred
1425  $this->getEditCount(); // revalidation for nulls
1426  return true;
1427  }
1428 
1429  // Invalid user_id
1430  $this->mId = 0;
1431  $this->loadDefaults();
1432 
1433  return false;
1434  }
1435 
1448  protected function loadFromRow( $row, $data = null ) {
1450 
1451  if ( !is_object( $row ) ) {
1452  throw new InvalidArgumentException( '$row must be an object' );
1453  }
1454 
1455  $all = true;
1456 
1457  $this->mGroupMemberships = null; // deferred
1458 
1459  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
1460  // but it does little harm and might be needed for write callers loading a User.
1461  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
1462  if ( isset( $row->actor_id ) ) {
1463  $this->mActorId = (int)$row->actor_id;
1464  if ( $this->mActorId !== 0 ) {
1465  $this->mFrom = 'actor';
1466  }
1467  $this->setItemLoaded( 'actor' );
1468  } else {
1469  $all = false;
1470  }
1471  }
1472 
1473  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1474  $this->mName = $row->user_name;
1475  $this->mFrom = 'name';
1476  $this->setItemLoaded( 'name' );
1477  } else {
1478  $all = false;
1479  }
1480 
1481  if ( isset( $row->user_real_name ) ) {
1482  $this->mRealName = $row->user_real_name;
1483  $this->setItemLoaded( 'realname' );
1484  } else {
1485  $all = false;
1486  }
1487 
1488  if ( isset( $row->user_id ) ) {
1489  $this->mId = intval( $row->user_id );
1490  if ( $this->mId !== 0 ) {
1491  $this->mFrom = 'id';
1492  }
1493  $this->setItemLoaded( 'id' );
1494  } else {
1495  $all = false;
1496  }
1497 
1498  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1499  self::$idCacheByName[$row->user_name] = $row->user_id;
1500  }
1501 
1502  if ( isset( $row->user_editcount ) ) {
1503  $this->mEditCount = $row->user_editcount;
1504  } else {
1505  $all = false;
1506  }
1507 
1508  if ( isset( $row->user_touched ) ) {
1509  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1510  } else {
1511  $all = false;
1512  }
1513 
1514  if ( isset( $row->user_token ) ) {
1515  // The definition for the column is binary(32), so trim the NULs
1516  // that appends. The previous definition was char(32), so trim
1517  // spaces too.
1518  $this->mToken = rtrim( $row->user_token, " \0" );
1519  if ( $this->mToken === '' ) {
1520  $this->mToken = null;
1521  }
1522  } else {
1523  $all = false;
1524  }
1525 
1526  if ( isset( $row->user_email ) ) {
1527  $this->mEmail = $row->user_email;
1528  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1529  $this->mEmailToken = $row->user_email_token;
1530  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1531  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1532  } else {
1533  $all = false;
1534  }
1535 
1536  if ( $all ) {
1537  $this->mLoadedItems = true;
1538  }
1539 
1540  if ( is_array( $data ) ) {
1541  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1542  if ( $data['user_groups'] === [] ) {
1543  $this->mGroupMemberships = [];
1544  } else {
1545  $firstGroup = reset( $data['user_groups'] );
1546  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1547  $this->mGroupMemberships = [];
1548  foreach ( $data['user_groups'] as $row ) {
1549  $ugm = UserGroupMembership::newFromRow( (object)$row );
1550  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1551  }
1552  }
1553  }
1554  }
1555  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1556  $this->loadOptions( $data['user_properties'] );
1557  }
1558  }
1559  }
1560 
1566  protected function loadFromUserObject( $user ) {
1567  $user->load();
1568  foreach ( self::$mCacheVars as $var ) {
1569  $this->$var = $user->$var;
1570  }
1571  }
1572 
1576  private function loadGroups() {
1577  if ( is_null( $this->mGroupMemberships ) ) {
1578  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1579  ? wfGetDB( DB_MASTER )
1580  : wfGetDB( DB_REPLICA );
1581  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1582  $this->mId, $db );
1583  }
1584  }
1585 
1600  public function addAutopromoteOnceGroups( $event ) {
1602 
1603  if ( wfReadOnly() || !$this->getId() ) {
1604  return [];
1605  }
1606 
1607  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1608  if ( $toPromote === [] ) {
1609  return [];
1610  }
1611 
1612  if ( !$this->checkAndSetTouched() ) {
1613  return []; // raced out (bug T48834)
1614  }
1615 
1616  $oldGroups = $this->getGroups(); // previous groups
1617  $oldUGMs = $this->getGroupMemberships();
1618  foreach ( $toPromote as $group ) {
1619  $this->addGroup( $group );
1620  }
1621  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1622  $newUGMs = $this->getGroupMemberships();
1623 
1624  // update groups in external authentication database
1625  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1626 
1627  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1628  $logEntry->setPerformer( $this );
1629  $logEntry->setTarget( $this->getUserPage() );
1630  $logEntry->setParameters( [
1631  '4::oldgroups' => $oldGroups,
1632  '5::newgroups' => $newGroups,
1633  ] );
1634  $logid = $logEntry->insert();
1635  if ( $wgAutopromoteOnceLogInRC ) {
1636  $logEntry->publish( $logid );
1637  }
1638 
1639  return $toPromote;
1640  }
1641 
1651  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1652  if ( $this->mTouched ) {
1653  // CAS check: only update if the row wasn't changed sicne it was loaded.
1654  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1655  }
1656 
1657  return $conditions;
1658  }
1659 
1669  protected function checkAndSetTouched() {
1670  $this->load();
1671 
1672  if ( !$this->mId ) {
1673  return false; // anon
1674  }
1675 
1676  // Get a new user_touched that is higher than the old one
1677  $newTouched = $this->newTouchedTimestamp();
1678 
1679  $dbw = wfGetDB( DB_MASTER );
1680  $dbw->update( 'user',
1681  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1682  $this->makeUpdateConditions( $dbw, [
1683  'user_id' => $this->mId,
1684  ] ),
1685  __METHOD__
1686  );
1687  $success = ( $dbw->affectedRows() > 0 );
1688 
1689  if ( $success ) {
1690  $this->mTouched = $newTouched;
1691  $this->clearSharedCache( 'changed' );
1692  } else {
1693  // Clears on failure too since that is desired if the cache is stale
1694  $this->clearSharedCache( 'refresh' );
1695  }
1696 
1697  return $success;
1698  }
1699 
1707  public function clearInstanceCache( $reloadFrom = false ) {
1708  $this->mNewtalk = -1;
1709  $this->mDatePreference = null;
1710  $this->mBlockedby = -1; # Unset
1711  $this->mHash = false;
1712  $this->mRights = null;
1713  $this->mEffectiveGroups = null;
1714  $this->mImplicitGroups = null;
1715  $this->mGroupMemberships = null;
1716  $this->mOptions = null;
1717  $this->mOptionsLoaded = false;
1718  $this->mEditCount = null;
1719 
1720  if ( $reloadFrom ) {
1721  $this->mLoadedItems = [];
1722  $this->mFrom = $reloadFrom;
1723  }
1724  }
1725 
1727  private static $defOpt = null;
1729  private static $defOptLang = null;
1730 
1737  public static function resetGetDefaultOptionsForTestsOnly() {
1738  Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
1739  self::$defOpt = null;
1740  self::$defOptLang = null;
1741  }
1742 
1749  public static function getDefaultOptions() {
1751 
1752  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1753  if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
1754  // The content language does not change (and should not change) mid-request, but the
1755  // unit tests change it anyway, and expect this method to return values relevant to the
1756  // current content language.
1757  return self::$defOpt;
1758  }
1759 
1760  self::$defOpt = $wgDefaultUserOptions;
1761  // Default language setting
1762  self::$defOptLang = $contLang->getCode();
1763  self::$defOpt['language'] = self::$defOptLang;
1764  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1765  if ( $langCode === $contLang->getCode() ) {
1766  self::$defOpt['variant'] = $langCode;
1767  } else {
1768  self::$defOpt["variant-$langCode"] = $langCode;
1769  }
1770  }
1771 
1772  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1773  // since extensions may change the set of searchable namespaces depending
1774  // on user groups/permissions.
1775  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1776  self::$defOpt['searchNs' . $nsnum] = (bool)$val;
1777  }
1778  self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1779 
1780  Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
1781 
1782  return self::$defOpt;
1783  }
1784 
1791  public static function getDefaultOption( $opt ) {
1792  $defOpts = self::getDefaultOptions();
1793  return $defOpts[$opt] ?? null;
1794  }
1795 
1805  private function getBlockedStatus( $fromReplica = true ) {
1806  if ( $this->mBlockedby != -1 ) {
1807  return;
1808  }
1809 
1810  wfDebug( __METHOD__ . ": checking...\n" );
1811 
1812  // Initialize data...
1813  // Otherwise something ends up stomping on $this->mBlockedby when
1814  // things get lazy-loaded later, causing false positive block hits
1815  // due to -1 !== 0. Probably session-related... Nothing should be
1816  // overwriting mBlockedby, surely?
1817  $this->load();
1818 
1819  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1820  $this,
1821  $fromReplica
1822  );
1823 
1824  if ( $block instanceof AbstractBlock ) {
1825  wfDebug( __METHOD__ . ": Found block.\n" );
1826  $this->mBlock = $block;
1827  $this->mBlockedby = $block->getByName();
1828  $this->mBlockreason = $block->getReason();
1829  $this->mHideName = $block->getHideName();
1830  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1831  } else {
1832  $this->mBlock = null;
1833  $this->mBlockedby = '';
1834  $this->mBlockreason = '';
1835  $this->mHideName = 0;
1836  $this->mAllowUsertalk = false;
1837  }
1838 
1839  // Avoid PHP 7.1 warning of passing $this by reference
1840  $thisUser = $this;
1841  // Extensions
1842  Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
1843  }
1844 
1853  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1854  return MediaWikiServices::getInstance()->getBlockManager()
1855  ->isDnsBlacklisted( $ip, $checkWhitelist );
1856  }
1857 
1866  public function inDnsBlacklist( $ip, $bases ) {
1867  wfDeprecated( __METHOD__, '1.34' );
1868 
1869  $found = false;
1870  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1871  if ( IP::isIPv4( $ip ) ) {
1872  // Reverse IP, T23255
1873  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1874 
1875  foreach ( (array)$bases as $base ) {
1876  // Make hostname
1877  // If we have an access key, use that too (ProjectHoneypot, etc.)
1878  $basename = $base;
1879  if ( is_array( $base ) ) {
1880  if ( count( $base ) >= 2 ) {
1881  // Access key is 1, base URL is 0
1882  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1883  } else {
1884  $host = "$ipReversed.{$base[0]}";
1885  }
1886  $basename = $base[0];
1887  } else {
1888  $host = "$ipReversed.$base";
1889  }
1890 
1891  // Send query
1892  $ipList = gethostbynamel( $host );
1893 
1894  if ( $ipList ) {
1895  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1896  $found = true;
1897  break;
1898  }
1899 
1900  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1901  }
1902  }
1903 
1904  return $found;
1905  }
1906 
1914  public static function isLocallyBlockedProxy( $ip ) {
1915  wfDeprecated( __METHOD__, '1.34' );
1916 
1917  global $wgProxyList;
1918 
1919  if ( !$wgProxyList ) {
1920  return false;
1921  }
1922 
1923  if ( !is_array( $wgProxyList ) ) {
1924  // Load values from the specified file
1925  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1926  }
1927 
1928  $resultProxyList = [];
1929  $deprecatedIPEntries = [];
1930 
1931  // backward compatibility: move all ip addresses in keys to values
1932  foreach ( $wgProxyList as $key => $value ) {
1933  $keyIsIP = IP::isIPAddress( $key );
1934  $valueIsIP = IP::isIPAddress( $value );
1935  if ( $keyIsIP && !$valueIsIP ) {
1936  $deprecatedIPEntries[] = $key;
1937  $resultProxyList[] = $key;
1938  } elseif ( $keyIsIP && $valueIsIP ) {
1939  $deprecatedIPEntries[] = $key;
1940  $resultProxyList[] = $key;
1941  $resultProxyList[] = $value;
1942  } else {
1943  $resultProxyList[] = $value;
1944  }
1945  }
1946 
1947  if ( $deprecatedIPEntries ) {
1948  wfDeprecated(
1949  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1950  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
1951  }
1952 
1953  $proxyListIPSet = new IPSet( $resultProxyList );
1954  return $proxyListIPSet->match( $ip );
1955  }
1956 
1962  public function isPingLimitable() {
1963  global $wgRateLimitsExcludedIPs;
1964  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1965  // No other good way currently to disable rate limits
1966  // for specific IPs. :P
1967  // But this is a crappy hack and should die.
1968  return false;
1969  }
1970  return !$this->isAllowed( 'noratelimit' );
1971  }
1972 
1987  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1988  // Avoid PHP 7.1 warning of passing $this by reference
1989  $user = $this;
1990  // Call the 'PingLimiter' hook
1991  $result = false;
1992  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1993  return $result;
1994  }
1995 
1996  global $wgRateLimits;
1997  if ( !isset( $wgRateLimits[$action] ) ) {
1998  return false;
1999  }
2000 
2001  $limits = array_merge(
2002  [ '&can-bypass' => true ],
2003  $wgRateLimits[$action]
2004  );
2005 
2006  // Some groups shouldn't trigger the ping limiter, ever
2007  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
2008  return false;
2009  }
2010 
2011  $keys = [];
2012  $id = $this->getId();
2013  $userLimit = false;
2014  $isNewbie = $this->isNewbie();
2016 
2017  if ( $id == 0 ) {
2018  // limits for anons
2019  if ( isset( $limits['anon'] ) ) {
2020  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
2021  }
2022  } elseif ( isset( $limits['user'] ) ) {
2023  // limits for logged-in users
2024  $userLimit = $limits['user'];
2025  }
2026 
2027  // limits for anons and for newbie logged-in users
2028  if ( $isNewbie ) {
2029  // ip-based limits
2030  if ( isset( $limits['ip'] ) ) {
2031  $ip = $this->getRequest()->getIP();
2032  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
2033  }
2034  // subnet-based limits
2035  if ( isset( $limits['subnet'] ) ) {
2036  $ip = $this->getRequest()->getIP();
2037  $subnet = IP::getSubnet( $ip );
2038  if ( $subnet !== false ) {
2039  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
2040  }
2041  }
2042  }
2043 
2044  // Check for group-specific permissions
2045  // If more than one group applies, use the group with the highest limit ratio (max/period)
2046  foreach ( $this->getGroups() as $group ) {
2047  if ( isset( $limits[$group] ) ) {
2048  if ( $userLimit === false
2049  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2050  ) {
2051  $userLimit = $limits[$group];
2052  }
2053  }
2054  }
2055 
2056  // limits for newbie logged-in users (override all the normal user limits)
2057  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2058  $userLimit = $limits['newbie'];
2059  }
2060 
2061  // Set the user limit key
2062  if ( $userLimit !== false ) {
2063  // phan is confused because &can-bypass's value is a bool, so it assumes
2064  // that $userLimit is also a bool here.
2065  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2066  list( $max, $period ) = $userLimit;
2067  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2068  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2069  }
2070 
2071  // ip-based limits for all ping-limitable users
2072  if ( isset( $limits['ip-all'] ) ) {
2073  $ip = $this->getRequest()->getIP();
2074  // ignore if user limit is more permissive
2075  if ( $isNewbie || $userLimit === false
2076  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2077  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2078  }
2079  }
2080 
2081  // subnet-based limits for all ping-limitable users
2082  if ( isset( $limits['subnet-all'] ) ) {
2083  $ip = $this->getRequest()->getIP();
2084  $subnet = IP::getSubnet( $ip );
2085  if ( $subnet !== false ) {
2086  // ignore if user limit is more permissive
2087  if ( $isNewbie || $userLimit === false
2088  || $limits['ip-all'][0] / $limits['ip-all'][1]
2089  > $userLimit[0] / $userLimit[1] ) {
2090  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2091  }
2092  }
2093  }
2094 
2095  $triggered = false;
2096  foreach ( $keys as $key => $limit ) {
2097  // phan is confused because &can-bypass's value is a bool, so it assumes
2098  // that $userLimit is also a bool here.
2099  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2100  list( $max, $period ) = $limit;
2101  $summary = "(limit $max in {$period}s)";
2102  $count = $cache->get( $key );
2103  // Already pinged?
2104  if ( $count ) {
2105  if ( $count >= $max ) {
2106  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2107  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2108  $triggered = true;
2109  } else {
2110  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2111  }
2112  } else {
2113  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2114  if ( $incrBy > 0 ) {
2115  $cache->add( $key, 0, intval( $period ) ); // first ping
2116  }
2117  }
2118  if ( $incrBy > 0 ) {
2119  $cache->incr( $key, $incrBy );
2120  }
2121  }
2122 
2123  return $triggered;
2124  }
2125 
2137  public function isBlocked( $fromReplica = true ) {
2138  return $this->getBlock( $fromReplica ) instanceof AbstractBlock &&
2139  $this->getBlock()->appliesToRight( 'edit' );
2140  }
2141 
2148  public function getBlock( $fromReplica = true ) {
2149  $this->getBlockedStatus( $fromReplica );
2150  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
2151  }
2152 
2165  public function isBlockedFrom( $title, $fromReplica = false ) {
2166  return MediaWikiServices::getInstance()->getPermissionManager()
2167  ->isBlockedFrom( $this, $title, $fromReplica );
2168  }
2169 
2174  public function blockedBy() {
2175  $this->getBlockedStatus();
2176  return $this->mBlockedby;
2177  }
2178 
2183  public function blockedFor() {
2184  $this->getBlockedStatus();
2185  return $this->mBlockreason;
2186  }
2187 
2192  public function getBlockId() {
2193  $this->getBlockedStatus();
2194  return ( $this->mBlock ? $this->mBlock->getId() : false );
2195  }
2196 
2205  public function isBlockedGlobally( $ip = '' ) {
2206  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
2207  }
2208 
2219  public function getGlobalBlock( $ip = '' ) {
2220  if ( $this->mGlobalBlock !== null ) {
2221  return $this->mGlobalBlock ?: null;
2222  }
2223  // User is already an IP?
2224  if ( IP::isIPAddress( $this->getName() ) ) {
2225  $ip = $this->getName();
2226  } elseif ( !$ip ) {
2227  $ip = $this->getRequest()->getIP();
2228  }
2229  // Avoid PHP 7.1 warning of passing $this by reference
2230  $user = $this;
2231  $blocked = false;
2232  $block = null;
2233  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2234 
2235  if ( $blocked && $block === null ) {
2236  // back-compat: UserIsBlockedGlobally didn't have $block param first
2237  $block = new SystemBlock( [
2238  'address' => $ip,
2239  'systemBlock' => 'global-block'
2240  ] );
2241  }
2242 
2243  $this->mGlobalBlock = $blocked ? $block : false;
2244  return $this->mGlobalBlock ?: null;
2245  }
2246 
2252  public function isLocked() {
2253  if ( $this->mLocked !== null ) {
2254  return $this->mLocked;
2255  }
2256  // Reset for hook
2257  $this->mLocked = false;
2258  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2259  return $this->mLocked;
2260  }
2261 
2267  public function isHidden() {
2268  if ( $this->mHideName !== null ) {
2269  return (bool)$this->mHideName;
2270  }
2271  $this->getBlockedStatus();
2272  if ( !$this->mHideName ) {
2273  // Reset for hook
2274  $this->mHideName = false;
2275  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2276  }
2277  return (bool)$this->mHideName;
2278  }
2279 
2284  public function getId() {
2285  if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2286  // Special case, we know the user is anonymous
2287  return 0;
2288  }
2289 
2290  if ( !$this->isItemLoaded( 'id' ) ) {
2291  // Don't load if this was initialized from an ID
2292  $this->load();
2293  }
2294 
2295  return (int)$this->mId;
2296  }
2297 
2302  public function setId( $v ) {
2303  $this->mId = $v;
2304  $this->clearInstanceCache( 'id' );
2305  }
2306 
2311  public function getName() {
2312  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2313  // Special case optimisation
2314  return $this->mName;
2315  }
2316 
2317  $this->load();
2318  if ( $this->mName === false ) {
2319  // Clean up IPs
2320  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2321  }
2322 
2323  return $this->mName;
2324  }
2325 
2339  public function setName( $str ) {
2340  $this->load();
2341  $this->mName = $str;
2342  }
2343 
2350  public function getActorId( IDatabase $dbw = null ) {
2352 
2353  // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
2354  // but it does little harm and might be needed for write callers loading a User.
2355  if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
2356  return 0;
2357  }
2358 
2359  if ( !$this->isItemLoaded( 'actor' ) ) {
2360  $this->load();
2361  }
2362 
2363  // Currently $this->mActorId might be null if $this was loaded from a
2364  // cache entry that was written when $wgActorTableSchemaMigrationStage
2365  // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
2366  // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
2367  // has been removed), that condition may be removed.
2368  if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2369  $q = [
2370  'actor_user' => $this->getId() ?: null,
2371  'actor_name' => (string)$this->getName(),
2372  ];
2373  if ( $dbw ) {
2374  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2375  throw new CannotCreateActorException(
2376  'Cannot create an actor for a usable name that is not an existing user'
2377  );
2378  }
2379  if ( $q['actor_name'] === '' ) {
2380  throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2381  }
2382  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2383  if ( $dbw->affectedRows() ) {
2384  $this->mActorId = (int)$dbw->insertId();
2385  } else {
2386  // Outdated cache?
2387  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2388  $this->mActorId = (int)$dbw->selectField(
2389  'actor',
2390  'actor_id',
2391  $q,
2392  __METHOD__,
2393  [ 'LOCK IN SHARE MODE' ]
2394  );
2395  if ( !$this->mActorId ) {
2396  throw new CannotCreateActorException(
2397  "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2398  );
2399  }
2400  }
2401  $this->invalidateCache();
2402  } else {
2403  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2404  $db = wfGetDB( $index );
2405  $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2406  }
2407  $this->setItemLoaded( 'actor' );
2408  }
2409 
2410  return (int)$this->mActorId;
2411  }
2412 
2417  public function getTitleKey() {
2418  return str_replace( ' ', '_', $this->getName() );
2419  }
2420 
2425  public function getNewtalk() {
2426  $this->load();
2427 
2428  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2429  if ( $this->mNewtalk === -1 ) {
2430  $this->mNewtalk = false; # reset talk page status
2431 
2432  // Check memcached separately for anons, who have no
2433  // entire User object stored in there.
2434  if ( !$this->mId ) {
2435  global $wgDisableAnonTalk;
2436  if ( $wgDisableAnonTalk ) {
2437  // Anon newtalk disabled by configuration.
2438  $this->mNewtalk = false;
2439  } else {
2440  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2441  }
2442  } else {
2443  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2444  }
2445  }
2446 
2447  return (bool)$this->mNewtalk;
2448  }
2449 
2463  public function getNewMessageLinks() {
2464  // Avoid PHP 7.1 warning of passing $this by reference
2465  $user = $this;
2466  $talks = [];
2467  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2468  return $talks;
2469  }
2470 
2471  if ( !$this->getNewtalk() ) {
2472  return [];
2473  }
2474  $utp = $this->getTalkPage();
2475  $dbr = wfGetDB( DB_REPLICA );
2476  // Get the "last viewed rev" timestamp from the oldest message notification
2477  $timestamp = $dbr->selectField( 'user_newtalk',
2478  'MIN(user_last_timestamp)',
2479  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2480  __METHOD__ );
2481  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2482  return [
2483  [
2485  'link' => $utp->getLocalURL(),
2486  'rev' => $rev
2487  ]
2488  ];
2489  }
2490 
2496  public function getNewMessageRevisionId() {
2497  $newMessageRevisionId = null;
2498  $newMessageLinks = $this->getNewMessageLinks();
2499 
2500  // Note: getNewMessageLinks() never returns more than a single link
2501  // and it is always for the same wiki, but we double-check here in
2502  // case that changes some time in the future.
2503  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2504  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2505  && $newMessageLinks[0]['rev']
2506  ) {
2508  $newMessageRevision = $newMessageLinks[0]['rev'];
2509  $newMessageRevisionId = $newMessageRevision->getId();
2510  }
2511 
2512  return $newMessageRevisionId;
2513  }
2514 
2523  protected function checkNewtalk( $field, $id ) {
2524  $dbr = wfGetDB( DB_REPLICA );
2525 
2526  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2527 
2528  return $ok !== false;
2529  }
2530 
2538  protected function updateNewtalk( $field, $id, $curRev = null ) {
2539  // Get timestamp of the talk page revision prior to the current one
2540  $prevRev = $curRev ? $curRev->getPrevious() : false;
2541  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2542  // Mark the user as having new messages since this revision
2543  $dbw = wfGetDB( DB_MASTER );
2544  $dbw->insert( 'user_newtalk',
2545  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2546  __METHOD__,
2547  'IGNORE' );
2548  if ( $dbw->affectedRows() ) {
2549  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2550  return true;
2551  }
2552 
2553  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2554  return false;
2555  }
2556 
2563  protected function deleteNewtalk( $field, $id ) {
2564  $dbw = wfGetDB( DB_MASTER );
2565  $dbw->delete( 'user_newtalk',
2566  [ $field => $id ],
2567  __METHOD__ );
2568  if ( $dbw->affectedRows() ) {
2569  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2570  return true;
2571  }
2572 
2573  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2574  return false;
2575  }
2576 
2583  public function setNewtalk( $val, $curRev = null ) {
2584  if ( wfReadOnly() ) {
2585  return;
2586  }
2587 
2588  $this->load();
2589  $this->mNewtalk = $val;
2590 
2591  if ( $this->isAnon() ) {
2592  $field = 'user_ip';
2593  $id = $this->getName();
2594  } else {
2595  $field = 'user_id';
2596  $id = $this->getId();
2597  }
2598 
2599  if ( $val ) {
2600  $changed = $this->updateNewtalk( $field, $id, $curRev );
2601  } else {
2602  $changed = $this->deleteNewtalk( $field, $id );
2603  }
2604 
2605  if ( $changed ) {
2606  $this->invalidateCache();
2607  }
2608  }
2609 
2616  private function newTouchedTimestamp() {
2617  $time = time();
2618  if ( $this->mTouched ) {
2619  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2620  }
2621 
2622  return wfTimestamp( TS_MW, $time );
2623  }
2624 
2635  public function clearSharedCache( $mode = 'refresh' ) {
2636  if ( !$this->getId() ) {
2637  return;
2638  }
2639 
2640  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2641  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2642  $key = $this->getCacheKey( $cache );
2643 
2644  if ( $mode === 'refresh' ) {
2645  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2646  } else {
2647  $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
2648  function () use ( $cache, $key ) {
2649  $cache->delete( $key );
2650  },
2651  __METHOD__
2652  );
2653  }
2654  }
2655 
2661  public function invalidateCache() {
2662  $this->touch();
2663  $this->clearSharedCache( 'changed' );
2664  }
2665 
2678  public function touch() {
2679  $id = $this->getId();
2680  if ( $id ) {
2681  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2682  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2683  $cache->touchCheckKey( $key );
2684  $this->mQuickTouched = null;
2685  }
2686  }
2687 
2693  public function validateCache( $timestamp ) {
2694  return ( $timestamp >= $this->getTouched() );
2695  }
2696 
2705  public function getTouched() {
2706  $this->load();
2707 
2708  if ( $this->mId ) {
2709  if ( $this->mQuickTouched === null ) {
2710  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2711  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2712 
2713  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2714  }
2715 
2716  return max( $this->mTouched, $this->mQuickTouched );
2717  }
2718 
2719  return $this->mTouched;
2720  }
2721 
2727  public function getDBTouched() {
2728  $this->load();
2729 
2730  return $this->mTouched;
2731  }
2732 
2749  public function setPassword( $str ) {
2750  wfDeprecated( __METHOD__, '1.27' );
2751  return $this->setPasswordInternal( $str );
2752  }
2753 
2762  public function setInternalPassword( $str ) {
2763  wfDeprecated( __METHOD__, '1.27' );
2764  $this->setPasswordInternal( $str );
2765  }
2766 
2775  private function setPasswordInternal( $str ) {
2776  $manager = AuthManager::singleton();
2777 
2778  // If the user doesn't exist yet, fail
2779  if ( !$manager->userExists( $this->getName() ) ) {
2780  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2781  }
2782 
2783  $status = $this->changeAuthenticationData( [
2784  'username' => $this->getName(),
2785  'password' => $str,
2786  'retype' => $str,
2787  ] );
2788  if ( !$status->isGood() ) {
2790  ->info( __METHOD__ . ': Password change rejected: '
2791  . $status->getWikiText( null, null, 'en' ) );
2792  return false;
2793  }
2794 
2795  $this->setOption( 'watchlisttoken', false );
2796  SessionManager::singleton()->invalidateSessionsForUser( $this );
2797 
2798  return true;
2799  }
2800 
2813  public function changeAuthenticationData( array $data ) {
2814  $manager = AuthManager::singleton();
2815  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2816  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2817 
2818  $status = Status::newGood( 'ignored' );
2819  foreach ( $reqs as $req ) {
2820  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2821  }
2822  if ( $status->getValue() === 'ignored' ) {
2823  $status->warning( 'authenticationdatachange-ignored' );
2824  }
2825 
2826  if ( $status->isGood() ) {
2827  foreach ( $reqs as $req ) {
2828  $manager->changeAuthenticationData( $req );
2829  }
2830  }
2831  return $status;
2832  }
2833 
2840  public function getToken( $forceCreation = true ) {
2842 
2843  $this->load();
2844  if ( !$this->mToken && $forceCreation ) {
2845  $this->setToken();
2846  }
2847 
2848  if ( !$this->mToken ) {
2849  // The user doesn't have a token, return null to indicate that.
2850  return null;
2851  }
2852 
2853  if ( $this->mToken === self::INVALID_TOKEN ) {
2854  // We return a random value here so existing token checks are very
2855  // likely to fail.
2856  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2857  }
2858 
2859  if ( $wgAuthenticationTokenVersion === null ) {
2860  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2861  return $this->mToken;
2862  }
2863 
2864  // $wgAuthenticationTokenVersion in use, so hmac it.
2865  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2866 
2867  // The raw hash can be overly long. Shorten it up.
2868  $len = max( 32, self::TOKEN_LENGTH );
2869  if ( strlen( $ret ) < $len ) {
2870  // Should never happen, even md5 is 128 bits
2871  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2872  }
2873 
2874  return substr( $ret, -$len );
2875  }
2876 
2883  public function setToken( $token = false ) {
2884  $this->load();
2885  if ( $this->mToken === self::INVALID_TOKEN ) {
2887  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2888  } elseif ( !$token ) {
2889  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2890  } else {
2891  $this->mToken = $token;
2892  }
2893  }
2894 
2903  public function setNewpassword( $str, $throttle = true ) {
2904  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2905  }
2906 
2911  public function getEmail() {
2912  $this->load();
2913  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2914  return $this->mEmail;
2915  }
2916 
2922  $this->load();
2923  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2925  }
2926 
2931  public function setEmail( $str ) {
2932  $this->load();
2933  if ( $str == $this->mEmail ) {
2934  return;
2935  }
2936  $this->invalidateEmail();
2937  $this->mEmail = $str;
2938  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2939  }
2940 
2948  public function setEmailWithConfirmation( $str ) {
2950 
2951  if ( !$wgEnableEmail ) {
2952  return Status::newFatal( 'emaildisabled' );
2953  }
2954 
2955  $oldaddr = $this->getEmail();
2956  if ( $str === $oldaddr ) {
2957  return Status::newGood( true );
2958  }
2959 
2960  $type = $oldaddr != '' ? 'changed' : 'set';
2961  $notificationResult = null;
2962 
2963  if ( $wgEmailAuthentication && $type === 'changed' ) {
2964  // Send the user an email notifying the user of the change in registered
2965  // email address on their previous email address
2966  $change = $str != '' ? 'changed' : 'removed';
2967  $notificationResult = $this->sendMail(
2968  wfMessage( 'notificationemail_subject_' . $change )->text(),
2969  wfMessage( 'notificationemail_body_' . $change,
2970  $this->getRequest()->getIP(),
2971  $this->getName(),
2972  $str )->text()
2973  );
2974  }
2975 
2976  $this->setEmail( $str );
2977 
2978  if ( $str !== '' && $wgEmailAuthentication ) {
2979  // Send a confirmation request to the new address if needed
2980  $result = $this->sendConfirmationMail( $type );
2981 
2982  if ( $notificationResult !== null ) {
2983  $result->merge( $notificationResult );
2984  }
2985 
2986  if ( $result->isGood() ) {
2987  // Say to the caller that a confirmation and notification mail has been sent
2988  $result->value = 'eauth';
2989  }
2990  } else {
2991  $result = Status::newGood( true );
2992  }
2993 
2994  return $result;
2995  }
2996 
3001  public function getRealName() {
3002  if ( !$this->isItemLoaded( 'realname' ) ) {
3003  $this->load();
3004  }
3005 
3006  return $this->mRealName;
3007  }
3008 
3013  public function setRealName( $str ) {
3014  $this->load();
3015  $this->mRealName = $str;
3016  }
3017 
3028  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3029  global $wgHiddenPrefs;
3030  $this->loadOptions();
3031 
3032  # We want 'disabled' preferences to always behave as the default value for
3033  # users, even if they have set the option explicitly in their settings (ie they
3034  # set it, and then it was disabled removing their ability to change it). But
3035  # we don't want to erase the preferences in the database in case the preference
3036  # is re-enabled again. So don't touch $mOptions, just override the returned value
3037  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3038  return self::getDefaultOption( $oname );
3039  }
3040 
3041  if ( array_key_exists( $oname, $this->mOptions ) ) {
3042  return $this->mOptions[$oname];
3043  }
3044 
3045  return $defaultOverride;
3046  }
3047 
3056  public function getOptions( $flags = 0 ) {
3057  global $wgHiddenPrefs;
3058  $this->loadOptions();
3060 
3061  # We want 'disabled' preferences to always behave as the default value for
3062  # users, even if they have set the option explicitly in their settings (ie they
3063  # set it, and then it was disabled removing their ability to change it). But
3064  # we don't want to erase the preferences in the database in case the preference
3065  # is re-enabled again. So don't touch $mOptions, just override the returned value
3066  foreach ( $wgHiddenPrefs as $pref ) {
3067  $default = self::getDefaultOption( $pref );
3068  if ( $default !== null ) {
3069  $options[$pref] = $default;
3070  }
3071  }
3072 
3073  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3074  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3075  }
3076 
3077  return $options;
3078  }
3079 
3087  public function getBoolOption( $oname ) {
3088  return (bool)$this->getOption( $oname );
3089  }
3090 
3099  public function getIntOption( $oname, $defaultOverride = 0 ) {
3100  $val = $this->getOption( $oname );
3101  if ( $val == '' ) {
3102  $val = $defaultOverride;
3103  }
3104  return intval( $val );
3105  }
3106 
3115  public function setOption( $oname, $val ) {
3116  $this->loadOptions();
3117 
3118  // Explicitly NULL values should refer to defaults
3119  if ( is_null( $val ) ) {
3120  $val = self::getDefaultOption( $oname );
3121  }
3122 
3123  $this->mOptions[$oname] = $val;
3124  }
3125 
3136  public function getTokenFromOption( $oname ) {
3137  global $wgHiddenPrefs;
3138 
3139  $id = $this->getId();
3140  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3141  return false;
3142  }
3143 
3144  $token = $this->getOption( $oname );
3145  if ( !$token ) {
3146  // Default to a value based on the user token to avoid space
3147  // wasted on storing tokens for all users. When this option
3148  // is set manually by the user, only then is it stored.
3149  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3150  }
3151 
3152  return $token;
3153  }
3154 
3164  public function resetTokenFromOption( $oname ) {
3165  global $wgHiddenPrefs;
3166  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3167  return false;
3168  }
3169 
3170  $token = MWCryptRand::generateHex( 40 );
3171  $this->setOption( $oname, $token );
3172  return $token;
3173  }
3174 
3198  public static function listOptionKinds() {
3199  return [
3200  'registered',
3201  'registered-multiselect',
3202  'registered-checkmatrix',
3203  'userjs',
3204  'special',
3205  'unused'
3206  ];
3207  }
3208 
3222  $this->loadOptions();
3223  if ( $options === null ) {
3225  }
3226 
3227  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3228  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3229  $mapping = [];
3230 
3231  // Pull out the "special" options, so they don't get converted as
3232  // multiselect or checkmatrix.
3233  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3234  foreach ( $specialOptions as $name => $value ) {
3235  unset( $prefs[$name] );
3236  }
3237 
3238  // Multiselect and checkmatrix options are stored in the database with
3239  // one key per option, each having a boolean value. Extract those keys.
3240  $multiselectOptions = [];
3241  foreach ( $prefs as $name => $info ) {
3242  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3243  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3244  $opts = HTMLFormField::flattenOptions( $info['options'] );
3245  $prefix = $info['prefix'] ?? $name;
3246 
3247  foreach ( $opts as $value ) {
3248  $multiselectOptions["$prefix$value"] = true;
3249  }
3250 
3251  unset( $prefs[$name] );
3252  }
3253  }
3254  $checkmatrixOptions = [];
3255  foreach ( $prefs as $name => $info ) {
3256  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3257  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3258  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3259  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3260  $prefix = $info['prefix'] ?? $name;
3261 
3262  foreach ( $columns as $column ) {
3263  foreach ( $rows as $row ) {
3264  $checkmatrixOptions["$prefix$column-$row"] = true;
3265  }
3266  }
3267 
3268  unset( $prefs[$name] );
3269  }
3270  }
3271 
3272  // $value is ignored
3273  foreach ( $options as $key => $value ) {
3274  if ( isset( $prefs[$key] ) ) {
3275  $mapping[$key] = 'registered';
3276  } elseif ( isset( $multiselectOptions[$key] ) ) {
3277  $mapping[$key] = 'registered-multiselect';
3278  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3279  $mapping[$key] = 'registered-checkmatrix';
3280  } elseif ( isset( $specialOptions[$key] ) ) {
3281  $mapping[$key] = 'special';
3282  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3283  $mapping[$key] = 'userjs';
3284  } else {
3285  $mapping[$key] = 'unused';
3286  }
3287  }
3288 
3289  return $mapping;
3290  }
3291 
3306  public function resetOptions(
3307  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3309  ) {
3310  $this->load();
3311  $defaultOptions = self::getDefaultOptions();
3312 
3313  if ( !is_array( $resetKinds ) ) {
3314  $resetKinds = [ $resetKinds ];
3315  }
3316 
3317  if ( in_array( 'all', $resetKinds ) ) {
3318  $newOptions = $defaultOptions;
3319  } else {
3320  if ( $context === null ) {
3322  }
3323 
3324  $optionKinds = $this->getOptionKinds( $context );
3325  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3326  $newOptions = [];
3327 
3328  // Use default values for the options that should be deleted, and
3329  // copy old values for the ones that shouldn't.
3330  foreach ( $this->mOptions as $key => $value ) {
3331  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3332  if ( array_key_exists( $key, $defaultOptions ) ) {
3333  $newOptions[$key] = $defaultOptions[$key];
3334  }
3335  } else {
3336  $newOptions[$key] = $value;
3337  }
3338  }
3339  }
3340 
3341  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3342 
3343  $this->mOptions = $newOptions;
3344  $this->mOptionsLoaded = true;
3345  }
3346 
3351  public function getDatePreference() {
3352  // Important migration for old data rows
3353  if ( is_null( $this->mDatePreference ) ) {
3354  global $wgLang;
3355  $value = $this->getOption( 'date' );
3356  $map = $wgLang->getDatePreferenceMigrationMap();
3357  if ( isset( $map[$value] ) ) {
3358  $value = $map[$value];
3359  }
3360  $this->mDatePreference = $value;
3361  }
3362  return $this->mDatePreference;
3363  }
3364 
3371  public function requiresHTTPS() {
3372  global $wgSecureLogin;
3373  if ( !$wgSecureLogin ) {
3374  return false;
3375  }
3376 
3377  $https = $this->getBoolOption( 'prefershttps' );
3378  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3379  if ( $https ) {
3380  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3381  }
3382 
3383  return $https;
3384  }
3385 
3391  public function getStubThreshold() {
3392  global $wgMaxArticleSize; # Maximum article size, in Kb
3393  $threshold = $this->getIntOption( 'stubthreshold' );
3394  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3395  // If they have set an impossible value, disable the preference
3396  // so we can use the parser cache again.
3397  $threshold = 0;
3398  }
3399  return $threshold;
3400  }
3401 
3406  public function getRights() {
3407  if ( is_null( $this->mRights ) ) {
3408  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3409  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3410 
3411  // Deny any rights denied by the user's session, unless this
3412  // endpoint has no sessions.
3413  if ( !defined( 'MW_NO_SESSION' ) ) {
3414  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3415  if ( $allowedRights !== null ) {
3416  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3417  }
3418  }
3419 
3420  Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
3421  // Force reindexation of rights when a hook has unset one of them
3422  $this->mRights = array_values( array_unique( $this->mRights ) );
3423 
3424  // If block disables login, we should also remove any
3425  // extra rights blocked users might have, in case the
3426  // blocked user has a pre-existing session (T129738).
3427  // This is checked here for cases where people only call
3428  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3429  // to give a better error message in the common case.
3430  $config = RequestContext::getMain()->getConfig();
3431  // @TODO Partial blocks should not prevent the user from logging in.
3432  // see: https://phabricator.wikimedia.org/T208895
3433  if (
3434  $this->isLoggedIn() &&
3435  $config->get( 'BlockDisablesLogin' ) &&
3436  $this->getBlock()
3437  ) {
3438  $anon = new User;
3439  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3440  }
3441  }
3442  return $this->mRights;
3443  }
3444 
3451  public function getGroups() {
3452  $this->load();
3453  $this->loadGroups();
3454  return array_keys( $this->mGroupMemberships );
3455  }
3456 
3464  public function getGroupMemberships() {
3465  $this->load();
3466  $this->loadGroups();
3467  return $this->mGroupMemberships;
3468  }
3469 
3477  public function getEffectiveGroups( $recache = false ) {
3478  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3479  $this->mEffectiveGroups = array_unique( array_merge(
3480  $this->getGroups(), // explicit groups
3481  $this->getAutomaticGroups( $recache ) // implicit groups
3482  ) );
3483  // Avoid PHP 7.1 warning of passing $this by reference
3484  $user = $this;
3485  // Hook for additional groups
3486  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3487  // Force reindexation of groups when a hook has unset one of them
3488  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3489  }
3490  return $this->mEffectiveGroups;
3491  }
3492 
3500  public function getAutomaticGroups( $recache = false ) {
3501  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3502  $this->mImplicitGroups = [ '*' ];
3503  if ( $this->getId() ) {
3504  $this->mImplicitGroups[] = 'user';
3505 
3506  $this->mImplicitGroups = array_unique( array_merge(
3507  $this->mImplicitGroups,
3509  ) );
3510  }
3511  if ( $recache ) {
3512  // Assure data consistency with rights/groups,
3513  // as getEffectiveGroups() depends on this function
3514  $this->mEffectiveGroups = null;
3515  }
3516  }
3517  return $this->mImplicitGroups;
3518  }
3519 
3529  public function getFormerGroups() {
3530  $this->load();
3531 
3532  if ( is_null( $this->mFormerGroups ) ) {
3533  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3534  ? wfGetDB( DB_MASTER )
3535  : wfGetDB( DB_REPLICA );
3536  $res = $db->select( 'user_former_groups',
3537  [ 'ufg_group' ],
3538  [ 'ufg_user' => $this->mId ],
3539  __METHOD__ );
3540  $this->mFormerGroups = [];
3541  foreach ( $res as $row ) {
3542  $this->mFormerGroups[] = $row->ufg_group;
3543  }
3544  }
3545 
3546  return $this->mFormerGroups;
3547  }
3548 
3553  public function getEditCount() {
3554  if ( !$this->getId() ) {
3555  return null;
3556  }
3557 
3558  if ( $this->mEditCount === null ) {
3559  /* Populate the count, if it has not been populated yet */
3560  $dbr = wfGetDB( DB_REPLICA );
3561  // check if the user_editcount field has been initialized
3562  $count = $dbr->selectField(
3563  'user', 'user_editcount',
3564  [ 'user_id' => $this->mId ],
3565  __METHOD__
3566  );
3567 
3568  if ( $count === null ) {
3569  // it has not been initialized. do so.
3570  $count = $this->initEditCountInternal();
3571  }
3572  $this->mEditCount = $count;
3573  }
3574  return (int)$this->mEditCount;
3575  }
3576 
3588  public function addGroup( $group, $expiry = null ) {
3589  $this->load();
3590  $this->loadGroups();
3591 
3592  if ( $expiry ) {
3593  $expiry = wfTimestamp( TS_MW, $expiry );
3594  }
3595 
3596  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3597  return false;
3598  }
3599 
3600  // create the new UserGroupMembership and put it in the DB
3601  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3602  if ( !$ugm->insert( true ) ) {
3603  return false;
3604  }
3605 
3606  $this->mGroupMemberships[$group] = $ugm;
3607 
3608  // Refresh the groups caches, and clear the rights cache so it will be
3609  // refreshed on the next call to $this->getRights().
3610  $this->getEffectiveGroups( true );
3611  $this->mRights = null;
3612 
3613  $this->invalidateCache();
3614 
3615  return true;
3616  }
3617 
3624  public function removeGroup( $group ) {
3625  $this->load();
3626 
3627  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3628  return false;
3629  }
3630 
3631  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3632  // delete the membership entry
3633  if ( !$ugm || !$ugm->delete() ) {
3634  return false;
3635  }
3636 
3637  $this->loadGroups();
3638  unset( $this->mGroupMemberships[$group] );
3639 
3640  // Refresh the groups caches, and clear the rights cache so it will be
3641  // refreshed on the next call to $this->getRights().
3642  $this->getEffectiveGroups( true );
3643  $this->mRights = null;
3644 
3645  $this->invalidateCache();
3646 
3647  return true;
3648  }
3649 
3659  public function isRegistered() {
3660  return $this->getId() != 0;
3661  }
3662 
3667  public function isLoggedIn() {
3668  return $this->isRegistered();
3669  }
3670 
3675  public function isAnon() {
3676  return !$this->isRegistered();
3677  }
3678 
3683  public function isBot() {
3684  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3685  return true;
3686  }
3687 
3688  $isBot = false;
3689  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3690 
3691  return $isBot;
3692  }
3693 
3700  public function isAllowedAny() {
3701  $permissions = func_get_args();
3702  foreach ( $permissions as $permission ) {
3703  if ( $this->isAllowed( $permission ) ) {
3704  return true;
3705  }
3706  }
3707  return false;
3708  }
3709 
3715  public function isAllowedAll() {
3716  $permissions = func_get_args();
3717  foreach ( $permissions as $permission ) {
3718  if ( !$this->isAllowed( $permission ) ) {
3719  return false;
3720  }
3721  }
3722  return true;
3723  }
3724 
3730  public function isAllowed( $action = '' ) {
3731  if ( $action === '' ) {
3732  return true; // In the spirit of DWIM
3733  }
3734  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3735  // by misconfiguration: 0 == 'foo'
3736  return in_array( $action, $this->getRights(), true );
3737  }
3738 
3743  public function useRCPatrol() {
3744  global $wgUseRCPatrol;
3745  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3746  }
3747 
3752  public function useNPPatrol() {
3754  return (
3755  ( $wgUseRCPatrol || $wgUseNPPatrol )
3756  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3757  );
3758  }
3759 
3764  public function useFilePatrol() {
3766  return (
3767  ( $wgUseRCPatrol || $wgUseFilePatrol )
3768  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3769  );
3770  }
3771 
3777  public function getRequest() {
3778  if ( $this->mRequest ) {
3779  return $this->mRequest;
3780  }
3781 
3782  global $wgRequest;
3783  return $wgRequest;
3784  }
3785 
3794  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3795  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3796  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3797  }
3798  return false;
3799  }
3800 
3808  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3809  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3810  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3811  $this,
3812  [ $title->getSubjectPage(), $title->getTalkPage() ]
3813  );
3814  }
3815  $this->invalidateCache();
3816  }
3817 
3825  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3826  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3827  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3828  $store->removeWatch( $this, $title->getSubjectPage() );
3829  $store->removeWatch( $this, $title->getTalkPage() );
3830  }
3831  $this->invalidateCache();
3832  }
3833 
3842  public function clearNotification( &$title, $oldid = 0 ) {
3844 
3845  // Do nothing if the database is locked to writes
3846  if ( wfReadOnly() ) {
3847  return;
3848  }
3849 
3850  // Do nothing if not allowed to edit the watchlist
3851  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3852  return;
3853  }
3854 
3855  // If we're working on user's talk page, we should update the talk page message indicator
3856  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3857  // Avoid PHP 7.1 warning of passing $this by reference
3858  $user = $this;
3859  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3860  return;
3861  }
3862 
3863  // Try to update the DB post-send and only if needed...
3864  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3865  if ( !$this->getNewtalk() ) {
3866  return; // no notifications to clear
3867  }
3868 
3869  // Delete the last notifications (they stack up)
3870  $this->setNewtalk( false );
3871 
3872  // If there is a new, unseen, revision, use its timestamp
3873  $nextid = $oldid
3874  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3875  : null;
3876  if ( $nextid ) {
3877  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3878  }
3879  } );
3880  }
3881 
3882  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3883  return;
3884  }
3885 
3886  if ( $this->isAnon() ) {
3887  // Nothing else to do...
3888  return;
3889  }
3890 
3891  // Only update the timestamp if the page is being watched.
3892  // The query to find out if it is watched is cached both in memcached and per-invocation,
3893  // and when it does have to be executed, it can be on a replica DB
3894  // If this is the user's newtalk page, we always update the timestamp
3895  $force = '';
3896  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3897  $force = 'force';
3898  }
3899 
3900  MediaWikiServices::getInstance()->getWatchedItemStore()
3901  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3902  }
3903 
3910  public function clearAllNotifications() {
3912  // Do nothing if not allowed to edit the watchlist
3913  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3914  return;
3915  }
3916 
3917  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3918  $this->setNewtalk( false );
3919  return;
3920  }
3921 
3922  $id = $this->getId();
3923  if ( !$id ) {
3924  return;
3925  }
3926 
3927  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
3928  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
3929 
3930  // We also need to clear here the "you have new message" notification for the own
3931  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3932  }
3933 
3939  public function getExperienceLevel() {
3940  global $wgLearnerEdits,
3944 
3945  if ( $this->isAnon() ) {
3946  return false;
3947  }
3948 
3949  $editCount = $this->getEditCount();
3950  $registration = $this->getRegistration();
3951  $now = time();
3952  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3953  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3954 
3955  if ( $editCount < $wgLearnerEdits ||
3956  $registration > $learnerRegistration ) {
3957  return 'newcomer';
3958  }
3959 
3960  if ( $editCount > $wgExperiencedUserEdits &&
3961  $registration <= $experiencedRegistration
3962  ) {
3963  return 'experienced';
3964  }
3965 
3966  return 'learner';
3967  }
3968 
3977  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3978  $this->load();
3979  if ( $this->mId == 0 ) {
3980  return;
3981  }
3982 
3983  $session = $this->getRequest()->getSession();
3984  if ( $request && $session->getRequest() !== $request ) {
3985  $session = $session->sessionWithRequest( $request );
3986  }
3987  $delay = $session->delaySave();
3988 
3989  if ( !$session->getUser()->equals( $this ) ) {
3990  if ( !$session->canSetUser() ) {
3992  ->warning( __METHOD__ .
3993  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3994  );
3995  return;
3996  }
3997  $session->setUser( $this );
3998  }
3999 
4000  $session->setRememberUser( $rememberMe );
4001  if ( $secure !== null ) {
4002  $session->setForceHTTPS( $secure );
4003  }
4004 
4005  $session->persist();
4006 
4007  ScopedCallback::consume( $delay );
4008  }
4009 
4013  public function logout() {
4014  // Avoid PHP 7.1 warning of passing $this by reference
4015  $user = $this;
4016  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
4017  $this->doLogout();
4018  }
4019  }
4020 
4025  public function doLogout() {
4026  $session = $this->getRequest()->getSession();
4027  if ( !$session->canSetUser() ) {
4029  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
4030  $error = 'immutable';
4031  } elseif ( !$session->getUser()->equals( $this ) ) {
4033  ->warning( __METHOD__ .
4034  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4035  );
4036  // But we still may as well make this user object anon
4037  $this->clearInstanceCache( 'defaults' );
4038  $error = 'wronguser';
4039  } else {
4040  $this->clearInstanceCache( 'defaults' );
4041  $delay = $session->delaySave();
4042  $session->unpersist(); // Clear cookies (T127436)
4043  $session->setLoggedOutTimestamp( time() );
4044  $session->setUser( new User );
4045  $session->set( 'wsUserID', 0 ); // Other code expects this
4046  $session->resetAllTokens();
4047  ScopedCallback::consume( $delay );
4048  $error = false;
4049  }
4050  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4051  'event' => 'logout',
4052  'successful' => $error === false,
4053  'status' => $error ?: 'success',
4054  ] );
4055  }
4056 
4061  public function saveSettings() {
4062  if ( wfReadOnly() ) {
4063  // @TODO: caller should deal with this instead!
4064  // This should really just be an exception.
4066  null,
4067  "Could not update user with ID '{$this->mId}'; DB is read-only."
4068  ) );
4069  return;
4070  }
4071 
4072  $this->load();
4073  if ( $this->mId == 0 ) {
4074  return; // anon
4075  }
4076 
4077  // Get a new user_touched that is higher than the old one.
4078  // This will be used for a CAS check as a last-resort safety
4079  // check against race conditions and replica DB lag.
4080  $newTouched = $this->newTouchedTimestamp();
4081 
4082  $dbw = wfGetDB( DB_MASTER );
4083  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
4085 
4086  $dbw->update( 'user',
4087  [ /* SET */
4088  'user_name' => $this->mName,
4089  'user_real_name' => $this->mRealName,
4090  'user_email' => $this->mEmail,
4091  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4092  'user_touched' => $dbw->timestamp( $newTouched ),
4093  'user_token' => strval( $this->mToken ),
4094  'user_email_token' => $this->mEmailToken,
4095  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4096  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4097  'user_id' => $this->mId,
4098  ] ), $fname
4099  );
4100 
4101  if ( !$dbw->affectedRows() ) {
4102  // Maybe the problem was a missed cache update; clear it to be safe
4103  $this->clearSharedCache( 'refresh' );
4104  // User was changed in the meantime or loaded with stale data
4105  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4106  LoggerFactory::getInstance( 'preferences' )->warning(
4107  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4108  [ 'user_id' => $this->mId, 'db_flag' => $from ]
4109  );
4110  throw new MWException( "CAS update failed on user_touched. " .
4111  "The version of the user to be saved is older than the current version."
4112  );
4113  }
4114 
4115  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4116  $dbw->update(
4117  'actor',
4118  [ 'actor_name' => $this->mName ],
4119  [ 'actor_user' => $this->mId ],
4120  $fname
4121  );
4122  }
4123  } );
4124 
4125  $this->mTouched = $newTouched;
4126  $this->saveOptions();
4127 
4128  Hooks::run( 'UserSaveSettings', [ $this ] );
4129  $this->clearSharedCache( 'changed' );
4130  $this->getUserPage()->purgeSquid();
4131  }
4132 
4139  public function idForName( $flags = 0 ) {
4140  $s = trim( $this->getName() );
4141  if ( $s === '' ) {
4142  return 0;
4143  }
4144 
4145  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4146  ? wfGetDB( DB_MASTER )
4147  : wfGetDB( DB_REPLICA );
4148 
4149  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4150  ? [ 'LOCK IN SHARE MODE' ]
4151  : [];
4152 
4153  $id = $db->selectField( 'user',
4154  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4155 
4156  return (int)$id;
4157  }
4158 
4174  public static function createNew( $name, $params = [] ) {
4175  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4176  if ( isset( $params[$field] ) ) {
4177  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4178  unset( $params[$field] );
4179  }
4180  }
4181 
4182  $user = new User;
4183  $user->load();
4184  $user->setToken(); // init token
4185  if ( isset( $params['options'] ) ) {
4186  $user->mOptions = $params['options'] + (array)$user->mOptions;
4187  unset( $params['options'] );
4188  }
4189  $dbw = wfGetDB( DB_MASTER );
4190 
4191  $noPass = PasswordFactory::newInvalidPassword()->toString();
4192 
4193  $fields = [
4194  'user_name' => $name,
4195  'user_password' => $noPass,
4196  'user_newpassword' => $noPass,
4197  'user_email' => $user->mEmail,
4198  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4199  'user_real_name' => $user->mRealName,
4200  'user_token' => strval( $user->mToken ),
4201  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4202  'user_editcount' => 0,
4203  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4204  ];
4205  foreach ( $params as $name => $value ) {
4206  $fields["user_$name"] = $value;
4207  }
4208 
4209  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
4210  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4211  if ( $dbw->affectedRows() ) {
4212  $newUser = self::newFromId( $dbw->insertId() );
4213  $newUser->mName = $fields['user_name'];
4214  $newUser->updateActorId( $dbw );
4215  // Load the user from master to avoid replica lag
4216  $newUser->load( self::READ_LATEST );
4217  } else {
4218  $newUser = null;
4219  }
4220  return $newUser;
4221  } );
4222  }
4223 
4250  public function addToDatabase() {
4251  $this->load();
4252  if ( !$this->mToken ) {
4253  $this->setToken(); // init token
4254  }
4255 
4256  if ( !is_string( $this->mName ) ) {
4257  throw new RuntimeException( "User name field is not set." );
4258  }
4259 
4260  $this->mTouched = $this->newTouchedTimestamp();
4261 
4262  $dbw = wfGetDB( DB_MASTER );
4263  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
4264  $noPass = PasswordFactory::newInvalidPassword()->toString();
4265  $dbw->insert( 'user',
4266  [
4267  'user_name' => $this->mName,
4268  'user_password' => $noPass,
4269  'user_newpassword' => $noPass,
4270  'user_email' => $this->mEmail,
4271  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4272  'user_real_name' => $this->mRealName,
4273  'user_token' => strval( $this->mToken ),
4274  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4275  'user_editcount' => 0,
4276  'user_touched' => $dbw->timestamp( $this->mTouched ),
4277  ], $fname,
4278  [ 'IGNORE' ]
4279  );
4280  if ( !$dbw->affectedRows() ) {
4281  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4282  $this->mId = $dbw->selectField(
4283  'user',
4284  'user_id',
4285  [ 'user_name' => $this->mName ],
4286  $fname,
4287  [ 'LOCK IN SHARE MODE' ]
4288  );
4289  $loaded = false;
4290  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
4291  $loaded = true;
4292  }
4293  if ( !$loaded ) {
4294  throw new MWException( $fname . ": hit a key conflict attempting " .
4295  "to insert user '{$this->mName}' row, but it was not present in select!" );
4296  }
4297  return Status::newFatal( 'userexists' );
4298  }
4299  $this->mId = $dbw->insertId();
4300  self::$idCacheByName[$this->mName] = $this->mId;
4301  $this->updateActorId( $dbw );
4302 
4303  return Status::newGood();
4304  } );
4305  if ( !$status->isGood() ) {
4306  return $status;
4307  }
4308 
4309  // Clear instance cache other than user table data and actor, which is already accurate
4310  $this->clearInstanceCache();
4311 
4312  $this->saveOptions();
4313  return Status::newGood();
4314  }
4315 
4320  private function updateActorId( IDatabase $dbw ) {
4322 
4323  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4324  $dbw->insert(
4325  'actor',
4326  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4327  __METHOD__
4328  );
4329  $this->mActorId = (int)$dbw->insertId();
4330  }
4331  }
4332 
4338  public function spreadAnyEditBlock() {
4339  if ( $this->isLoggedIn() && $this->getBlock() ) {
4340  return $this->spreadBlock();
4341  }
4342 
4343  return false;
4344  }
4345 
4351  protected function spreadBlock() {
4352  wfDebug( __METHOD__ . "()\n" );
4353  $this->load();
4354  if ( $this->mId == 0 ) {
4355  return false;
4356  }
4357 
4358  $userblock = Block::newFromTarget( $this->getName() );
4359  if ( !$userblock ) {
4360  return false;
4361  }
4362 
4363  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4364  }
4365 
4370  public function isBlockedFromCreateAccount() {
4371  $this->getBlockedStatus();
4372  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4373  return $this->mBlock;
4374  }
4375 
4376  # T15611: if the IP address the user is trying to create an account from is
4377  # blocked with createaccount disabled, prevent new account creation there even
4378  # when the user is logged in
4379  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4380  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4381  }
4382  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
4383  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4384  ? $this->mBlockedFromCreateAccount
4385  : false;
4386  }
4387 
4392  public function isBlockedFromEmailuser() {
4393  $this->getBlockedStatus();
4394  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4395  }
4396 
4403  public function isBlockedFromUpload() {
4404  $this->getBlockedStatus();
4405  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4406  }
4407 
4412  public function isAllowedToCreateAccount() {
4413  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4414  }
4415 
4421  public function getUserPage() {
4422  return Title::makeTitle( NS_USER, $this->getName() );
4423  }
4424 
4430  public function getTalkPage() {
4431  $title = $this->getUserPage();
4432  return $title->getTalkPage();
4433  }
4434 
4440  public function isNewbie() {
4441  return !$this->isAllowed( 'autoconfirmed' );
4442  }
4443 
4450  public function checkPassword( $password ) {
4451  wfDeprecated( __METHOD__, '1.27' );
4452 
4453  $manager = AuthManager::singleton();
4454  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4455  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4456  [
4457  'username' => $this->getName(),
4458  'password' => $password,
4459  ]
4460  );
4461  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4462  switch ( $res->status ) {
4463  case AuthenticationResponse::PASS:
4464  return true;
4465  case AuthenticationResponse::FAIL:
4466  // Hope it's not a PreAuthenticationProvider that failed...
4468  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4469  return false;
4470  default:
4471  throw new BadMethodCallException(
4472  'AuthManager returned a response unsupported by ' . __METHOD__
4473  );
4474  }
4475  }
4476 
4485  public function checkTemporaryPassword( $plaintext ) {
4486  wfDeprecated( __METHOD__, '1.27' );
4487  // Can't check the temporary password individually.
4488  return $this->checkPassword( $plaintext );
4489  }
4490 
4502  public function getEditTokenObject( $salt = '', $request = null ) {
4503  if ( $this->isAnon() ) {
4504  return new LoggedOutEditToken();
4505  }
4506 
4507  if ( !$request ) {
4508  $request = $this->getRequest();
4509  }
4510  return $request->getSession()->getToken( $salt );
4511  }
4512 
4526  public function getEditToken( $salt = '', $request = null ) {
4527  return $this->getEditTokenObject( $salt, $request )->toString();
4528  }
4529 
4542  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4543  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4544  }
4545 
4556  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4557  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4558  return $this->matchEditToken( $val, $salt, $request, $maxage );
4559  }
4560 
4568  public function sendConfirmationMail( $type = 'created' ) {
4569  global $wgLang;
4570  $expiration = null; // gets passed-by-ref and defined in next line.
4571  $token = $this->confirmationToken( $expiration );
4572  $url = $this->confirmationTokenUrl( $token );
4573  $invalidateURL = $this->invalidationTokenUrl( $token );
4574  $this->saveSettings();
4575 
4576  if ( $type == 'created' || $type === false ) {
4577  $message = 'confirmemail_body';
4578  $type = 'created';
4579  } elseif ( $type === true ) {
4580  $message = 'confirmemail_body_changed';
4581  $type = 'changed';
4582  } else {
4583  // Messages: confirmemail_body_changed, confirmemail_body_set
4584  $message = 'confirmemail_body_' . $type;
4585  }
4586 
4587  $mail = [
4588  'subject' => wfMessage( 'confirmemail_subject' )->text(),
4589  'body' => wfMessage( $message,
4590  $this->getRequest()->getIP(),
4591  $this->getName(),
4592  $url,
4593  $wgLang->userTimeAndDate( $expiration, $this ),
4594  $invalidateURL,
4595  $wgLang->userDate( $expiration, $this ),
4596  $wgLang->userTime( $expiration, $this ) )->text(),
4597  'from' => null,
4598  'replyTo' => null,
4599  ];
4600  $info = [
4601  'type' => $type,
4602  'ip' => $this->getRequest()->getIP(),
4603  'confirmURL' => $url,
4604  'invalidateURL' => $invalidateURL,
4605  'expiration' => $expiration
4606  ];
4607 
4608  Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4609  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4610  }
4611 
4623  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4624  global $wgPasswordSender;
4625 
4626  if ( $from instanceof User ) {
4627  $sender = MailAddress::newFromUser( $from );
4628  } else {
4629  $sender = new MailAddress( $wgPasswordSender,
4630  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4631  }
4632  $to = MailAddress::newFromUser( $this );
4633 
4634  return UserMailer::send( $to, $sender, $subject, $body, [
4635  'replyTo' => $replyto,
4636  ] );
4637  }
4638 
4649  protected function confirmationToken( &$expiration ) {
4651  $now = time();
4652  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4653  $expiration = wfTimestamp( TS_MW, $expires );
4654  $this->load();
4655  $token = MWCryptRand::generateHex( 32 );
4656  $hash = md5( $token );
4657  $this->mEmailToken = $hash;
4658  $this->mEmailTokenExpires = $expiration;
4659  return $token;
4660  }
4661 
4667  protected function confirmationTokenUrl( $token ) {
4668  return $this->getTokenUrl( 'ConfirmEmail', $token );
4669  }
4670 
4676  protected function invalidationTokenUrl( $token ) {
4677  return $this->getTokenUrl( 'InvalidateEmail', $token );
4678  }
4679 
4694  protected function getTokenUrl( $page, $token ) {
4695  // Hack to bypass localization of 'Special:'
4696  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4697  return $title->getCanonicalURL();
4698  }
4699 
4707  public function confirmEmail() {
4708  // Check if it's already confirmed, so we don't touch the database
4709  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4710  if ( !$this->isEmailConfirmed() ) {
4712  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4713  }
4714  return true;
4715  }
4716 
4724  public function invalidateEmail() {
4725  $this->load();
4726  $this->mEmailToken = null;
4727  $this->mEmailTokenExpires = null;
4729  $this->mEmail = '';
4730  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4731  return true;
4732  }
4733 
4738  public function setEmailAuthenticationTimestamp( $timestamp ) {
4739  $this->load();
4740  $this->mEmailAuthenticated = $timestamp;
4741  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4742  }
4743 
4749  public function canSendEmail() {
4751  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4752  return false;
4753  }
4754  $canSend = $this->isEmailConfirmed();
4755  // Avoid PHP 7.1 warning of passing $this by reference
4756  $user = $this;
4757  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4758  return $canSend;
4759  }
4760 
4766  public function canReceiveEmail() {
4767  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4768  }
4769 
4780  public function isEmailConfirmed() {
4781  global $wgEmailAuthentication;
4782  $this->load();
4783  // Avoid PHP 7.1 warning of passing $this by reference
4784  $user = $this;
4785  $confirmed = true;
4786  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4787  if ( $this->isAnon() ) {
4788  return false;
4789  }
4790  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4791  return false;
4792  }
4793  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4794  return false;
4795  }
4796  return true;
4797  }
4798 
4799  return $confirmed;
4800  }
4801 
4806  public function isEmailConfirmationPending() {
4807  global $wgEmailAuthentication;
4808  return $wgEmailAuthentication &&
4809  !$this->isEmailConfirmed() &&
4810  $this->mEmailToken &&
4811  $this->mEmailTokenExpires > wfTimestamp();
4812  }
4813 
4821  public function getRegistration() {
4822  if ( $this->isAnon() ) {
4823  return false;
4824  }
4825  $this->load();
4826  return $this->mRegistration;
4827  }
4828 
4835  public function getFirstEditTimestamp() {
4836  return $this->getEditTimestamp( true );
4837  }
4838 
4846  public function getLatestEditTimestamp() {
4847  return $this->getEditTimestamp( false );
4848  }
4849 
4857  private function getEditTimestamp( $first ) {
4858  if ( $this->getId() == 0 ) {
4859  return false; // anons
4860  }
4861  $dbr = wfGetDB( DB_REPLICA );
4862  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4863  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4864  ? 'revactor_timestamp' : 'rev_timestamp';
4865  $sortOrder = $first ? 'ASC' : 'DESC';
4866  $time = $dbr->selectField(
4867  [ 'revision' ] + $actorWhere['tables'],
4868  $tsField,
4869  [ $actorWhere['conds'] ],
4870  __METHOD__,
4871  [ 'ORDER BY' => "$tsField $sortOrder" ],
4872  $actorWhere['joins']
4873  );
4874  if ( !$time ) {
4875  return false; // no edits
4876  }
4877  return wfTimestamp( TS_MW, $time );
4878  }
4879 
4886  public static function getGroupPermissions( $groups ) {
4888  $rights = [];
4889  // grant every granted permission first
4890  foreach ( $groups as $group ) {
4891  if ( isset( $wgGroupPermissions[$group] ) ) {
4892  $rights = array_merge( $rights,
4893  // array_filter removes empty items
4894  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4895  }
4896  }
4897  // now revoke the revoked permissions
4898  foreach ( $groups as $group ) {
4899  if ( isset( $wgRevokePermissions[$group] ) ) {
4900  $rights = array_diff( $rights,
4901  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4902  }
4903  }
4904  return array_unique( $rights );
4905  }
4906 
4913  public static function getGroupsWithPermission( $role ) {
4914  global $wgGroupPermissions;
4915  $allowedGroups = [];
4916  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4917  if ( self::groupHasPermission( $group, $role ) ) {
4918  $allowedGroups[] = $group;
4919  }
4920  }
4921  return $allowedGroups;
4922  }
4923 
4936  public static function groupHasPermission( $group, $role ) {
4938  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
4939  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
4940  }
4941 
4956  public static function isEveryoneAllowed( $right ) {
4958  static $cache = [];
4959 
4960  // Use the cached results, except in unit tests which rely on
4961  // being able change the permission mid-request
4962  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
4963  return $cache[$right];
4964  }
4965 
4966  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
4967  $cache[$right] = false;
4968  return false;
4969  }
4970 
4971  // If it's revoked anywhere, then everyone doesn't have it
4972  foreach ( $wgRevokePermissions as $rights ) {
4973  if ( isset( $rights[$right] ) && $rights[$right] ) {
4974  $cache[$right] = false;
4975  return false;
4976  }
4977  }
4978 
4979  // Remove any rights that aren't allowed to the global-session user,
4980  // unless there are no sessions for this endpoint.
4981  if ( !defined( 'MW_NO_SESSION' ) ) {
4982  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
4983  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
4984  $cache[$right] = false;
4985  return false;
4986  }
4987  }
4988 
4989  // Allow extensions to say false
4990  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
4991  $cache[$right] = false;
4992  return false;
4993  }
4994 
4995  $cache[$right] = true;
4996  return true;
4997  }
4998 
5005  public static function getAllGroups() {
5007  return array_values( array_diff(
5008  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
5009  self::getImplicitGroups()
5010  ) );
5011  }
5012 
5017  public static function getAllRights() {
5018  if ( self::$mAllRights === false ) {
5019  global $wgAvailableRights;
5020  if ( count( $wgAvailableRights ) ) {
5021  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
5022  } else {
5023  self::$mAllRights = self::$mCoreRights;
5024  }
5025  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
5026  }
5027  return self::$mAllRights;
5028  }
5029 
5036  public static function getImplicitGroups() {
5037  global $wgImplicitGroups;
5038  return $wgImplicitGroups;
5039  }
5040 
5050  public static function changeableByGroup( $group ) {
5052 
5053  $groups = [
5054  'add' => [],
5055  'remove' => [],
5056  'add-self' => [],
5057  'remove-self' => []
5058  ];
5059 
5060  if ( empty( $wgAddGroups[$group] ) ) {
5061  // Don't add anything to $groups
5062  } elseif ( $wgAddGroups[$group] === true ) {
5063  // You get everything
5064  $groups['add'] = self::getAllGroups();
5065  } elseif ( is_array( $wgAddGroups[$group] ) ) {
5066  $groups['add'] = $wgAddGroups[$group];
5067  }
5068 
5069  // Same thing for remove
5070  if ( empty( $wgRemoveGroups[$group] ) ) {
5071  // Do nothing
5072  } elseif ( $wgRemoveGroups[$group] === true ) {
5073  $groups['remove'] = self::getAllGroups();
5074  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5075  $groups['remove'] = $wgRemoveGroups[$group];
5076  }
5077 
5078  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5079  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5080  foreach ( $wgGroupsAddToSelf as $key => $value ) {
5081  if ( is_int( $key ) ) {
5082  $wgGroupsAddToSelf['user'][] = $value;
5083  }
5084  }
5085  }
5086 
5087  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5088  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5089  if ( is_int( $key ) ) {
5090  $wgGroupsRemoveFromSelf['user'][] = $value;
5091  }
5092  }
5093  }
5094 
5095  // Now figure out what groups the user can add to him/herself
5096  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5097  // Do nothing
5098  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5099  // No idea WHY this would be used, but it's there
5100  $groups['add-self'] = self::getAllGroups();
5101  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5102  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5103  }
5104 
5105  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5106  // Do nothing
5107  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5108  $groups['remove-self'] = self::getAllGroups();
5109  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5110  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5111  }
5112 
5113  return $groups;
5114  }
5115 
5123  public function changeableGroups() {
5124  if ( $this->isAllowed( 'userrights' ) ) {
5125  // This group gives the right to modify everything (reverse-
5126  // compatibility with old "userrights lets you change
5127  // everything")
5128  // Using array_merge to make the groups reindexed
5129  $all = array_merge( self::getAllGroups() );
5130  return [
5131  'add' => $all,
5132  'remove' => $all,
5133  'add-self' => [],
5134  'remove-self' => []
5135  ];
5136  }
5137 
5138  // Okay, it's not so simple, we will have to go through the arrays
5139  $groups = [
5140  'add' => [],
5141  'remove' => [],
5142  'add-self' => [],
5143  'remove-self' => []
5144  ];
5145  $addergroups = $this->getEffectiveGroups();
5146 
5147  foreach ( $addergroups as $addergroup ) {
5148  $groups = array_merge_recursive(
5149  $groups, $this->changeableByGroup( $addergroup )
5150  );
5151  $groups['add'] = array_unique( $groups['add'] );
5152  $groups['remove'] = array_unique( $groups['remove'] );
5153  $groups['add-self'] = array_unique( $groups['add-self'] );
5154  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5155  }
5156  return $groups;
5157  }
5158 
5162  public function incEditCount() {
5163  if ( $this->isAnon() ) {
5164  return; // sanity
5165  }
5166 
5168  new UserEditCountUpdate( $this, 1 ),
5170  );
5171  }
5172 
5178  public function setEditCountInternal( $count ) {
5179  $this->mEditCount = $count;
5180  }
5181 
5189  public function initEditCountInternal() {
5190  // Pull from a replica DB to be less cruel to servers
5191  // Accuracy isn't the point anyway here
5192  $dbr = wfGetDB( DB_REPLICA );
5193  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5194  $count = (int)$dbr->selectField(
5195  [ 'revision' ] + $actorWhere['tables'],
5196  'COUNT(*)',
5197  [ $actorWhere['conds'] ],
5198  __METHOD__,
5199  [],
5200  $actorWhere['joins']
5201  );
5202 
5203  $dbw = wfGetDB( DB_MASTER );
5204  $dbw->update(
5205  'user',
5206  [ 'user_editcount' => $count ],
5207  [
5208  'user_id' => $this->getId(),
5209  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5210  ],
5211  __METHOD__
5212  );
5213 
5214  return $count;
5215  }
5216 
5224  public static function getRightDescription( $right ) {
5225  $key = "right-$right";
5226  $msg = wfMessage( $key );
5227  return $msg->isDisabled() ? $right : $msg->text();
5228  }
5229 
5237  public static function getGrantName( $grant ) {
5238  $key = "grant-$grant";
5239  $msg = wfMessage( $key );
5240  return $msg->isDisabled() ? $grant : $msg->text();
5241  }
5242 
5263  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5264  return true; // disabled
5265  }
5266 
5275  public function addNewUserLogEntryAutoCreate() {
5276  $this->addNewUserLogEntry( 'autocreate' );
5277 
5278  return true;
5279  }
5280 
5286  protected function loadOptions( $data = null ) {
5287  $this->load();
5288 
5289  if ( $this->mOptionsLoaded ) {
5290  return;
5291  }
5292 
5293  $this->mOptions = self::getDefaultOptions();
5294 
5295  if ( !$this->getId() ) {
5296  // For unlogged-in users, load language/variant options from request.
5297  // There's no need to do it for logged-in users: they can set preferences,
5298  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5299  // so don't override user's choice (especially when the user chooses site default).
5300  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5301  $this->mOptions['variant'] = $variant;
5302  $this->mOptions['language'] = $variant;
5303  $this->mOptionsLoaded = true;
5304  return;
5305  }
5306 
5307  // Maybe load from the object
5308  if ( !is_null( $this->mOptionOverrides ) ) {
5309  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5310  foreach ( $this->mOptionOverrides as $key => $value ) {
5311  $this->mOptions[$key] = $value;
5312  }
5313  } else {
5314  if ( !is_array( $data ) ) {
5315  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5316  // Load from database
5317  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5318  ? wfGetDB( DB_MASTER )
5319  : wfGetDB( DB_REPLICA );
5320 
5321  $res = $dbr->select(
5322  'user_properties',
5323  [ 'up_property', 'up_value' ],
5324  [ 'up_user' => $this->getId() ],
5325  __METHOD__
5326  );
5327 
5328  $this->mOptionOverrides = [];
5329  $data = [];
5330  foreach ( $res as $row ) {
5331  // Convert '0' to 0. PHP's boolean conversion considers them both
5332  // false, but e.g. JavaScript considers the former as true.
5333  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5334  // and convert all values here.
5335  if ( $row->up_value === '0' ) {
5336  $row->up_value = 0;
5337  }
5338  $data[$row->up_property] = $row->up_value;
5339  }
5340  }
5341 
5342  foreach ( $data as $property => $value ) {
5343  $this->mOptionOverrides[$property] = $value;
5344  $this->mOptions[$property] = $value;
5345  }
5346  }
5347 
5348  // Replace deprecated language codes
5349  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5350  $this->mOptions['language']
5351  );
5352 
5353  $this->mOptionsLoaded = true;
5354 
5355  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5356  }
5357 
5363  protected function saveOptions() {
5364  $this->loadOptions();
5365 
5366  // Not using getOptions(), to keep hidden preferences in database
5367  $saveOptions = $this->mOptions;
5368 
5369  // Allow hooks to abort, for instance to save to a global profile.
5370  // Reset options to default state before saving.
5371  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5372  return;
5373  }
5374 
5375  $userId = $this->getId();
5376 
5377  $insert_rows = []; // all the new preference rows
5378  foreach ( $saveOptions as $key => $value ) {
5379  // Don't bother storing default values
5380  $defaultOption = self::getDefaultOption( $key );
5381  if ( ( $defaultOption === null && $value !== false && $value !== null )
5382  || $value != $defaultOption
5383  ) {
5384  $insert_rows[] = [
5385  'up_user' => $userId,
5386  'up_property' => $key,
5387  'up_value' => $value,
5388  ];
5389  }
5390  }
5391 
5392  $dbw = wfGetDB( DB_MASTER );
5393 
5394  $res = $dbw->select( 'user_properties',
5395  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5396 
5397  // Find prior rows that need to be removed or updated. These rows will
5398  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5399  $keysDelete = [];
5400  foreach ( $res as $row ) {
5401  if ( !isset( $saveOptions[$row->up_property] )
5402  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5403  ) {
5404  $keysDelete[] = $row->up_property;
5405  }
5406  }
5407 
5408  if ( count( $keysDelete ) ) {
5409  // Do the DELETE by PRIMARY KEY for prior rows.
5410  // In the past a very large portion of calls to this function are for setting
5411  // 'rememberpassword' for new accounts (a preference that has since been removed).
5412  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5413  // caused gap locks on [max user ID,+infinity) which caused high contention since
5414  // updates would pile up on each other as they are for higher (newer) user IDs.
5415  // It might not be necessary these days, but it shouldn't hurt either.
5416  $dbw->delete( 'user_properties',
5417  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5418  }
5419  // Insert the new preference rows
5420  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5421  }
5422 
5429  public static function selectFields() {
5430  wfDeprecated( __METHOD__, '1.31' );
5431  return [
5432  'user_id',
5433  'user_name',
5434  'user_real_name',
5435  'user_email',
5436  'user_touched',
5437  'user_token',
5438  'user_email_authenticated',
5439  'user_email_token',
5440  'user_email_token_expires',
5441  'user_registration',
5442  'user_editcount',
5443  ];
5444  }
5445 
5455  public static function getQueryInfo() {
5457 
5458  $ret = [
5459  'tables' => [ 'user' ],
5460  'fields' => [
5461  'user_id',
5462  'user_name',
5463  'user_real_name',
5464  'user_email',
5465  'user_touched',
5466  'user_token',
5467  'user_email_authenticated',
5468  'user_email_token',
5469  'user_email_token_expires',
5470  'user_registration',
5471  'user_editcount',
5472  ],
5473  'joins' => [],
5474  ];
5475 
5476  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
5477  // but it does little harm and might be needed for write callers loading a User.
5478  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
5479  $ret['tables']['user_actor'] = 'actor';
5480  $ret['fields'][] = 'user_actor.actor_id';
5481  $ret['joins']['user_actor'] = [
5482  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
5483  [ 'user_actor.actor_user = user_id' ]
5484  ];
5485  }
5486 
5487  return $ret;
5488  }
5489 
5497  static function newFatalPermissionDeniedStatus( $permission ) {
5498  global $wgLang;
5499 
5500  $groups = [];
5501  foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5502  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5503  }
5504 
5505  if ( $groups ) {
5506  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5507  }
5508 
5509  return Status::newFatal( 'badaccess-group0' );
5510  }
5511 
5521  public function getInstanceForUpdate() {
5522  if ( !$this->getId() ) {
5523  return null; // anon
5524  }
5525 
5526  $user = self::newFromId( $this->getId() );
5527  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5528  return null;
5529  }
5530 
5531  return $user;
5532  }
5533 
5541  public function equals( UserIdentity $user ) {
5542  // XXX it's not clear whether central ID providers are supposed to obey this
5543  return $this->getName() === $user->getName();
5544  }
5545 
5551  public function isAllowUsertalk() {
5552  return $this->mAllowUsertalk;
5553  }
5554 
5555 }
static array null $defOpt
Definition: User.php:1727
getEmail()
Get the user&#39;s e-mail address.
Definition: User.php:2911
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))
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
isHidden()
Check if user account is hidden.
Definition: User.php:2267
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
static getMembership( $userId, $group, IDatabase $db=null)
Returns a UserGroupMembership object that pertains to the given user and group, or false if the user ...
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2762
string $mBlockedby
Definition: User.php:270
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:125
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:66
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2633
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
setRealName( $str)
Set the user&#39;s real name.
Definition: User.php:3013
$wgMaxArticleSize
Maximum article size in kilobytes.
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1332
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2463
isAllowedAll()
Definition: User.php:3715
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1707
string $mDatePreference
Definition: User.php:268
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4440
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:898
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:102
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3808
$property
$wgMaxNameChars
Maximum number of bytes in username.
UserGroupMembership [] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:234
affectedRows()
Get the number of rows affected by the last write query.
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
loadFromSession()
Load user data from the session.
Definition: User.php:1343
const NS_MAIN
Definition: Defines.php:64
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:5050
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4412
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
$success
logout()
Log this user out.
Definition: User.php:4013
clearNotification(&$title, $oldid=0)
Clear the user&#39;s notification timestamp for the given title.
Definition: User.php:3842
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4886
static getImplicitGroups()
Get a list of implicit groups TODO: Should we deprecate this? It&#39;s trivial, but we don&#39;t want to enco...
Definition: User.php:5036
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
saveSettings()
Save this user&#39;s settings into the database.
Definition: User.php:4061
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2302
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Definition: UserMailer.php:115
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4835
static $mCoreRights
Array of Strings Core rights.
Definition: User.php:117
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:1982
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4956
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:980
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2693
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:72
initEditCountInternal()
Initialize user_editcount from data out of the revision table.
Definition: User.php:5189
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3700
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4392
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3477
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:55
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2678
Handles increment the edit count for a given set of users.
AbstractBlock $mGlobalBlock
Definition: User.php:284
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2205
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target&#39;s type, get an existing Block object if possible.
Definition: Block.php:1102
setName( $str)
Set the user name.
Definition: User.php:2339
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3028
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2174
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:220
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:295
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2148
static getWikiIdFromDbDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:269
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3939
__toString()
Definition: User.php:327
static $idCacheByName
Definition: User.php:307
Exception thrown when an actor can&#39;t be created.
null for the local wiki Added in
Definition: hooks.txt:1585
getRealName()
Get the user&#39;s real name.
Definition: User.php:3001
$wgSecureLogin
This is to let user authenticate using https when they come from http.
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2523
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
$value
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user&#39;s account.
Definition: User.php:4623
Check if a user&#39;s password complies with any password policies that apply to that user...
checkAndSetTouched()
Bump user_touched if it didn&#39;t change since this object was loaded.
Definition: User.php:1669
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5123
clearAllNotifications()
Resets all of the given user&#39;s page-change notification timestamps.
Definition: User.php:3910
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:287
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4556
static string null $defOptLang
Definition: User.php:1729
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3667
static getLocalClusterInstance()
Get the main cluster-local cache object.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
$wgGroupsRemoveFromSelf
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2883
insertId()
Get the inserted value of an auto-increment row.
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:918
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:739
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of or an object and a method hook function The function part of a third party developers and local administrators to define code that will be run at certain points in the mainline and to modify the data run by that mainline code Hooks can keep mainline code and make it easier to write extensions Hooks are a principled alternative to local patches for two options in MediaWiki One reverses the order of a title before displaying the article
Definition: hooks.txt:23
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition: User.php:4403
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4485
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
$wgHiddenPrefs
An array of preferences to not show for the user.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object...
Definition: User.php:5455
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:960
array $mFormerGroups
Definition: User.php:282
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2727
target page
$wgRevokePermissions
Permission keys revoked from users in each group.
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:628
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3115
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2219
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition: User.php:4857
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1853
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1799
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:2137
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Value object representing a logged-out user&#39;s edit token.
const DB_MASTER
Definition: defines.php:26
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition: IP.php:729
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user&#39;s session (e.g.
Definition: User.php:3977
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2311
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5429
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4320
string $mName
Cache variables.
Definition: User.php:209
string $mRegistration
Cache variables.
Definition: User.php:230
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
int $mEditCount
Cache variables.
Definition: User.php:232
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2813
int null $mActorId
Cache variables.
Definition: User.php:211
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3825
static newFromAnyId( $userId, $userName, $actorId, $wikiId=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:681
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1980
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4450
getTitleKey()
Get the user&#39;s name escaped by underscores.
Definition: User.php:2417
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:5005
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3198
$wgGroupPermissions
Permission keys given to users in each group.
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1090
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:2118
Interface for objects representing user identity.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:50
string $mEmailTokenExpires
Cache variables.
Definition: User.php:228
static getAllRights()
Get a list of all available permissions.
Definition: User.php:5017
isEmailConfirmed()
Is this user&#39;s e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4780
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:1982
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:4526
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1228
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1263
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4370
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:462
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:908
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:243
makeGlobalKey( $class, $component=null)
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2538
string $mEmailToken
Cache variables.
Definition: User.php:226
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it&#39;s empty (and savin...
Definition: User.php:3136
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1600
$wgPasswordPolicy
Password policy for the wiki.
AbstractBlock $mBlockedFromCreateAccount
Definition: User.php:302
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4821
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects...
Definition: User.php:3464
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
wfReadOnly()
Check whether the wiki is in read-only mode.
$wgLang
Definition: Setup.php:928
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5224
static newMigration()
Static constructor.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
static getMain()
Get the RequestContext object associated with the main request.
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations, but may be used internally either by batch processes or by user accounts which have already been created.
Definition: User.php:1130
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4667
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing we can concentrate it all in an extension file
Definition: hooks.txt:91
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:514
static newFromIDs( $ids)
Definition: UserArray.php:45
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5275
$wgExperiencedUserEdits
Name of the external diff engine to use.
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1160
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4913
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:139
getBoolOption( $oname)
Get the user&#39;s current setting for a given option, as a boolean value.
Definition: User.php:3087
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1651
string $mBlockreason
Definition: User.php:276
isAnon()
Get whether the user is anonymous.
Definition: User.php:3675
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5551
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1805
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4694
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2635
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1914
Stores a single person&#39;s name and email address.
Definition: MailAddress.php:32
static purge( $wikiId, $userId)
Definition: User.php:492
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:286
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5237
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3164
$res
Definition: database.txt:21
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:305
$wgImplicitGroups
Implicit groups, aren&#39;t shown on Special:Listusers or somewhere else.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getEditTokenObject( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4502
doLogout()
Clear the user&#39;s session, and reset the instance cache.
Definition: User.php:4025
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:362
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5541
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:526
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4806
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:55
incEditCount()
Schedule a deferred update to update the user&#39;s edit count.
Definition: User.php:5162
System blocks are temporary blocks that are created on enforcement (e.g.
Definition: SystemBlock.php:35
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2616
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1866
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
getOptionKinds(IContextSource $context, $options=null)
Return an associative array mapping preferences keys to the kind of a preference they&#39;re used for...
Definition: User.php:3221
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1395
$wgProxyList
Big list of banned IP addresses.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:218
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4749
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4846
array $mEffectiveGroups
Definition: User.php:278
$cache
Definition: mcc.php:33
$wgAvailableRights
A list of available rights, in addition to the ones defined by the core.
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2775
const IGNORE_USER_RIGHTS
Definition: User.php:82
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
$params
string $mEmailAuthenticated
Cache variables.
Definition: User.php:224
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3730
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:1982
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
array $wgLearnerEdits
The following variables define 3 user experience levels:
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3794
WebRequest $mRequest
Definition: User.php:293
$wgRemoveGroups
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format...
Definition: MWCryptRand.php:36
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1322
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:35
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:1055
if(ini_get('mbstring.func_overload'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:51
trackBlockWithCookie()
Set the &#39;BlockID&#39; cookie depending on block type and user authentication status.
Definition: User.php:1378
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
requiresHTTPS()
Determine based on the wiki configuration and the user&#39;s options, whether this user must be over HTTP...
Definition: User.php:3371
$wgRateLimits
Simple rate limiter options to brake edit floods.
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2165
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3659
array $mImplicitGroups
Definition: User.php:280
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:202
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1566
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they&#39;ve successfully logged in from...
Definition: User.php:4351
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
static hasFlags( $bitfield, $flags)
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
isBot()
Definition: User.php:3683
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
AbstractBlock $mBlock
Definition: User.php:296
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4738
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1766
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:5178
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:780
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:656
string $mToken
Cache variables.
Definition: User.php:222
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:123
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4936
getToken( $forceCreation=true)
Get the user&#39;s current token.
Definition: User.php:2840
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:982
static resetGetDefaultOptionsForTestsOnly()
Reset the process cache of default user options.
Definition: User.php:1737
static newInvalidPassword()
Create an InvalidPassword.
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3743
$wgPasswordSender
Sender email address for e-mail notifications.
appliesToRight( $right)
Determine whether the Block prevents a given right.
$wgLearnerMemberSince
Name of the external diff engine to use.
loadDefaults( $name=false)
Set cached properties to default.
Definition: User.php:1283
array $mOptions
Definition: User.php:290
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1791
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:2903
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:266
invalidateEmail()
Invalidate the user&#39;s e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4724
loadGroups()
Load the groups from the database if they aren&#39;t already loaded.
Definition: User.php:1576
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5363
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:617
isIPRange()
Is the user an IP range?
Definition: User.php:991
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:589
static getCurrentWikiDbDomain()
Definition: WikiMap.php:302
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3391
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3777
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
string $mEmail
Cache variables.
Definition: User.php:216
this hook is for auditing only $req
Definition: hooks.txt:979
getNewtalk()
Check if the user has new messages.
Definition: User.php:2425
bool $mLocked
Definition: User.php:286
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:613
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1448
$wgUseEnotif
Definition: Setup.php:437
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3451
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:320
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date...
getIntOption( $oname, $defaultOverride=0)
Get the user&#39;s current setting for a given option, as an integer value.
Definition: User.php:3099
idForName( $flags=0)
If only this user&#39;s username is known, and it exists, return the user ID.
Definition: User.php:4139
getOptions( $flags=0)
Get all user&#39;s options.
Definition: User.php:3056
The ContentHandler facility adds support for arbitrary content types on wiki pages
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they&#39;ve successfully logged in from...
Definition: User.php:4338
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:322
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:1006
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3764
getId()
Get the user&#39;s ID.
Definition: User.php:2284
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4542
getEmailAuthenticationTimestamp()
Get the timestamp of the user&#39;s e-mail authentication.
Definition: User.php:2921
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1987
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5286
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4250
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
getActorId(IDatabase $dbw=null)
Get the user&#39;s actor ID.
Definition: User.php:2350
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:785
bool $mAllowUsertalk
Definition: User.php:299
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2661
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4707
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5497
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
bool $mHideName
Definition: User.php:288
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:763
getEditCount()
Get the user&#39;s edit count.
Definition: User.php:3553
getUserPage()
Get this user&#39;s personal page title.
Definition: User.php:4421
setNewtalk( $val, $curRev=null)
Update the &#39;You have new messages!&#39; status.
Definition: User.php:2583
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5521
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3752
const SCHEMA_COMPAT_NEW
Definition: Defines.php:291
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:2633
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3351
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2192
array $mRights
Definition: User.php:274
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:655
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user&#39;s give...
Definition: User.php:4568
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2496
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:261
MediaWiki Logger LoggerFactory implements a PSR [0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method. MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances. The "Spi" in MediaWiki\Logger\Spi stands for "service provider interface". An SPI is an API intended to be implemented or extended by a third party. This software design pattern is intended to enable framework extension and replaceable components. It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki. The service provider interface allows the backend logging library to be implemented in multiple ways. The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime. This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance. Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
isLocked()
Check if user account is locked.
Definition: User.php:2252
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:780
string $mHash
Definition: User.php:272
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2563
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4766
const DB_REPLICA
Definition: defines.php:25
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS...
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4676
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1186
setEmailWithConfirmation( $str)
Set the user&#39;s e-mail address and a confirmation mail if needed.
Definition: User.php:2948
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4174
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3500
array $mOptionOverrides
Cache variables.
Definition: User.php:236
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:589
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1749
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:60
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2749
string $mRealName
Cache variables.
Definition: User.php:213
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3588
isSafeToLoad()
Test if it&#39;s safe to load this User object.
Definition: User.php:345
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:118
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
getCacheKey(WANObjectCache $cache)
Definition: User.php:503
setEmail( $str)
Set the user&#39;s e-mail address.
Definition: User.php:2931
const NS_USER_TALK
Definition: Defines.php:67
static array $languagesWithVariants
languages supporting variants
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5263
getTalkPage()
Get this user&#39;s talk page title.
Definition: User.php:4430
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1962
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:2633
static singleton()
Definition: UserCache.php:34
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3624
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:90
const CHECK_USER_RIGHTS
Definition: User.php:77
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1473
getRights()
Get the permissions this user has.
Definition: User.php:3406
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3529
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2183
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
Base class for the more common types of database errors.
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3306
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:826
int $mId
Cache variables.
Definition: User.php:207
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4649
getTouched()
Get the user touched timestamp.
Definition: User.php:2705
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:248
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319