MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
36 
47 class User implements IDBAccessObject, UserIdentity {
51  const TOKEN_LENGTH = 32;
52 
56  const INVALID_TOKEN = '*** INVALID ***';
57 
61  const VERSION = 13;
62 
68 
72  const CHECK_USER_RIGHTS = true;
73 
77  const IGNORE_USER_RIGHTS = false;
78 
85  protected static $mCacheVars = [
86  // user table
87  'mId',
88  'mName',
89  'mRealName',
90  'mEmail',
91  'mTouched',
92  'mToken',
93  'mEmailAuthenticated',
94  'mEmailToken',
95  'mEmailTokenExpires',
96  'mRegistration',
97  'mEditCount',
98  // user_groups table
99  'mGroupMemberships',
100  // user_properties table
101  'mOptionOverrides',
102  // actor table
103  'mActorId',
104  ];
105 
112  protected static $mCoreRights = [
113  'apihighlimits',
114  'applychangetags',
115  'autoconfirmed',
116  'autocreateaccount',
117  'autopatrol',
118  'bigdelete',
119  'block',
120  'blockemail',
121  'bot',
122  'browsearchive',
123  'changetags',
124  'createaccount',
125  'createpage',
126  'createtalk',
127  'delete',
128  'deletechangetags',
129  'deletedhistory',
130  'deletedtext',
131  'deletelogentry',
132  'deleterevision',
133  'edit',
134  'editcontentmodel',
135  'editinterface',
136  'editprotected',
137  'editmyoptions',
138  'editmyprivateinfo',
139  'editmyusercss',
140  'editmyuserjson',
141  'editmyuserjs',
142  'editmywatchlist',
143  'editsemiprotected',
144  'editsitecss',
145  'editsitejson',
146  'editsitejs',
147  'editusercss',
148  'edituserjson',
149  'edituserjs',
150  'hideuser',
151  'import',
152  'importupload',
153  'ipblock-exempt',
154  'managechangetags',
155  'markbotedits',
156  'mergehistory',
157  'minoredit',
158  'move',
159  'movefile',
160  'move-categorypages',
161  'move-rootuserpages',
162  'move-subpages',
163  'nominornewtalk',
164  'noratelimit',
165  'override-export-depth',
166  'pagelang',
167  'patrol',
168  'patrolmarks',
169  'protect',
170  'purge',
171  'read',
172  'reupload',
173  'reupload-own',
174  'reupload-shared',
175  'rollback',
176  'sendemail',
177  'siteadmin',
178  'suppressionlog',
179  'suppressredirect',
180  'suppressrevision',
181  'unblockself',
182  'undelete',
183  'unwatchedpages',
184  'upload',
185  'upload_by_url',
186  'userrights',
187  'userrights-interwiki',
188  'viewmyprivateinfo',
189  'viewmywatchlist',
190  'viewsuppressed',
191  'writeapi',
192  ];
193 
197  protected static $mAllRights = false;
198 
200  // @{
202  public $mId;
204  public $mName;
206  protected $mActorId;
208  public $mRealName;
209 
211  public $mEmail;
213  public $mTouched;
215  protected $mQuickTouched;
217  protected $mToken;
221  protected $mEmailToken;
225  protected $mRegistration;
227  protected $mEditCount;
231  protected $mOptionOverrides;
232  // @}
233 
237  // @{
239 
243  protected $mLoadedItems = [];
244  // @}
245 
256  public $mFrom;
257 
261  protected $mNewtalk;
263  protected $mDatePreference;
265  public $mBlockedby;
267  protected $mHash;
269  public $mRights;
271  protected $mBlockreason;
273  protected $mEffectiveGroups;
275  protected $mImplicitGroups;
277  protected $mFormerGroups;
279  protected $mGlobalBlock;
281  protected $mLocked;
283  public $mHideName;
285  public $mOptions;
286 
288  private $mRequest;
289 
291  public $mBlock;
292 
294  protected $mAllowUsertalk;
295 
297  private $mBlockedFromCreateAccount = false;
298 
300  protected $queryFlagsUsed = self::READ_NORMAL;
301 
302  public static $idCacheByName = [];
303 
315  public function __construct() {
316  $this->clearInstanceCache( 'defaults' );
317  }
318 
322  public function __toString() {
323  return (string)$this->getName();
324  }
325 
340  public function isSafeToLoad() {
341  global $wgFullyInitialised;
342 
343  // The user is safe to load if:
344  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
345  // * mLoadedItems === true (already loaded)
346  // * mFrom !== 'session' (sessions not involved at all)
347 
348  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
349  $this->mLoadedItems === true || $this->mFrom !== 'session';
350  }
351 
357  public function load( $flags = self::READ_NORMAL ) {
358  global $wgFullyInitialised;
359 
360  if ( $this->mLoadedItems === true ) {
361  return;
362  }
363 
364  // Set it now to avoid infinite recursion in accessors
365  $oldLoadedItems = $this->mLoadedItems;
366  $this->mLoadedItems = true;
367  $this->queryFlagsUsed = $flags;
368 
369  // If this is called too early, things are likely to break.
370  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
372  ->warning( 'User::loadFromSession called before the end of Setup.php', [
373  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
374  ] );
375  $this->loadDefaults();
376  $this->mLoadedItems = $oldLoadedItems;
377  return;
378  }
379 
380  switch ( $this->mFrom ) {
381  case 'defaults':
382  $this->loadDefaults();
383  break;
384  case 'name':
385  // Make sure this thread sees its own changes
386  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
387  if ( $lb->hasOrMadeRecentMasterChanges() ) {
388  $flags |= self::READ_LATEST;
389  $this->queryFlagsUsed = $flags;
390  }
391 
392  $this->mId = self::idFromName( $this->mName, $flags );
393  if ( !$this->mId ) {
394  // Nonexistent user placeholder object
395  $this->loadDefaults( $this->mName );
396  } else {
397  $this->loadFromId( $flags );
398  }
399  break;
400  case 'id':
401  // Make sure this thread sees its own changes, if the ID isn't 0
402  if ( $this->mId != 0 ) {
403  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
404  if ( $lb->hasOrMadeRecentMasterChanges() ) {
405  $flags |= self::READ_LATEST;
406  $this->queryFlagsUsed = $flags;
407  }
408  }
409 
410  $this->loadFromId( $flags );
411  break;
412  case 'actor':
413  // Make sure this thread sees its own changes
414  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
415  if ( $lb->hasOrMadeRecentMasterChanges() ) {
416  $flags |= self::READ_LATEST;
417  $this->queryFlagsUsed = $flags;
418  }
419 
420  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
421  $row = wfGetDB( $index )->selectRow(
422  'actor',
423  [ 'actor_user', 'actor_name' ],
424  [ 'actor_id' => $this->mActorId ],
425  __METHOD__,
426  $options
427  );
428 
429  if ( !$row ) {
430  // Ugh.
431  $this->loadDefaults();
432  } elseif ( $row->actor_user ) {
433  $this->mId = $row->actor_user;
434  $this->loadFromId( $flags );
435  } else {
436  $this->loadDefaults( $row->actor_name );
437  }
438  break;
439  case 'session':
440  if ( !$this->loadFromSession() ) {
441  // Loading from session failed. Load defaults.
442  $this->loadDefaults();
443  }
444  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
445  break;
446  default:
447  throw new UnexpectedValueException(
448  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
449  }
450  }
451 
457  public function loadFromId( $flags = self::READ_NORMAL ) {
458  if ( $this->mId == 0 ) {
459  // Anonymous users are not in the database (don't need cache)
460  $this->loadDefaults();
461  return false;
462  }
463 
464  // Try cache (unless this needs data from the master DB).
465  // NOTE: if this thread called saveSettings(), the cache was cleared.
466  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
467  if ( $latest ) {
468  if ( !$this->loadFromDatabase( $flags ) ) {
469  // Can't load from ID
470  return false;
471  }
472  } else {
473  $this->loadFromCache();
474  }
475 
476  $this->mLoadedItems = true;
477  $this->queryFlagsUsed = $flags;
478 
479  return true;
480  }
481 
487  public static function purge( $wikiId, $userId ) {
488  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
489  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
490  $cache->delete( $key );
491  }
492 
498  protected function getCacheKey( WANObjectCache $cache ) {
499  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
500  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
501 
502  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
503  }
504 
511  $id = $this->getId();
512 
513  return $id ? [ $this->getCacheKey( $cache ) ] : [];
514  }
515 
522  protected function loadFromCache() {
524  $data = $cache->getWithSetCallback(
525  $this->getCacheKey( $cache ),
526  $cache::TTL_HOUR,
527  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
528  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
529  wfDebug( "User: cache miss for user {$this->mId}\n" );
530 
531  $this->loadFromDatabase( self::READ_NORMAL );
532  $this->loadGroups();
533  $this->loadOptions();
534 
535  $data = [];
536  foreach ( self::$mCacheVars as $name ) {
537  $data[$name] = $this->$name;
538  }
539 
540  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
541 
542  // if a user group membership is about to expire, the cache needs to
543  // expire at that time (T163691)
544  foreach ( $this->mGroupMemberships as $ugm ) {
545  if ( $ugm->getExpiry() ) {
546  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
547  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
548  $ttl = $secondsUntilExpiry;
549  }
550  }
551  }
552 
553  return $data;
554  },
555  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
556  );
557 
558  // Restore from cache
559  foreach ( self::$mCacheVars as $name ) {
560  $this->$name = $data[$name];
561  }
562 
563  return true;
564  }
565 
567  // @{
568 
585  public static function newFromName( $name, $validate = 'valid' ) {
586  if ( $validate === true ) {
587  $validate = 'valid';
588  }
589  $name = self::getCanonicalName( $name, $validate );
590  if ( $name === false ) {
591  return false;
592  } else {
593  // Create unloaded user object
594  $u = new User;
595  $u->mName = $name;
596  $u->mFrom = 'name';
597  $u->setItemLoaded( 'name' );
598  return $u;
599  }
600  }
601 
608  public static function newFromId( $id ) {
609  $u = new User;
610  $u->mId = $id;
611  $u->mFrom = 'id';
612  $u->setItemLoaded( 'id' );
613  return $u;
614  }
615 
623  public static function newFromActorId( $id ) {
625 
626  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
627  // but it does little harm and might be needed for write callers loading a User.
628  if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) ) {
629  throw new BadMethodCallException(
630  'Cannot use ' . __METHOD__
631  . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
632  );
633  }
634 
635  $u = new User;
636  $u->mActorId = $id;
637  $u->mFrom = 'actor';
638  $u->setItemLoaded( 'actor' );
639  return $u;
640  }
641 
651  public static function newFromIdentity( UserIdentity $identity ) {
652  if ( $identity instanceof User ) {
653  return $identity;
654  }
655 
656  return self::newFromAnyId(
657  $identity->getId() === 0 ? null : $identity->getId(),
658  $identity->getName() === '' ? null : $identity->getName(),
659  $identity->getActorId() === 0 ? null : $identity->getActorId()
660  );
661  }
662 
675  public static function newFromAnyId( $userId, $userName, $actorId ) {
677 
678  $user = new User;
679  $user->mFrom = 'defaults';
680 
681  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
682  // but it does little harm and might be needed for write callers loading a User.
683  if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
684  $user->mActorId = (int)$actorId;
685  if ( $user->mActorId !== 0 ) {
686  $user->mFrom = 'actor';
687  }
688  $user->setItemLoaded( 'actor' );
689  }
690 
691  if ( $userName !== null && $userName !== '' ) {
692  $user->mName = $userName;
693  $user->mFrom = 'name';
694  $user->setItemLoaded( 'name' );
695  }
696 
697  if ( $userId !== null ) {
698  $user->mId = (int)$userId;
699  if ( $user->mId !== 0 ) {
700  $user->mFrom = 'id';
701  }
702  $user->setItemLoaded( 'id' );
703  }
704 
705  if ( $user->mFrom === 'defaults' ) {
706  throw new InvalidArgumentException(
707  'Cannot create a user with no name, no ID, and no actor ID'
708  );
709  }
710 
711  return $user;
712  }
713 
725  public static function newFromConfirmationCode( $code, $flags = 0 ) {
726  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
727  ? wfGetDB( DB_MASTER )
728  : wfGetDB( DB_REPLICA );
729 
730  $id = $db->selectField(
731  'user',
732  'user_id',
733  [
734  'user_email_token' => md5( $code ),
735  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
736  ]
737  );
738 
739  return $id ? self::newFromId( $id ) : null;
740  }
741 
749  public static function newFromSession( WebRequest $request = null ) {
750  $user = new User;
751  $user->mFrom = 'session';
752  $user->mRequest = $request;
753  return $user;
754  }
755 
771  public static function newFromRow( $row, $data = null ) {
772  $user = new User;
773  $user->loadFromRow( $row, $data );
774  return $user;
775  }
776 
812  public static function newSystemUser( $name, $options = [] ) {
813  $options += [
814  'validate' => 'valid',
815  'create' => true,
816  'steal' => false,
817  ];
818 
819  $name = self::getCanonicalName( $name, $options['validate'] );
820  if ( $name === false ) {
821  return null;
822  }
823 
824  $dbr = wfGetDB( DB_REPLICA );
825  $userQuery = self::getQueryInfo();
826  $row = $dbr->selectRow(
827  $userQuery['tables'],
828  $userQuery['fields'],
829  [ 'user_name' => $name ],
830  __METHOD__,
831  [],
832  $userQuery['joins']
833  );
834  if ( !$row ) {
835  // Try the master database...
836  $dbw = wfGetDB( DB_MASTER );
837  $row = $dbw->selectRow(
838  $userQuery['tables'],
839  $userQuery['fields'],
840  [ 'user_name' => $name ],
841  __METHOD__,
842  [],
843  $userQuery['joins']
844  );
845  }
846 
847  if ( !$row ) {
848  // No user. Create it?
849  return $options['create']
850  ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
851  : null;
852  }
853 
854  $user = self::newFromRow( $row );
855 
856  // A user is considered to exist as a non-system user if it can
857  // authenticate, or has an email set, or has a non-invalid token.
858  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
859  AuthManager::singleton()->userCanAuthenticate( $name )
860  ) {
861  // User exists. Steal it?
862  if ( !$options['steal'] ) {
863  return null;
864  }
865 
866  AuthManager::singleton()->revokeAccessForUser( $name );
867 
868  $user->invalidateEmail();
869  $user->mToken = self::INVALID_TOKEN;
870  $user->saveSettings();
871  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
872  }
873 
874  return $user;
875  }
876 
877  // @}
878 
884  public static function whoIs( $id ) {
885  return UserCache::singleton()->getProp( $id, 'name' );
886  }
887 
894  public static function whoIsReal( $id ) {
895  return UserCache::singleton()->getProp( $id, 'real_name' );
896  }
897 
904  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
905  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
906  $name = (string)$name;
908  if ( is_null( $nt ) ) {
909  // Illegal name
910  return null;
911  }
912 
913  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
914  return self::$idCacheByName[$name];
915  }
916 
917  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
918  $db = wfGetDB( $index );
919 
920  $s = $db->selectRow(
921  'user',
922  [ 'user_id' ],
923  [ 'user_name' => $nt->getText() ],
924  __METHOD__,
925  $options
926  );
927 
928  if ( $s === false ) {
929  $result = null;
930  } else {
931  $result = (int)$s->user_id;
932  }
933 
934  self::$idCacheByName[$name] = $result;
935 
936  if ( count( self::$idCacheByName ) > 1000 ) {
937  self::$idCacheByName = [];
938  }
939 
940  return $result;
941  }
942 
946  public static function resetIdByNameCache() {
947  self::$idCacheByName = [];
948  }
949 
966  public static function isIP( $name ) {
967  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
968  || IP::isIPv6( $name );
969  }
970 
977  public function isIPRange() {
978  return IP::isValidRange( $this->mName );
979  }
980 
992  public static function isValidUserName( $name ) {
993  global $wgMaxNameChars;
994 
995  if ( $name == ''
996  || self::isIP( $name )
997  || strpos( $name, '/' ) !== false
998  || strlen( $name ) > $wgMaxNameChars
999  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
1000  ) {
1001  return false;
1002  }
1003 
1004  // Ensure that the name can't be misresolved as a different title,
1005  // such as with extra namespace keys at the start.
1006  $parsed = Title::newFromText( $name );
1007  if ( is_null( $parsed )
1008  || $parsed->getNamespace()
1009  || strcmp( $name, $parsed->getPrefixedText() ) ) {
1010  return false;
1011  }
1012 
1013  // Check an additional blacklist of troublemaker characters.
1014  // Should these be merged into the title char list?
1015  $unicodeBlacklist = '/[' .
1016  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
1017  '\x{00a0}' . # non-breaking space
1018  '\x{2000}-\x{200f}' . # various whitespace
1019  '\x{2028}-\x{202f}' . # breaks and control chars
1020  '\x{3000}' . # ideographic space
1021  '\x{e000}-\x{f8ff}' . # private use
1022  ']/u';
1023  if ( preg_match( $unicodeBlacklist, $name ) ) {
1024  return false;
1025  }
1026 
1027  return true;
1028  }
1029 
1041  public static function isUsableName( $name ) {
1042  global $wgReservedUsernames;
1043  // Must be a valid username, obviously ;)
1044  if ( !self::isValidUserName( $name ) ) {
1045  return false;
1046  }
1047 
1048  static $reservedUsernames = false;
1049  if ( !$reservedUsernames ) {
1050  $reservedUsernames = $wgReservedUsernames;
1051  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
1052  }
1053 
1054  // Certain names may be reserved for batch processes.
1055  foreach ( $reservedUsernames as $reserved ) {
1056  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1057  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1058  }
1059  if ( $reserved == $name ) {
1060  return false;
1061  }
1062  }
1063  return true;
1064  }
1065 
1076  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1077  if ( $groups === [] ) {
1078  return UserArrayFromResult::newFromIDs( [] );
1079  }
1080 
1081  $groups = array_unique( (array)$groups );
1082  $limit = min( 5000, $limit );
1083 
1084  $conds = [ 'ug_group' => $groups ];
1085  if ( $after !== null ) {
1086  $conds[] = 'ug_user > ' . (int)$after;
1087  }
1088 
1089  $dbr = wfGetDB( DB_REPLICA );
1090  $ids = $dbr->selectFieldValues(
1091  'user_groups',
1092  'ug_user',
1093  $conds,
1094  __METHOD__,
1095  [
1096  'DISTINCT' => true,
1097  'ORDER BY' => 'ug_user',
1098  'LIMIT' => $limit,
1099  ]
1100  ) ?: [];
1101  return UserArray::newFromIDs( $ids );
1102  }
1103 
1116  public static function isCreatableName( $name ) {
1118 
1119  // Ensure that the username isn't longer than 235 bytes, so that
1120  // (at least for the builtin skins) user javascript and css files
1121  // will work. (T25080)
1122  if ( strlen( $name ) > 235 ) {
1123  wfDebugLog( 'username', __METHOD__ .
1124  ": '$name' invalid due to length" );
1125  return false;
1126  }
1127 
1128  // Preg yells if you try to give it an empty string
1129  if ( $wgInvalidUsernameCharacters !== '' ) {
1130  if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
1131  wfDebugLog( 'username', __METHOD__ .
1132  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1133  return false;
1134  }
1135  }
1136 
1137  return self::isUsableName( $name );
1138  }
1139 
1146  public function isValidPassword( $password ) {
1147  // simple boolean wrapper for checkPasswordValidity
1148  return $this->checkPasswordValidity( $password )->isGood();
1149  }
1150 
1158  public function getPasswordValidity( $password ) {
1159  wfDeprecated( __METHOD__, '1.33' );
1160 
1161  $result = $this->checkPasswordValidity( $password );
1162  if ( $result->isGood() ) {
1163  return true;
1164  } else {
1165  $messages = [];
1166  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1167  $messages[] = $error['message'];
1168  }
1169  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1170  $messages[] = $warning['message'];
1171  }
1172  if ( count( $messages ) === 1 ) {
1173  return $messages[0];
1174  }
1175  return $messages;
1176  }
1177  }
1178 
1198  public function checkPasswordValidity( $password ) {
1199  global $wgPasswordPolicy;
1200 
1201  $upp = new UserPasswordPolicy(
1202  $wgPasswordPolicy['policies'],
1203  $wgPasswordPolicy['checks']
1204  );
1205 
1206  $status = Status::newGood( [] );
1207  $result = false; // init $result to false for the internal checks
1208 
1209  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1210  $status->error( $result );
1211  return $status;
1212  }
1213 
1214  if ( $result === false ) {
1215  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1216  return $status;
1217  } elseif ( $result === true ) {
1218  return $status;
1219  } else {
1220  $status->error( $result );
1221  return $status; // the isValidPassword hook set a string $result and returned true
1222  }
1223  }
1224 
1238  public static function getCanonicalName( $name, $validate = 'valid' ) {
1239  // Force usernames to capital
1240  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1241 
1242  # Reject names containing '#'; these will be cleaned up
1243  # with title normalisation, but then it's too late to
1244  # check elsewhere
1245  if ( strpos( $name, '#' ) !== false ) {
1246  return false;
1247  }
1248 
1249  // Clean up name according to title rules,
1250  // but only when validation is requested (T14654)
1251  $t = ( $validate !== false ) ?
1253  // Check for invalid titles
1254  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1255  return false;
1256  }
1257 
1258  // Reject various classes of invalid names
1259  $name = AuthManager::callLegacyAuthPlugin(
1260  'getCanonicalName', [ $t->getText() ], $t->getText()
1261  );
1262 
1263  switch ( $validate ) {
1264  case false:
1265  break;
1266  case 'valid':
1267  if ( !self::isValidUserName( $name ) ) {
1268  $name = false;
1269  }
1270  break;
1271  case 'usable':
1272  if ( !self::isUsableName( $name ) ) {
1273  $name = false;
1274  }
1275  break;
1276  case 'creatable':
1277  if ( !self::isCreatableName( $name ) ) {
1278  $name = false;
1279  }
1280  break;
1281  default:
1282  throw new InvalidArgumentException(
1283  'Invalid parameter value for $validate in ' . __METHOD__ );
1284  }
1285  return $name;
1286  }
1287 
1294  public static function randomPassword() {
1295  global $wgMinimalPasswordLength;
1296  return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1297  }
1298 
1307  public function loadDefaults( $name = false ) {
1308  $this->mId = 0;
1309  $this->mName = $name;
1310  $this->mActorId = null;
1311  $this->mRealName = '';
1312  $this->mEmail = '';
1313  $this->mOptionOverrides = null;
1314  $this->mOptionsLoaded = false;
1315 
1316  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1317  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1318  if ( $loggedOut !== 0 ) {
1319  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1320  } else {
1321  $this->mTouched = '1'; # Allow any pages to be cached
1322  }
1323 
1324  $this->mToken = null; // Don't run cryptographic functions till we need a token
1325  $this->mEmailAuthenticated = null;
1326  $this->mEmailToken = '';
1327  $this->mEmailTokenExpires = null;
1328  $this->mRegistration = wfTimestamp( TS_MW );
1329  $this->mGroupMemberships = [];
1330 
1331  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1332  }
1333 
1346  public function isItemLoaded( $item, $all = 'all' ) {
1347  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1348  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1349  }
1350 
1356  protected function setItemLoaded( $item ) {
1357  if ( is_array( $this->mLoadedItems ) ) {
1358  $this->mLoadedItems[$item] = true;
1359  }
1360  }
1361 
1367  private function loadFromSession() {
1368  // Deprecated hook
1369  $result = null;
1370  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1371  if ( $result !== null ) {
1372  return $result;
1373  }
1374 
1375  // MediaWiki\Session\Session already did the necessary authentication of the user
1376  // returned here, so just use it if applicable.
1377  $session = $this->getRequest()->getSession();
1378  $user = $session->getUser();
1379  if ( $user->isLoggedIn() ) {
1380  $this->loadFromUserObject( $user );
1381  if ( $user->isBlocked() ) {
1382  // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1383  // every session load, because an autoblocked editor might not edit again from the same
1384  // IP address after being blocked.
1385  $this->trackBlockWithCookie();
1386  }
1387 
1388  // Other code expects these to be set in the session, so set them.
1389  $session->set( 'wsUserID', $this->getId() );
1390  $session->set( 'wsUserName', $this->getName() );
1391  $session->set( 'wsToken', $this->getToken() );
1392 
1393  return true;
1394  }
1395 
1396  return false;
1397  }
1398 
1402  public function trackBlockWithCookie() {
1403  $block = $this->getBlock();
1404  if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null ) {
1405  $config = RequestContext::getMain()->getConfig();
1406  $shouldSetCookie = false;
1407 
1408  if ( $this->isAnon() && $config->get( 'CookieSetOnIpBlock' ) ) {
1409  // If user is logged-out, set a cookie to track the Block
1410  $shouldSetCookie = in_array( $block->getType(), [
1412  ] );
1413  if ( $shouldSetCookie ) {
1414  $block->setCookie( $this->getRequest()->response() );
1415 
1416  // temporary measure the use of cookies on ip blocks
1417  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1418  $stats->increment( 'block.ipblock.setCookie.success' );
1419  }
1420  } elseif ( $this->isLoggedIn() && $config->get( 'CookieSetOnAutoblock' ) ) {
1421  $shouldSetCookie = $block->getType() === Block::TYPE_USER && $block->isAutoblocking();
1422  if ( $shouldSetCookie ) {
1423  $block->setCookie( $this->getRequest()->response() );
1424  }
1425  }
1426  }
1427  }
1428 
1436  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1437  // Paranoia
1438  $this->mId = intval( $this->mId );
1439 
1440  if ( !$this->mId ) {
1441  // Anonymous users are not in the database
1442  $this->loadDefaults();
1443  return false;
1444  }
1445 
1446  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1447  $db = wfGetDB( $index );
1448 
1449  $userQuery = self::getQueryInfo();
1450  $s = $db->selectRow(
1451  $userQuery['tables'],
1452  $userQuery['fields'],
1453  [ 'user_id' => $this->mId ],
1454  __METHOD__,
1455  $options,
1456  $userQuery['joins']
1457  );
1458 
1459  $this->queryFlagsUsed = $flags;
1460  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1461 
1462  if ( $s !== false ) {
1463  // Initialise user table data
1464  $this->loadFromRow( $s );
1465  $this->mGroupMemberships = null; // deferred
1466  $this->getEditCount(); // revalidation for nulls
1467  return true;
1468  } else {
1469  // Invalid user_id
1470  $this->mId = 0;
1471  $this->loadDefaults();
1472  return false;
1473  }
1474  }
1475 
1488  protected function loadFromRow( $row, $data = null ) {
1490 
1491  if ( !is_object( $row ) ) {
1492  throw new InvalidArgumentException( '$row must be an object' );
1493  }
1494 
1495  $all = true;
1496 
1497  $this->mGroupMemberships = null; // deferred
1498 
1499  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
1500  // but it does little harm and might be needed for write callers loading a User.
1501  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
1502  if ( isset( $row->actor_id ) ) {
1503  $this->mActorId = (int)$row->actor_id;
1504  if ( $this->mActorId !== 0 ) {
1505  $this->mFrom = 'actor';
1506  }
1507  $this->setItemLoaded( 'actor' );
1508  } else {
1509  $all = false;
1510  }
1511  }
1512 
1513  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1514  $this->mName = $row->user_name;
1515  $this->mFrom = 'name';
1516  $this->setItemLoaded( 'name' );
1517  } else {
1518  $all = false;
1519  }
1520 
1521  if ( isset( $row->user_real_name ) ) {
1522  $this->mRealName = $row->user_real_name;
1523  $this->setItemLoaded( 'realname' );
1524  } else {
1525  $all = false;
1526  }
1527 
1528  if ( isset( $row->user_id ) ) {
1529  $this->mId = intval( $row->user_id );
1530  if ( $this->mId !== 0 ) {
1531  $this->mFrom = 'id';
1532  }
1533  $this->setItemLoaded( 'id' );
1534  } else {
1535  $all = false;
1536  }
1537 
1538  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1539  self::$idCacheByName[$row->user_name] = $row->user_id;
1540  }
1541 
1542  if ( isset( $row->user_editcount ) ) {
1543  $this->mEditCount = $row->user_editcount;
1544  } else {
1545  $all = false;
1546  }
1547 
1548  if ( isset( $row->user_touched ) ) {
1549  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1550  } else {
1551  $all = false;
1552  }
1553 
1554  if ( isset( $row->user_token ) ) {
1555  // The definition for the column is binary(32), so trim the NULs
1556  // that appends. The previous definition was char(32), so trim
1557  // spaces too.
1558  $this->mToken = rtrim( $row->user_token, " \0" );
1559  if ( $this->mToken === '' ) {
1560  $this->mToken = null;
1561  }
1562  } else {
1563  $all = false;
1564  }
1565 
1566  if ( isset( $row->user_email ) ) {
1567  $this->mEmail = $row->user_email;
1568  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1569  $this->mEmailToken = $row->user_email_token;
1570  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1571  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1572  } else {
1573  $all = false;
1574  }
1575 
1576  if ( $all ) {
1577  $this->mLoadedItems = true;
1578  }
1579 
1580  if ( is_array( $data ) ) {
1581  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1582  if ( $data['user_groups'] === [] ) {
1583  $this->mGroupMemberships = [];
1584  } else {
1585  $firstGroup = reset( $data['user_groups'] );
1586  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1587  $this->mGroupMemberships = [];
1588  foreach ( $data['user_groups'] as $row ) {
1589  $ugm = UserGroupMembership::newFromRow( (object)$row );
1590  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1591  }
1592  }
1593  }
1594  }
1595  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1596  $this->loadOptions( $data['user_properties'] );
1597  }
1598  }
1599  }
1600 
1606  protected function loadFromUserObject( $user ) {
1607  $user->load();
1608  foreach ( self::$mCacheVars as $var ) {
1609  $this->$var = $user->$var;
1610  }
1611  }
1612 
1616  private function loadGroups() {
1617  if ( is_null( $this->mGroupMemberships ) ) {
1618  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1619  ? wfGetDB( DB_MASTER )
1620  : wfGetDB( DB_REPLICA );
1621  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1622  $this->mId, $db );
1623  }
1624  }
1625 
1640  public function addAutopromoteOnceGroups( $event ) {
1642 
1643  if ( wfReadOnly() || !$this->getId() ) {
1644  return [];
1645  }
1646 
1647  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1648  if ( $toPromote === [] ) {
1649  return [];
1650  }
1651 
1652  if ( !$this->checkAndSetTouched() ) {
1653  return []; // raced out (bug T48834)
1654  }
1655 
1656  $oldGroups = $this->getGroups(); // previous groups
1657  $oldUGMs = $this->getGroupMemberships();
1658  foreach ( $toPromote as $group ) {
1659  $this->addGroup( $group );
1660  }
1661  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1662  $newUGMs = $this->getGroupMemberships();
1663 
1664  // update groups in external authentication database
1665  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1666  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1667 
1668  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1669  $logEntry->setPerformer( $this );
1670  $logEntry->setTarget( $this->getUserPage() );
1671  $logEntry->setParameters( [
1672  '4::oldgroups' => $oldGroups,
1673  '5::newgroups' => $newGroups,
1674  ] );
1675  $logid = $logEntry->insert();
1676  if ( $wgAutopromoteOnceLogInRC ) {
1677  $logEntry->publish( $logid );
1678  }
1679 
1680  return $toPromote;
1681  }
1682 
1692  protected function makeUpdateConditions( Database $db, array $conditions ) {
1693  if ( $this->mTouched ) {
1694  // CAS check: only update if the row wasn't changed sicne it was loaded.
1695  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1696  }
1697 
1698  return $conditions;
1699  }
1700 
1710  protected function checkAndSetTouched() {
1711  $this->load();
1712 
1713  if ( !$this->mId ) {
1714  return false; // anon
1715  }
1716 
1717  // Get a new user_touched that is higher than the old one
1718  $newTouched = $this->newTouchedTimestamp();
1719 
1720  $dbw = wfGetDB( DB_MASTER );
1721  $dbw->update( 'user',
1722  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1723  $this->makeUpdateConditions( $dbw, [
1724  'user_id' => $this->mId,
1725  ] ),
1726  __METHOD__
1727  );
1728  $success = ( $dbw->affectedRows() > 0 );
1729 
1730  if ( $success ) {
1731  $this->mTouched = $newTouched;
1732  $this->clearSharedCache();
1733  } else {
1734  // Clears on failure too since that is desired if the cache is stale
1735  $this->clearSharedCache( 'refresh' );
1736  }
1737 
1738  return $success;
1739  }
1740 
1748  public function clearInstanceCache( $reloadFrom = false ) {
1749  $this->mNewtalk = -1;
1750  $this->mDatePreference = null;
1751  $this->mBlockedby = -1; # Unset
1752  $this->mHash = false;
1753  $this->mRights = null;
1754  $this->mEffectiveGroups = null;
1755  $this->mImplicitGroups = null;
1756  $this->mGroupMemberships = null;
1757  $this->mOptions = null;
1758  $this->mOptionsLoaded = false;
1759  $this->mEditCount = null;
1760 
1761  if ( $reloadFrom ) {
1762  $this->mLoadedItems = [];
1763  $this->mFrom = $reloadFrom;
1764  }
1765  }
1766 
1773  public static function getDefaultOptions() {
1775 
1776  static $defOpt = null;
1777  static $defOptLang = null;
1778 
1779  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1780  if ( $defOpt !== null && $defOptLang === $contLang->getCode() ) {
1781  // The content language does not change (and should not change) mid-request, but the
1782  // unit tests change it anyway, and expect this method to return values relevant to the
1783  // current content language.
1784  return $defOpt;
1785  }
1786 
1787  $defOpt = $wgDefaultUserOptions;
1788  // Default language setting
1789  $defOptLang = $contLang->getCode();
1790  $defOpt['language'] = $defOptLang;
1791  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1792  if ( $langCode === $contLang->getCode() ) {
1793  $defOpt['variant'] = $langCode;
1794  } else {
1795  $defOpt["variant-$langCode"] = $langCode;
1796  }
1797  }
1798 
1799  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1800  // since extensions may change the set of searchable namespaces depending
1801  // on user groups/permissions.
1802  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1803  $defOpt['searchNs' . $nsnum] = (bool)$val;
1804  }
1805  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1806 
1807  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1808 
1809  return $defOpt;
1810  }
1811 
1818  public static function getDefaultOption( $opt ) {
1819  $defOpts = self::getDefaultOptions();
1820  return $defOpts[$opt] ?? null;
1821  }
1822 
1829  private function getBlockedStatus( $bFromReplica = true ) {
1831 
1832  if ( $this->mBlockedby != -1 ) {
1833  return;
1834  }
1835 
1836  wfDebug( __METHOD__ . ": checking...\n" );
1837 
1838  // Initialize data...
1839  // Otherwise something ends up stomping on $this->mBlockedby when
1840  // things get lazy-loaded later, causing false positive block hits
1841  // due to -1 !== 0. Probably session-related... Nothing should be
1842  // overwriting mBlockedby, surely?
1843  $this->load();
1844 
1845  # We only need to worry about passing the IP address to the Block generator if the
1846  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1847  # know which IP address they're actually coming from
1848  $ip = null;
1849  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1850  // $wgUser->getName() only works after the end of Setup.php. Until
1851  // then, assume it's a logged-out user.
1852  $globalUserName = $wgUser->isSafeToLoad()
1853  ? $wgUser->getName()
1854  : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1855  if ( $this->getName() === $globalUserName ) {
1856  $ip = $this->getRequest()->getIP();
1857  }
1858  }
1859 
1860  // User/IP blocking
1861  $block = Block::newFromTarget( $this, $ip, !$bFromReplica );
1862 
1863  // Cookie blocking
1864  if ( !$block instanceof Block ) {
1865  $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
1866  }
1867 
1868  // Proxy blocking
1869  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1870  // Local list
1871  if ( self::isLocallyBlockedProxy( $ip ) ) {
1872  $block = new Block( [
1873  'byText' => wfMessage( 'proxyblocker' )->text(),
1874  'reason' => wfMessage( 'proxyblockreason' )->plain(),
1875  'address' => $ip,
1876  'systemBlock' => 'proxy',
1877  ] );
1878  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1879  $block = new Block( [
1880  'byText' => wfMessage( 'sorbs' )->text(),
1881  'reason' => wfMessage( 'sorbsreason' )->plain(),
1882  'address' => $ip,
1883  'systemBlock' => 'dnsbl',
1884  ] );
1885  }
1886  }
1887 
1888  // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
1889  if ( !$block instanceof Block
1890  && $wgApplyIpBlocksToXff
1891  && $ip !== null
1892  && !in_array( $ip, $wgProxyWhitelist )
1893  ) {
1894  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1895  $xff = array_map( 'trim', explode( ',', $xff ) );
1896  $xff = array_diff( $xff, [ $ip ] );
1897  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromReplica );
1898  $block = Block::chooseBlock( $xffblocks, $xff );
1899  if ( $block instanceof Block ) {
1900  # Mangle the reason to alert the user that the block
1901  # originated from matching the X-Forwarded-For header.
1902  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->plain();
1903  }
1904  }
1905 
1906  if ( !$block instanceof Block
1907  && $ip !== null
1908  && $this->isAnon()
1909  && IP::isInRanges( $ip, $wgSoftBlockRanges )
1910  ) {
1911  $block = new Block( [
1912  'address' => $ip,
1913  'byText' => 'MediaWiki default',
1914  'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
1915  'anonOnly' => true,
1916  'systemBlock' => 'wgSoftBlockRanges',
1917  ] );
1918  }
1919 
1920  if ( $block instanceof Block ) {
1921  wfDebug( __METHOD__ . ": Found block.\n" );
1922  $this->mBlock = $block;
1923  $this->mBlockedby = $block->getByName();
1924  $this->mBlockreason = $block->mReason;
1925  $this->mHideName = $block->mHideName;
1926  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1927  } else {
1928  $this->mBlock = null;
1929  $this->mBlockedby = '';
1930  $this->mBlockreason = '';
1931  $this->mHideName = 0;
1932  $this->mAllowUsertalk = false;
1933  }
1934 
1935  // Avoid PHP 7.1 warning of passing $this by reference
1936  $user = $this;
1937  // Extensions
1938  Hooks::run( 'GetBlockedStatus', [ &$user ] );
1939  }
1940 
1946  protected function getBlockFromCookieValue( $blockCookieVal ) {
1947  // Make sure there's something to check. The cookie value must start with a number.
1948  if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
1949  return false;
1950  }
1951  // Load the Block from the ID in the cookie.
1952  $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
1953  if ( $blockCookieId !== null ) {
1954  // An ID was found in the cookie.
1955  $tmpBlock = Block::newFromID( $blockCookieId );
1956  if ( $tmpBlock instanceof Block ) {
1957  $config = RequestContext::getMain()->getConfig();
1958 
1959  switch ( $tmpBlock->getType() ) {
1960  case Block::TYPE_USER:
1961  $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
1962  $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
1963  break;
1964  case Block::TYPE_IP:
1965  case Block::TYPE_RANGE:
1966  // If block is type IP or IP range, load only if user is not logged in (T152462)
1967  $blockIsValid = !$tmpBlock->isExpired() && !$this->isLoggedIn();
1968  $useBlockCookie = ( $config->get( 'CookieSetOnIpBlock' ) === true );
1969  break;
1970  default:
1971  $blockIsValid = false;
1972  $useBlockCookie = false;
1973  }
1974 
1975  if ( $blockIsValid && $useBlockCookie ) {
1976  // Use the block.
1977  return $tmpBlock;
1978  } else {
1979  // If the block is not valid, remove the cookie.
1980  Block::clearCookie( $this->getRequest()->response() );
1981  }
1982  } else {
1983  // If the block doesn't exist, remove the cookie.
1984  Block::clearCookie( $this->getRequest()->response() );
1985  }
1986  }
1987  return false;
1988  }
1989 
1997  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1999 
2000  if ( !$wgEnableDnsBlacklist ) {
2001  return false;
2002  }
2003 
2004  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
2005  return false;
2006  }
2007 
2008  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
2009  }
2010 
2018  public function inDnsBlacklist( $ip, $bases ) {
2019  $found = false;
2020  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
2021  if ( IP::isIPv4( $ip ) ) {
2022  // Reverse IP, T23255
2023  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
2024 
2025  foreach ( (array)$bases as $base ) {
2026  // Make hostname
2027  // If we have an access key, use that too (ProjectHoneypot, etc.)
2028  $basename = $base;
2029  if ( is_array( $base ) ) {
2030  if ( count( $base ) >= 2 ) {
2031  // Access key is 1, base URL is 0
2032  $host = "{$base[1]}.$ipReversed.{$base[0]}";
2033  } else {
2034  $host = "$ipReversed.{$base[0]}";
2035  }
2036  $basename = $base[0];
2037  } else {
2038  $host = "$ipReversed.$base";
2039  }
2040 
2041  // Send query
2042  $ipList = gethostbynamel( $host );
2043 
2044  if ( $ipList ) {
2045  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
2046  $found = true;
2047  break;
2048  } else {
2049  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
2050  }
2051  }
2052  }
2053 
2054  return $found;
2055  }
2056 
2064  public static function isLocallyBlockedProxy( $ip ) {
2065  global $wgProxyList;
2066 
2067  if ( !$wgProxyList ) {
2068  return false;
2069  }
2070 
2071  if ( !is_array( $wgProxyList ) ) {
2072  // Load values from the specified file
2073  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
2074  }
2075 
2076  $resultProxyList = [];
2077  $deprecatedIPEntries = [];
2078 
2079  // backward compatibility: move all ip addresses in keys to values
2080  foreach ( $wgProxyList as $key => $value ) {
2081  $keyIsIP = IP::isIPAddress( $key );
2082  $valueIsIP = IP::isIPAddress( $value );
2083  if ( $keyIsIP && !$valueIsIP ) {
2084  $deprecatedIPEntries[] = $key;
2085  $resultProxyList[] = $key;
2086  } elseif ( $keyIsIP && $valueIsIP ) {
2087  $deprecatedIPEntries[] = $key;
2088  $resultProxyList[] = $key;
2089  $resultProxyList[] = $value;
2090  } else {
2091  $resultProxyList[] = $value;
2092  }
2093  }
2094 
2095  if ( $deprecatedIPEntries ) {
2096  wfDeprecated(
2097  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
2098  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
2099  }
2100 
2101  $proxyListIPSet = new IPSet( $resultProxyList );
2102  return $proxyListIPSet->match( $ip );
2103  }
2104 
2110  public function isPingLimitable() {
2111  global $wgRateLimitsExcludedIPs;
2112  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
2113  // No other good way currently to disable rate limits
2114  // for specific IPs. :P
2115  // But this is a crappy hack and should die.
2116  return false;
2117  }
2118  return !$this->isAllowed( 'noratelimit' );
2119  }
2120 
2135  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
2136  // Avoid PHP 7.1 warning of passing $this by reference
2137  $user = $this;
2138  // Call the 'PingLimiter' hook
2139  $result = false;
2140  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
2141  return $result;
2142  }
2143 
2144  global $wgRateLimits;
2145  if ( !isset( $wgRateLimits[$action] ) ) {
2146  return false;
2147  }
2148 
2149  $limits = array_merge(
2150  [ '&can-bypass' => true ],
2151  $wgRateLimits[$action]
2152  );
2153 
2154  // Some groups shouldn't trigger the ping limiter, ever
2155  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
2156  return false;
2157  }
2158 
2159  $keys = [];
2160  $id = $this->getId();
2161  $userLimit = false;
2162  $isNewbie = $this->isNewbie();
2164 
2165  if ( $id == 0 ) {
2166  // limits for anons
2167  if ( isset( $limits['anon'] ) ) {
2168  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
2169  }
2170  } else {
2171  // limits for logged-in users
2172  if ( isset( $limits['user'] ) ) {
2173  $userLimit = $limits['user'];
2174  }
2175  }
2176 
2177  // limits for anons and for newbie logged-in users
2178  if ( $isNewbie ) {
2179  // ip-based limits
2180  if ( isset( $limits['ip'] ) ) {
2181  $ip = $this->getRequest()->getIP();
2182  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
2183  }
2184  // subnet-based limits
2185  if ( isset( $limits['subnet'] ) ) {
2186  $ip = $this->getRequest()->getIP();
2187  $subnet = IP::getSubnet( $ip );
2188  if ( $subnet !== false ) {
2189  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
2190  }
2191  }
2192  }
2193 
2194  // Check for group-specific permissions
2195  // If more than one group applies, use the group with the highest limit ratio (max/period)
2196  foreach ( $this->getGroups() as $group ) {
2197  if ( isset( $limits[$group] ) ) {
2198  if ( $userLimit === false
2199  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2200  ) {
2201  $userLimit = $limits[$group];
2202  }
2203  }
2204  }
2205 
2206  // limits for newbie logged-in users (override all the normal user limits)
2207  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2208  $userLimit = $limits['newbie'];
2209  }
2210 
2211  // Set the user limit key
2212  if ( $userLimit !== false ) {
2213  list( $max, $period ) = $userLimit;
2214  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2215  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2216  }
2217 
2218  // ip-based limits for all ping-limitable users
2219  if ( isset( $limits['ip-all'] ) ) {
2220  $ip = $this->getRequest()->getIP();
2221  // ignore if user limit is more permissive
2222  if ( $isNewbie || $userLimit === false
2223  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2224  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2225  }
2226  }
2227 
2228  // subnet-based limits for all ping-limitable users
2229  if ( isset( $limits['subnet-all'] ) ) {
2230  $ip = $this->getRequest()->getIP();
2231  $subnet = IP::getSubnet( $ip );
2232  if ( $subnet !== false ) {
2233  // ignore if user limit is more permissive
2234  if ( $isNewbie || $userLimit === false
2235  || $limits['ip-all'][0] / $limits['ip-all'][1]
2236  > $userLimit[0] / $userLimit[1] ) {
2237  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2238  }
2239  }
2240  }
2241 
2242  $triggered = false;
2243  foreach ( $keys as $key => $limit ) {
2244  list( $max, $period ) = $limit;
2245  $summary = "(limit $max in {$period}s)";
2246  $count = $cache->get( $key );
2247  // Already pinged?
2248  if ( $count ) {
2249  if ( $count >= $max ) {
2250  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2251  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2252  $triggered = true;
2253  } else {
2254  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2255  }
2256  } else {
2257  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2258  if ( $incrBy > 0 ) {
2259  $cache->add( $key, 0, intval( $period ) ); // first ping
2260  }
2261  }
2262  if ( $incrBy > 0 ) {
2263  $cache->incr( $key, $incrBy );
2264  }
2265  }
2266 
2267  return $triggered;
2268  }
2269 
2277  public function isBlocked( $bFromReplica = true ) {
2278  return $this->getBlock( $bFromReplica ) instanceof Block && $this->getBlock()->prevents( 'edit' );
2279  }
2280 
2287  public function getBlock( $bFromReplica = true ) {
2288  $this->getBlockedStatus( $bFromReplica );
2289  return $this->mBlock instanceof Block ? $this->mBlock : null;
2290  }
2291 
2299  public function isBlockedFrom( $title, $fromReplica = false ) {
2300  $blocked = $this->isHidden();
2301 
2302  if ( !$blocked ) {
2303  $block = $this->getBlock( $fromReplica );
2304  if ( $block ) {
2305  // Special handling for a user's own talk page. The block is not aware
2306  // of the user, so this must be done here.
2307  if ( $title->equals( $this->getTalkPage() ) ) {
2308  if ( $block->isSitewide() ) {
2309  // If the block is sitewide, whatever is set is what is honored.
2310  // This must be checked here, because Block::appliesToPage will
2311  // return true for a sitewide block.
2312  $blocked = $block->prevents( 'editownusertalk' );
2313  } else {
2314  // The page restrictions always take precedence over the namespace
2315  // restrictions. If the user is explicity blocked from their own
2316  // talk page, nothing can change that.
2317  $blocked = $block->appliesToPage( $title->getArticleID() );
2318 
2319  // If the block applies to the user talk namespace, then whatever is
2320  // set is what is honored.
2321  if ( !$blocked && $block->appliesToNamespace( NS_USER_TALK ) ) {
2322  $blocked = $block->prevents( 'editownusertalk' );
2323  }
2324 
2325  // If another type of restriction is added, it should be checked
2326  // here.
2327  }
2328  } else {
2329  $blocked = $block->appliesToTitle( $title );
2330  }
2331  }
2332  }
2333 
2334  // only for the purpose of the hook. We really don't need this here.
2335  $allowUsertalk = $this->mAllowUsertalk;
2336 
2337  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
2338 
2339  return $blocked;
2340  }
2341 
2346  public function blockedBy() {
2347  $this->getBlockedStatus();
2348  return $this->mBlockedby;
2349  }
2350 
2355  public function blockedFor() {
2356  $this->getBlockedStatus();
2357  return $this->mBlockreason;
2358  }
2359 
2364  public function getBlockId() {
2365  $this->getBlockedStatus();
2366  return ( $this->mBlock ? $this->mBlock->getId() : false );
2367  }
2368 
2377  public function isBlockedGlobally( $ip = '' ) {
2378  return $this->getGlobalBlock( $ip ) instanceof Block;
2379  }
2380 
2391  public function getGlobalBlock( $ip = '' ) {
2392  if ( $this->mGlobalBlock !== null ) {
2393  return $this->mGlobalBlock ?: null;
2394  }
2395  // User is already an IP?
2396  if ( IP::isIPAddress( $this->getName() ) ) {
2397  $ip = $this->getName();
2398  } elseif ( !$ip ) {
2399  $ip = $this->getRequest()->getIP();
2400  }
2401  // Avoid PHP 7.1 warning of passing $this by reference
2402  $user = $this;
2403  $blocked = false;
2404  $block = null;
2405  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2406 
2407  if ( $blocked && $block === null ) {
2408  // back-compat: UserIsBlockedGlobally didn't have $block param first
2409  $block = new Block( [
2410  'address' => $ip,
2411  'systemBlock' => 'global-block'
2412  ] );
2413  }
2414 
2415  $this->mGlobalBlock = $blocked ? $block : false;
2416  return $this->mGlobalBlock ?: null;
2417  }
2418 
2424  public function isLocked() {
2425  if ( $this->mLocked !== null ) {
2426  return $this->mLocked;
2427  }
2428  // Avoid PHP 7.1 warning of passing $this by reference
2429  $user = $this;
2430  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2431  $this->mLocked = $authUser && $authUser->isLocked();
2432  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2433  return $this->mLocked;
2434  }
2435 
2441  public function isHidden() {
2442  if ( $this->mHideName !== null ) {
2443  return (bool)$this->mHideName;
2444  }
2445  $this->getBlockedStatus();
2446  if ( !$this->mHideName ) {
2447  // Avoid PHP 7.1 warning of passing $this by reference
2448  $user = $this;
2449  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2450  $this->mHideName = $authUser && $authUser->isHidden();
2451  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2452  }
2453  return (bool)$this->mHideName;
2454  }
2455 
2460  public function getId() {
2461  if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2462  // Special case, we know the user is anonymous
2463  return 0;
2464  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2465  // Don't load if this was initialized from an ID
2466  $this->load();
2467  }
2468 
2469  return (int)$this->mId;
2470  }
2471 
2476  public function setId( $v ) {
2477  $this->mId = $v;
2478  $this->clearInstanceCache( 'id' );
2479  }
2480 
2485  public function getName() {
2486  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2487  // Special case optimisation
2488  return $this->mName;
2489  } else {
2490  $this->load();
2491  if ( $this->mName === false ) {
2492  // Clean up IPs
2493  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2494  }
2495  return $this->mName;
2496  }
2497  }
2498 
2512  public function setName( $str ) {
2513  $this->load();
2514  $this->mName = $str;
2515  }
2516 
2523  public function getActorId( IDatabase $dbw = null ) {
2525 
2526  // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
2527  // but it does little harm and might be needed for write callers loading a User.
2528  if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
2529  return 0;
2530  }
2531 
2532  if ( !$this->isItemLoaded( 'actor' ) ) {
2533  $this->load();
2534  }
2535 
2536  // Currently $this->mActorId might be null if $this was loaded from a
2537  // cache entry that was written when $wgActorTableSchemaMigrationStage
2538  // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
2539  // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
2540  // has been removed), that condition may be removed.
2541  if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2542  $q = [
2543  'actor_user' => $this->getId() ?: null,
2544  'actor_name' => (string)$this->getName(),
2545  ];
2546  if ( $dbw ) {
2547  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2548  throw new CannotCreateActorException(
2549  'Cannot create an actor for a usable name that is not an existing user'
2550  );
2551  }
2552  if ( $q['actor_name'] === '' ) {
2553  throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2554  }
2555  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2556  if ( $dbw->affectedRows() ) {
2557  $this->mActorId = (int)$dbw->insertId();
2558  } else {
2559  // Outdated cache?
2560  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2561  $this->mActorId = (int)$dbw->selectField(
2562  'actor',
2563  'actor_id',
2564  $q,
2565  __METHOD__,
2566  [ 'LOCK IN SHARE MODE' ]
2567  );
2568  if ( !$this->mActorId ) {
2569  throw new CannotCreateActorException(
2570  "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2571  );
2572  }
2573  }
2574  $this->invalidateCache();
2575  } else {
2576  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2577  $db = wfGetDB( $index );
2578  $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2579  }
2580  $this->setItemLoaded( 'actor' );
2581  }
2582 
2583  return (int)$this->mActorId;
2584  }
2585 
2590  public function getTitleKey() {
2591  return str_replace( ' ', '_', $this->getName() );
2592  }
2593 
2598  public function getNewtalk() {
2599  $this->load();
2600 
2601  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2602  if ( $this->mNewtalk === -1 ) {
2603  $this->mNewtalk = false; # reset talk page status
2604 
2605  // Check memcached separately for anons, who have no
2606  // entire User object stored in there.
2607  if ( !$this->mId ) {
2608  global $wgDisableAnonTalk;
2609  if ( $wgDisableAnonTalk ) {
2610  // Anon newtalk disabled by configuration.
2611  $this->mNewtalk = false;
2612  } else {
2613  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2614  }
2615  } else {
2616  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2617  }
2618  }
2619 
2620  return (bool)$this->mNewtalk;
2621  }
2622 
2636  public function getNewMessageLinks() {
2637  // Avoid PHP 7.1 warning of passing $this by reference
2638  $user = $this;
2639  $talks = [];
2640  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2641  return $talks;
2642  } elseif ( !$this->getNewtalk() ) {
2643  return [];
2644  }
2645  $utp = $this->getTalkPage();
2646  $dbr = wfGetDB( DB_REPLICA );
2647  // Get the "last viewed rev" timestamp from the oldest message notification
2648  $timestamp = $dbr->selectField( 'user_newtalk',
2649  'MIN(user_last_timestamp)',
2650  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2651  __METHOD__ );
2652  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2653  return [
2654  [
2656  'link' => $utp->getLocalURL(),
2657  'rev' => $rev
2658  ]
2659  ];
2660  }
2661 
2667  public function getNewMessageRevisionId() {
2668  $newMessageRevisionId = null;
2669  $newMessageLinks = $this->getNewMessageLinks();
2670  if ( $newMessageLinks ) {
2671  // Note: getNewMessageLinks() never returns more than a single link
2672  // and it is always for the same wiki, but we double-check here in
2673  // case that changes some time in the future.
2674  if ( count( $newMessageLinks ) === 1
2675  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2676  && $newMessageLinks[0]['rev']
2677  ) {
2679  $newMessageRevision = $newMessageLinks[0]['rev'];
2680  $newMessageRevisionId = $newMessageRevision->getId();
2681  }
2682  }
2683  return $newMessageRevisionId;
2684  }
2685 
2694  protected function checkNewtalk( $field, $id ) {
2695  $dbr = wfGetDB( DB_REPLICA );
2696 
2697  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2698 
2699  return $ok !== false;
2700  }
2701 
2709  protected function updateNewtalk( $field, $id, $curRev = null ) {
2710  // Get timestamp of the talk page revision prior to the current one
2711  $prevRev = $curRev ? $curRev->getPrevious() : false;
2712  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2713  // Mark the user as having new messages since this revision
2714  $dbw = wfGetDB( DB_MASTER );
2715  $dbw->insert( 'user_newtalk',
2716  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2717  __METHOD__,
2718  'IGNORE' );
2719  if ( $dbw->affectedRows() ) {
2720  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2721  return true;
2722  } else {
2723  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2724  return false;
2725  }
2726  }
2727 
2734  protected function deleteNewtalk( $field, $id ) {
2735  $dbw = wfGetDB( DB_MASTER );
2736  $dbw->delete( 'user_newtalk',
2737  [ $field => $id ],
2738  __METHOD__ );
2739  if ( $dbw->affectedRows() ) {
2740  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2741  return true;
2742  } else {
2743  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2744  return false;
2745  }
2746  }
2747 
2754  public function setNewtalk( $val, $curRev = null ) {
2755  if ( wfReadOnly() ) {
2756  return;
2757  }
2758 
2759  $this->load();
2760  $this->mNewtalk = $val;
2761 
2762  if ( $this->isAnon() ) {
2763  $field = 'user_ip';
2764  $id = $this->getName();
2765  } else {
2766  $field = 'user_id';
2767  $id = $this->getId();
2768  }
2769 
2770  if ( $val ) {
2771  $changed = $this->updateNewtalk( $field, $id, $curRev );
2772  } else {
2773  $changed = $this->deleteNewtalk( $field, $id );
2774  }
2775 
2776  if ( $changed ) {
2777  $this->invalidateCache();
2778  }
2779  }
2780 
2786  private function newTouchedTimestamp() {
2787  global $wgClockSkewFudge;
2788 
2789  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2790  if ( $this->mTouched && $time <= $this->mTouched ) {
2791  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2792  }
2793 
2794  return $time;
2795  }
2796 
2807  public function clearSharedCache( $mode = 'changed' ) {
2808  if ( !$this->getId() ) {
2809  return;
2810  }
2811 
2813  $key = $this->getCacheKey( $cache );
2814  if ( $mode === 'refresh' ) {
2815  $cache->delete( $key, 1 );
2816  } else {
2817  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2818  if ( $lb->hasOrMadeRecentMasterChanges() ) {
2819  $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
2820  function () use ( $cache, $key ) {
2821  $cache->delete( $key );
2822  },
2823  __METHOD__
2824  );
2825  } else {
2826  $cache->delete( $key );
2827  }
2828  }
2829  }
2830 
2836  public function invalidateCache() {
2837  $this->touch();
2838  $this->clearSharedCache();
2839  }
2840 
2853  public function touch() {
2854  $id = $this->getId();
2855  if ( $id ) {
2856  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2857  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2858  $cache->touchCheckKey( $key );
2859  $this->mQuickTouched = null;
2860  }
2861  }
2862 
2868  public function validateCache( $timestamp ) {
2869  return ( $timestamp >= $this->getTouched() );
2870  }
2871 
2880  public function getTouched() {
2881  $this->load();
2882 
2883  if ( $this->mId ) {
2884  if ( $this->mQuickTouched === null ) {
2885  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2886  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2887 
2888  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2889  }
2890 
2891  return max( $this->mTouched, $this->mQuickTouched );
2892  }
2893 
2894  return $this->mTouched;
2895  }
2896 
2902  public function getDBTouched() {
2903  $this->load();
2904 
2905  return $this->mTouched;
2906  }
2907 
2924  public function setPassword( $str ) {
2925  wfDeprecated( __METHOD__, '1.27' );
2926  return $this->setPasswordInternal( $str );
2927  }
2928 
2937  public function setInternalPassword( $str ) {
2938  wfDeprecated( __METHOD__, '1.27' );
2939  $this->setPasswordInternal( $str );
2940  }
2941 
2950  private function setPasswordInternal( $str ) {
2951  $manager = AuthManager::singleton();
2952 
2953  // If the user doesn't exist yet, fail
2954  if ( !$manager->userExists( $this->getName() ) ) {
2955  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2956  }
2957 
2958  $status = $this->changeAuthenticationData( [
2959  'username' => $this->getName(),
2960  'password' => $str,
2961  'retype' => $str,
2962  ] );
2963  if ( !$status->isGood() ) {
2965  ->info( __METHOD__ . ': Password change rejected: '
2966  . $status->getWikiText( null, null, 'en' ) );
2967  return false;
2968  }
2969 
2970  $this->setOption( 'watchlisttoken', false );
2971  SessionManager::singleton()->invalidateSessionsForUser( $this );
2972 
2973  return true;
2974  }
2975 
2988  public function changeAuthenticationData( array $data ) {
2989  $manager = AuthManager::singleton();
2990  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2991  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2992 
2993  $status = Status::newGood( 'ignored' );
2994  foreach ( $reqs as $req ) {
2995  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2996  }
2997  if ( $status->getValue() === 'ignored' ) {
2998  $status->warning( 'authenticationdatachange-ignored' );
2999  }
3000 
3001  if ( $status->isGood() ) {
3002  foreach ( $reqs as $req ) {
3003  $manager->changeAuthenticationData( $req );
3004  }
3005  }
3006  return $status;
3007  }
3008 
3015  public function getToken( $forceCreation = true ) {
3017 
3018  $this->load();
3019  if ( !$this->mToken && $forceCreation ) {
3020  $this->setToken();
3021  }
3022 
3023  if ( !$this->mToken ) {
3024  // The user doesn't have a token, return null to indicate that.
3025  return null;
3026  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
3027  // We return a random value here so existing token checks are very
3028  // likely to fail.
3029  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
3030  } elseif ( $wgAuthenticationTokenVersion === null ) {
3031  // $wgAuthenticationTokenVersion not in use, so return the raw secret
3032  return $this->mToken;
3033  } else {
3034  // $wgAuthenticationTokenVersion in use, so hmac it.
3035  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
3036 
3037  // The raw hash can be overly long. Shorten it up.
3038  $len = max( 32, self::TOKEN_LENGTH );
3039  if ( strlen( $ret ) < $len ) {
3040  // Should never happen, even md5 is 128 bits
3041  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
3042  }
3043  return substr( $ret, -$len );
3044  }
3045  }
3046 
3053  public function setToken( $token = false ) {
3054  $this->load();
3055  if ( $this->mToken === self::INVALID_TOKEN ) {
3057  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
3058  } elseif ( !$token ) {
3059  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
3060  } else {
3061  $this->mToken = $token;
3062  }
3063  }
3064 
3073  public function setNewpassword( $str, $throttle = true ) {
3074  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
3075  }
3076 
3081  public function getEmail() {
3082  $this->load();
3083  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
3084  return $this->mEmail;
3085  }
3086 
3092  $this->load();
3093  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
3095  }
3096 
3101  public function setEmail( $str ) {
3102  $this->load();
3103  if ( $str == $this->mEmail ) {
3104  return;
3105  }
3106  $this->invalidateEmail();
3107  $this->mEmail = $str;
3108  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
3109  }
3110 
3118  public function setEmailWithConfirmation( $str ) {
3120 
3121  if ( !$wgEnableEmail ) {
3122  return Status::newFatal( 'emaildisabled' );
3123  }
3124 
3125  $oldaddr = $this->getEmail();
3126  if ( $str === $oldaddr ) {
3127  return Status::newGood( true );
3128  }
3129 
3130  $type = $oldaddr != '' ? 'changed' : 'set';
3131  $notificationResult = null;
3132 
3133  if ( $wgEmailAuthentication ) {
3134  // Send the user an email notifying the user of the change in registered
3135  // email address on their previous email address
3136  if ( $type == 'changed' ) {
3137  $change = $str != '' ? 'changed' : 'removed';
3138  $notificationResult = $this->sendMail(
3139  wfMessage( 'notificationemail_subject_' . $change )->text(),
3140  wfMessage( 'notificationemail_body_' . $change,
3141  $this->getRequest()->getIP(),
3142  $this->getName(),
3143  $str )->text()
3144  );
3145  }
3146  }
3147 
3148  $this->setEmail( $str );
3149 
3150  if ( $str !== '' && $wgEmailAuthentication ) {
3151  // Send a confirmation request to the new address if needed
3152  $result = $this->sendConfirmationMail( $type );
3153 
3154  if ( $notificationResult !== null ) {
3155  $result->merge( $notificationResult );
3156  }
3157 
3158  if ( $result->isGood() ) {
3159  // Say to the caller that a confirmation and notification mail has been sent
3160  $result->value = 'eauth';
3161  }
3162  } else {
3163  $result = Status::newGood( true );
3164  }
3165 
3166  return $result;
3167  }
3168 
3173  public function getRealName() {
3174  if ( !$this->isItemLoaded( 'realname' ) ) {
3175  $this->load();
3176  }
3177 
3178  return $this->mRealName;
3179  }
3180 
3185  public function setRealName( $str ) {
3186  $this->load();
3187  $this->mRealName = $str;
3188  }
3189 
3200  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3201  global $wgHiddenPrefs;
3202  $this->loadOptions();
3203 
3204  # We want 'disabled' preferences to always behave as the default value for
3205  # users, even if they have set the option explicitly in their settings (ie they
3206  # set it, and then it was disabled removing their ability to change it). But
3207  # we don't want to erase the preferences in the database in case the preference
3208  # is re-enabled again. So don't touch $mOptions, just override the returned value
3209  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3210  return self::getDefaultOption( $oname );
3211  }
3212 
3213  if ( array_key_exists( $oname, $this->mOptions ) ) {
3214  return $this->mOptions[$oname];
3215  } else {
3216  return $defaultOverride;
3217  }
3218  }
3219 
3228  public function getOptions( $flags = 0 ) {
3229  global $wgHiddenPrefs;
3230  $this->loadOptions();
3232 
3233  # We want 'disabled' preferences to always behave as the default value for
3234  # users, even if they have set the option explicitly in their settings (ie they
3235  # set it, and then it was disabled removing their ability to change it). But
3236  # we don't want to erase the preferences in the database in case the preference
3237  # is re-enabled again. So don't touch $mOptions, just override the returned value
3238  foreach ( $wgHiddenPrefs as $pref ) {
3239  $default = self::getDefaultOption( $pref );
3240  if ( $default !== null ) {
3241  $options[$pref] = $default;
3242  }
3243  }
3244 
3245  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3246  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3247  }
3248 
3249  return $options;
3250  }
3251 
3259  public function getBoolOption( $oname ) {
3260  return (bool)$this->getOption( $oname );
3261  }
3262 
3271  public function getIntOption( $oname, $defaultOverride = 0 ) {
3272  $val = $this->getOption( $oname );
3273  if ( $val == '' ) {
3274  $val = $defaultOverride;
3275  }
3276  return intval( $val );
3277  }
3278 
3287  public function setOption( $oname, $val ) {
3288  $this->loadOptions();
3289 
3290  // Explicitly NULL values should refer to defaults
3291  if ( is_null( $val ) ) {
3292  $val = self::getDefaultOption( $oname );
3293  }
3294 
3295  $this->mOptions[$oname] = $val;
3296  }
3297 
3308  public function getTokenFromOption( $oname ) {
3309  global $wgHiddenPrefs;
3310 
3311  $id = $this->getId();
3312  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3313  return false;
3314  }
3315 
3316  $token = $this->getOption( $oname );
3317  if ( !$token ) {
3318  // Default to a value based on the user token to avoid space
3319  // wasted on storing tokens for all users. When this option
3320  // is set manually by the user, only then is it stored.
3321  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3322  }
3323 
3324  return $token;
3325  }
3326 
3336  public function resetTokenFromOption( $oname ) {
3337  global $wgHiddenPrefs;
3338  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3339  return false;
3340  }
3341 
3342  $token = MWCryptRand::generateHex( 40 );
3343  $this->setOption( $oname, $token );
3344  return $token;
3345  }
3346 
3370  public static function listOptionKinds() {
3371  return [
3372  'registered',
3373  'registered-multiselect',
3374  'registered-checkmatrix',
3375  'userjs',
3376  'special',
3377  'unused'
3378  ];
3379  }
3380 
3394  $this->loadOptions();
3395  if ( $options === null ) {
3397  }
3398 
3399  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3400  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3401  $mapping = [];
3402 
3403  // Pull out the "special" options, so they don't get converted as
3404  // multiselect or checkmatrix.
3405  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3406  foreach ( $specialOptions as $name => $value ) {
3407  unset( $prefs[$name] );
3408  }
3409 
3410  // Multiselect and checkmatrix options are stored in the database with
3411  // one key per option, each having a boolean value. Extract those keys.
3412  $multiselectOptions = [];
3413  foreach ( $prefs as $name => $info ) {
3414  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3415  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3416  $opts = HTMLFormField::flattenOptions( $info['options'] );
3417  $prefix = $info['prefix'] ?? $name;
3418 
3419  foreach ( $opts as $value ) {
3420  $multiselectOptions["$prefix$value"] = true;
3421  }
3422 
3423  unset( $prefs[$name] );
3424  }
3425  }
3426  $checkmatrixOptions = [];
3427  foreach ( $prefs as $name => $info ) {
3428  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3429  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3430  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3431  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3432  $prefix = $info['prefix'] ?? $name;
3433 
3434  foreach ( $columns as $column ) {
3435  foreach ( $rows as $row ) {
3436  $checkmatrixOptions["$prefix$column-$row"] = true;
3437  }
3438  }
3439 
3440  unset( $prefs[$name] );
3441  }
3442  }
3443 
3444  // $value is ignored
3445  foreach ( $options as $key => $value ) {
3446  if ( isset( $prefs[$key] ) ) {
3447  $mapping[$key] = 'registered';
3448  } elseif ( isset( $multiselectOptions[$key] ) ) {
3449  $mapping[$key] = 'registered-multiselect';
3450  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3451  $mapping[$key] = 'registered-checkmatrix';
3452  } elseif ( isset( $specialOptions[$key] ) ) {
3453  $mapping[$key] = 'special';
3454  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3455  $mapping[$key] = 'userjs';
3456  } else {
3457  $mapping[$key] = 'unused';
3458  }
3459  }
3460 
3461  return $mapping;
3462  }
3463 
3478  public function resetOptions(
3479  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3481  ) {
3482  $this->load();
3483  $defaultOptions = self::getDefaultOptions();
3484 
3485  if ( !is_array( $resetKinds ) ) {
3486  $resetKinds = [ $resetKinds ];
3487  }
3488 
3489  if ( in_array( 'all', $resetKinds ) ) {
3490  $newOptions = $defaultOptions;
3491  } else {
3492  if ( $context === null ) {
3494  }
3495 
3496  $optionKinds = $this->getOptionKinds( $context );
3497  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3498  $newOptions = [];
3499 
3500  // Use default values for the options that should be deleted, and
3501  // copy old values for the ones that shouldn't.
3502  foreach ( $this->mOptions as $key => $value ) {
3503  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3504  if ( array_key_exists( $key, $defaultOptions ) ) {
3505  $newOptions[$key] = $defaultOptions[$key];
3506  }
3507  } else {
3508  $newOptions[$key] = $value;
3509  }
3510  }
3511  }
3512 
3513  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3514 
3515  $this->mOptions = $newOptions;
3516  $this->mOptionsLoaded = true;
3517  }
3518 
3523  public function getDatePreference() {
3524  // Important migration for old data rows
3525  if ( is_null( $this->mDatePreference ) ) {
3526  global $wgLang;
3527  $value = $this->getOption( 'date' );
3528  $map = $wgLang->getDatePreferenceMigrationMap();
3529  if ( isset( $map[$value] ) ) {
3530  $value = $map[$value];
3531  }
3532  $this->mDatePreference = $value;
3533  }
3534  return $this->mDatePreference;
3535  }
3536 
3543  public function requiresHTTPS() {
3544  global $wgSecureLogin;
3545  if ( !$wgSecureLogin ) {
3546  return false;
3547  } else {
3548  $https = $this->getBoolOption( 'prefershttps' );
3549  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3550  if ( $https ) {
3551  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3552  }
3553  return $https;
3554  }
3555  }
3556 
3562  public function getStubThreshold() {
3563  global $wgMaxArticleSize; # Maximum article size, in Kb
3564  $threshold = $this->getIntOption( 'stubthreshold' );
3565  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3566  // If they have set an impossible value, disable the preference
3567  // so we can use the parser cache again.
3568  $threshold = 0;
3569  }
3570  return $threshold;
3571  }
3572 
3577  public function getRights() {
3578  if ( is_null( $this->mRights ) ) {
3579  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3580  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3581 
3582  // Deny any rights denied by the user's session, unless this
3583  // endpoint has no sessions.
3584  if ( !defined( 'MW_NO_SESSION' ) ) {
3585  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3586  if ( $allowedRights !== null ) {
3587  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3588  }
3589  }
3590 
3591  Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
3592  // Force reindexation of rights when a hook has unset one of them
3593  $this->mRights = array_values( array_unique( $this->mRights ) );
3594 
3595  // If block disables login, we should also remove any
3596  // extra rights blocked users might have, in case the
3597  // blocked user has a pre-existing session (T129738).
3598  // This is checked here for cases where people only call
3599  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3600  // to give a better error message in the common case.
3601  $config = RequestContext::getMain()->getConfig();
3602  if (
3603  $this->isLoggedIn() &&
3604  $config->get( 'BlockDisablesLogin' ) &&
3605  $this->isBlocked()
3606  ) {
3607  $anon = new User;
3608  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3609  }
3610  }
3611  return $this->mRights;
3612  }
3613 
3620  public function getGroups() {
3621  $this->load();
3622  $this->loadGroups();
3623  return array_keys( $this->mGroupMemberships );
3624  }
3625 
3633  public function getGroupMemberships() {
3634  $this->load();
3635  $this->loadGroups();
3636  return $this->mGroupMemberships;
3637  }
3638 
3646  public function getEffectiveGroups( $recache = false ) {
3647  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3648  $this->mEffectiveGroups = array_unique( array_merge(
3649  $this->getGroups(), // explicit groups
3650  $this->getAutomaticGroups( $recache ) // implicit groups
3651  ) );
3652  // Avoid PHP 7.1 warning of passing $this by reference
3653  $user = $this;
3654  // Hook for additional groups
3655  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3656  // Force reindexation of groups when a hook has unset one of them
3657  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3658  }
3659  return $this->mEffectiveGroups;
3660  }
3661 
3669  public function getAutomaticGroups( $recache = false ) {
3670  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3671  $this->mImplicitGroups = [ '*' ];
3672  if ( $this->getId() ) {
3673  $this->mImplicitGroups[] = 'user';
3674 
3675  $this->mImplicitGroups = array_unique( array_merge(
3676  $this->mImplicitGroups,
3678  ) );
3679  }
3680  if ( $recache ) {
3681  // Assure data consistency with rights/groups,
3682  // as getEffectiveGroups() depends on this function
3683  $this->mEffectiveGroups = null;
3684  }
3685  }
3686  return $this->mImplicitGroups;
3687  }
3688 
3698  public function getFormerGroups() {
3699  $this->load();
3700 
3701  if ( is_null( $this->mFormerGroups ) ) {
3702  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3703  ? wfGetDB( DB_MASTER )
3704  : wfGetDB( DB_REPLICA );
3705  $res = $db->select( 'user_former_groups',
3706  [ 'ufg_group' ],
3707  [ 'ufg_user' => $this->mId ],
3708  __METHOD__ );
3709  $this->mFormerGroups = [];
3710  foreach ( $res as $row ) {
3711  $this->mFormerGroups[] = $row->ufg_group;
3712  }
3713  }
3714 
3715  return $this->mFormerGroups;
3716  }
3717 
3722  public function getEditCount() {
3723  if ( !$this->getId() ) {
3724  return null;
3725  }
3726 
3727  if ( $this->mEditCount === null ) {
3728  /* Populate the count, if it has not been populated yet */
3729  $dbr = wfGetDB( DB_REPLICA );
3730  // check if the user_editcount field has been initialized
3731  $count = $dbr->selectField(
3732  'user', 'user_editcount',
3733  [ 'user_id' => $this->mId ],
3734  __METHOD__
3735  );
3736 
3737  if ( $count === null ) {
3738  // it has not been initialized. do so.
3739  $count = $this->initEditCountInternal();
3740  }
3741  $this->mEditCount = $count;
3742  }
3743  return (int)$this->mEditCount;
3744  }
3745 
3757  public function addGroup( $group, $expiry = null ) {
3758  $this->load();
3759  $this->loadGroups();
3760 
3761  if ( $expiry ) {
3762  $expiry = wfTimestamp( TS_MW, $expiry );
3763  }
3764 
3765  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3766  return false;
3767  }
3768 
3769  // create the new UserGroupMembership and put it in the DB
3770  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3771  if ( !$ugm->insert( true ) ) {
3772  return false;
3773  }
3774 
3775  $this->mGroupMemberships[$group] = $ugm;
3776 
3777  // Refresh the groups caches, and clear the rights cache so it will be
3778  // refreshed on the next call to $this->getRights().
3779  $this->getEffectiveGroups( true );
3780  $this->mRights = null;
3781 
3782  $this->invalidateCache();
3783 
3784  return true;
3785  }
3786 
3793  public function removeGroup( $group ) {
3794  $this->load();
3795 
3796  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3797  return false;
3798  }
3799 
3800  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3801  // delete the membership entry
3802  if ( !$ugm || !$ugm->delete() ) {
3803  return false;
3804  }
3805 
3806  $this->loadGroups();
3807  unset( $this->mGroupMemberships[$group] );
3808 
3809  // Refresh the groups caches, and clear the rights cache so it will be
3810  // refreshed on the next call to $this->getRights().
3811  $this->getEffectiveGroups( true );
3812  $this->mRights = null;
3813 
3814  $this->invalidateCache();
3815 
3816  return true;
3817  }
3818 
3823  public function isLoggedIn() {
3824  return $this->getId() != 0;
3825  }
3826 
3831  public function isAnon() {
3832  return !$this->isLoggedIn();
3833  }
3834 
3839  public function isBot() {
3840  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3841  return true;
3842  }
3843 
3844  $isBot = false;
3845  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3846 
3847  return $isBot;
3848  }
3849 
3856  public function isAllowedAny() {
3857  $permissions = func_get_args();
3858  foreach ( $permissions as $permission ) {
3859  if ( $this->isAllowed( $permission ) ) {
3860  return true;
3861  }
3862  }
3863  return false;
3864  }
3865 
3871  public function isAllowedAll() {
3872  $permissions = func_get_args();
3873  foreach ( $permissions as $permission ) {
3874  if ( !$this->isAllowed( $permission ) ) {
3875  return false;
3876  }
3877  }
3878  return true;
3879  }
3880 
3886  public function isAllowed( $action = '' ) {
3887  if ( $action === '' ) {
3888  return true; // In the spirit of DWIM
3889  }
3890  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3891  // by misconfiguration: 0 == 'foo'
3892  return in_array( $action, $this->getRights(), true );
3893  }
3894 
3899  public function useRCPatrol() {
3900  global $wgUseRCPatrol;
3901  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3902  }
3903 
3908  public function useNPPatrol() {
3910  return (
3911  ( $wgUseRCPatrol || $wgUseNPPatrol )
3912  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3913  );
3914  }
3915 
3920  public function useFilePatrol() {
3922  return (
3923  ( $wgUseRCPatrol || $wgUseFilePatrol )
3924  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3925  );
3926  }
3927 
3933  public function getRequest() {
3934  if ( $this->mRequest ) {
3935  return $this->mRequest;
3936  } else {
3937  global $wgRequest;
3938  return $wgRequest;
3939  }
3940  }
3941 
3950  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3951  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3952  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3953  }
3954  return false;
3955  }
3956 
3964  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3965  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3966  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3967  $this,
3968  [ $title->getSubjectPage(), $title->getTalkPage() ]
3969  );
3970  }
3971  $this->invalidateCache();
3972  }
3973 
3981  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3982  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3983  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3984  $store->removeWatch( $this, $title->getSubjectPage() );
3985  $store->removeWatch( $this, $title->getTalkPage() );
3986  }
3987  $this->invalidateCache();
3988  }
3989 
3998  public function clearNotification( &$title, $oldid = 0 ) {
4000 
4001  // Do nothing if the database is locked to writes
4002  if ( wfReadOnly() ) {
4003  return;
4004  }
4005 
4006  // Do nothing if not allowed to edit the watchlist
4007  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
4008  return;
4009  }
4010 
4011  // If we're working on user's talk page, we should update the talk page message indicator
4012  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
4013  // Avoid PHP 7.1 warning of passing $this by reference
4014  $user = $this;
4015  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
4016  return;
4017  }
4018 
4019  // Try to update the DB post-send and only if needed...
4020  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
4021  if ( !$this->getNewtalk() ) {
4022  return; // no notifications to clear
4023  }
4024 
4025  // Delete the last notifications (they stack up)
4026  $this->setNewtalk( false );
4027 
4028  // If there is a new, unseen, revision, use its timestamp
4029  $nextid = $oldid
4030  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
4031  : null;
4032  if ( $nextid ) {
4033  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
4034  }
4035  } );
4036  }
4037 
4038  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4039  return;
4040  }
4041 
4042  if ( $this->isAnon() ) {
4043  // Nothing else to do...
4044  return;
4045  }
4046 
4047  // Only update the timestamp if the page is being watched.
4048  // The query to find out if it is watched is cached both in memcached and per-invocation,
4049  // and when it does have to be executed, it can be on a replica DB
4050  // If this is the user's newtalk page, we always update the timestamp
4051  $force = '';
4052  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
4053  $force = 'force';
4054  }
4055 
4056  MediaWikiServices::getInstance()->getWatchedItemStore()
4057  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
4058  }
4059 
4066  public function clearAllNotifications() {
4068  // Do nothing if not allowed to edit the watchlist
4069  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
4070  return;
4071  }
4072 
4073  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4074  $this->setNewtalk( false );
4075  return;
4076  }
4077 
4078  $id = $this->getId();
4079  if ( !$id ) {
4080  return;
4081  }
4082 
4083  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
4084  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
4085 
4086  // We also need to clear here the "you have new message" notification for the own
4087  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
4088  }
4089 
4095  public function getExperienceLevel() {
4096  global $wgLearnerEdits,
4100 
4101  if ( $this->isAnon() ) {
4102  return false;
4103  }
4104 
4105  $editCount = $this->getEditCount();
4106  $registration = $this->getRegistration();
4107  $now = time();
4108  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
4109  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
4110 
4111  if (
4112  $editCount < $wgLearnerEdits ||
4113  $registration > $learnerRegistration
4114  ) {
4115  return 'newcomer';
4116  } elseif (
4117  $editCount > $wgExperiencedUserEdits &&
4118  $registration <= $experiencedRegistration
4119  ) {
4120  return 'experienced';
4121  } else {
4122  return 'learner';
4123  }
4124  }
4125 
4134  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
4135  $this->load();
4136  if ( $this->mId == 0 ) {
4137  return;
4138  }
4139 
4140  $session = $this->getRequest()->getSession();
4141  if ( $request && $session->getRequest() !== $request ) {
4142  $session = $session->sessionWithRequest( $request );
4143  }
4144  $delay = $session->delaySave();
4145 
4146  if ( !$session->getUser()->equals( $this ) ) {
4147  if ( !$session->canSetUser() ) {
4149  ->warning( __METHOD__ .
4150  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
4151  );
4152  return;
4153  }
4154  $session->setUser( $this );
4155  }
4156 
4157  $session->setRememberUser( $rememberMe );
4158  if ( $secure !== null ) {
4159  $session->setForceHTTPS( $secure );
4160  }
4161 
4162  $session->persist();
4163 
4164  ScopedCallback::consume( $delay );
4165  }
4166 
4170  public function logout() {
4171  // Avoid PHP 7.1 warning of passing $this by reference
4172  $user = $this;
4173  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
4174  $this->doLogout();
4175  }
4176  }
4177 
4182  public function doLogout() {
4183  $session = $this->getRequest()->getSession();
4184  if ( !$session->canSetUser() ) {
4186  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
4187  $error = 'immutable';
4188  } elseif ( !$session->getUser()->equals( $this ) ) {
4190  ->warning( __METHOD__ .
4191  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4192  );
4193  // But we still may as well make this user object anon
4194  $this->clearInstanceCache( 'defaults' );
4195  $error = 'wronguser';
4196  } else {
4197  $this->clearInstanceCache( 'defaults' );
4198  $delay = $session->delaySave();
4199  $session->unpersist(); // Clear cookies (T127436)
4200  $session->setLoggedOutTimestamp( time() );
4201  $session->setUser( new User );
4202  $session->set( 'wsUserID', 0 ); // Other code expects this
4203  $session->resetAllTokens();
4204  ScopedCallback::consume( $delay );
4205  $error = false;
4206  }
4207  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4208  'event' => 'logout',
4209  'successful' => $error === false,
4210  'status' => $error ?: 'success',
4211  ] );
4212  }
4213 
4218  public function saveSettings() {
4219  if ( wfReadOnly() ) {
4220  // @TODO: caller should deal with this instead!
4221  // This should really just be an exception.
4223  null,
4224  "Could not update user with ID '{$this->mId}'; DB is read-only."
4225  ) );
4226  return;
4227  }
4228 
4229  $this->load();
4230  if ( $this->mId == 0 ) {
4231  return; // anon
4232  }
4233 
4234  // Get a new user_touched that is higher than the old one.
4235  // This will be used for a CAS check as a last-resort safety
4236  // check against race conditions and replica DB lag.
4237  $newTouched = $this->newTouchedTimestamp();
4238 
4239  $dbw = wfGetDB( DB_MASTER );
4240  $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
4242 
4243  $dbw->update( 'user',
4244  [ /* SET */
4245  'user_name' => $this->mName,
4246  'user_real_name' => $this->mRealName,
4247  'user_email' => $this->mEmail,
4248  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4249  'user_touched' => $dbw->timestamp( $newTouched ),
4250  'user_token' => strval( $this->mToken ),
4251  'user_email_token' => $this->mEmailToken,
4252  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4253  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4254  'user_id' => $this->mId,
4255  ] ), $fname
4256  );
4257 
4258  if ( !$dbw->affectedRows() ) {
4259  // Maybe the problem was a missed cache update; clear it to be safe
4260  $this->clearSharedCache( 'refresh' );
4261  // User was changed in the meantime or loaded with stale data
4262  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4263  LoggerFactory::getInstance( 'preferences' )->warning(
4264  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4265  [ 'user_id' => $this->mId, 'db_flag' => $from ]
4266  );
4267  throw new MWException( "CAS update failed on user_touched. " .
4268  "The version of the user to be saved is older than the current version."
4269  );
4270  }
4271 
4272  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4273  $dbw->update(
4274  'actor',
4275  [ 'actor_name' => $this->mName ],
4276  [ 'actor_user' => $this->mId ],
4277  $fname
4278  );
4279  }
4280  } );
4281 
4282  $this->mTouched = $newTouched;
4283  $this->saveOptions();
4284 
4285  Hooks::run( 'UserSaveSettings', [ $this ] );
4286  $this->clearSharedCache();
4287  $this->getUserPage()->invalidateCache();
4288  }
4289 
4296  public function idForName( $flags = 0 ) {
4297  $s = trim( $this->getName() );
4298  if ( $s === '' ) {
4299  return 0;
4300  }
4301 
4302  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4303  ? wfGetDB( DB_MASTER )
4304  : wfGetDB( DB_REPLICA );
4305 
4306  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4307  ? [ 'LOCK IN SHARE MODE' ]
4308  : [];
4309 
4310  $id = $db->selectField( 'user',
4311  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4312 
4313  return (int)$id;
4314  }
4315 
4331  public static function createNew( $name, $params = [] ) {
4332  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4333  if ( isset( $params[$field] ) ) {
4334  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4335  unset( $params[$field] );
4336  }
4337  }
4338 
4339  $user = new User;
4340  $user->load();
4341  $user->setToken(); // init token
4342  if ( isset( $params['options'] ) ) {
4343  $user->mOptions = $params['options'] + (array)$user->mOptions;
4344  unset( $params['options'] );
4345  }
4346  $dbw = wfGetDB( DB_MASTER );
4347 
4348  $noPass = PasswordFactory::newInvalidPassword()->toString();
4349 
4350  $fields = [
4351  'user_name' => $name,
4352  'user_password' => $noPass,
4353  'user_newpassword' => $noPass,
4354  'user_email' => $user->mEmail,
4355  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4356  'user_real_name' => $user->mRealName,
4357  'user_token' => strval( $user->mToken ),
4358  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4359  'user_editcount' => 0,
4360  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4361  ];
4362  foreach ( $params as $name => $value ) {
4363  $fields["user_$name"] = $value;
4364  }
4365 
4366  return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
4367  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4368  if ( $dbw->affectedRows() ) {
4369  $newUser = self::newFromId( $dbw->insertId() );
4370  $newUser->mName = $fields['user_name'];
4371  $newUser->updateActorId( $dbw );
4372  // Load the user from master to avoid replica lag
4373  $newUser->load( self::READ_LATEST );
4374  } else {
4375  $newUser = null;
4376  }
4377  return $newUser;
4378  } );
4379  }
4380 
4407  public function addToDatabase() {
4408  $this->load();
4409  if ( !$this->mToken ) {
4410  $this->setToken(); // init token
4411  }
4412 
4413  if ( !is_string( $this->mName ) ) {
4414  throw new RuntimeException( "User name field is not set." );
4415  }
4416 
4417  $this->mTouched = $this->newTouchedTimestamp();
4418 
4419  $dbw = wfGetDB( DB_MASTER );
4420  $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
4421  $noPass = PasswordFactory::newInvalidPassword()->toString();
4422  $dbw->insert( 'user',
4423  [
4424  'user_name' => $this->mName,
4425  'user_password' => $noPass,
4426  'user_newpassword' => $noPass,
4427  'user_email' => $this->mEmail,
4428  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4429  'user_real_name' => $this->mRealName,
4430  'user_token' => strval( $this->mToken ),
4431  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4432  'user_editcount' => 0,
4433  'user_touched' => $dbw->timestamp( $this->mTouched ),
4434  ], $fname,
4435  [ 'IGNORE' ]
4436  );
4437  if ( !$dbw->affectedRows() ) {
4438  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4439  $this->mId = $dbw->selectField(
4440  'user',
4441  'user_id',
4442  [ 'user_name' => $this->mName ],
4443  $fname,
4444  [ 'LOCK IN SHARE MODE' ]
4445  );
4446  $loaded = false;
4447  if ( $this->mId ) {
4448  if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4449  $loaded = true;
4450  }
4451  }
4452  if ( !$loaded ) {
4453  throw new MWException( $fname . ": hit a key conflict attempting " .
4454  "to insert user '{$this->mName}' row, but it was not present in select!" );
4455  }
4456  return Status::newFatal( 'userexists' );
4457  }
4458  $this->mId = $dbw->insertId();
4459  self::$idCacheByName[$this->mName] = $this->mId;
4460  $this->updateActorId( $dbw );
4461 
4462  return Status::newGood();
4463  } );
4464  if ( !$status->isGood() ) {
4465  return $status;
4466  }
4467 
4468  // Clear instance cache other than user table data and actor, which is already accurate
4469  $this->clearInstanceCache();
4470 
4471  $this->saveOptions();
4472  return Status::newGood();
4473  }
4474 
4479  private function updateActorId( IDatabase $dbw ) {
4481 
4482  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4483  $dbw->insert(
4484  'actor',
4485  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4486  __METHOD__
4487  );
4488  $this->mActorId = (int)$dbw->insertId();
4489  }
4490  }
4491 
4497  public function spreadAnyEditBlock() {
4498  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4499  return $this->spreadBlock();
4500  }
4501 
4502  return false;
4503  }
4504 
4510  protected function spreadBlock() {
4511  wfDebug( __METHOD__ . "()\n" );
4512  $this->load();
4513  if ( $this->mId == 0 ) {
4514  return false;
4515  }
4516 
4517  $userblock = Block::newFromTarget( $this->getName() );
4518  if ( !$userblock ) {
4519  return false;
4520  }
4521 
4522  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4523  }
4524 
4529  public function isBlockedFromCreateAccount() {
4530  $this->getBlockedStatus();
4531  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4532  return $this->mBlock;
4533  }
4534 
4535  # T15611: if the IP address the user is trying to create an account from is
4536  # blocked with createaccount disabled, prevent new account creation there even
4537  # when the user is logged in
4538  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4539  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4540  }
4541  return $this->mBlockedFromCreateAccount instanceof Block
4542  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4543  ? $this->mBlockedFromCreateAccount
4544  : false;
4545  }
4546 
4551  public function isBlockedFromEmailuser() {
4552  $this->getBlockedStatus();
4553  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4554  }
4555 
4562  public function isBlockedFromUpload() {
4563  $this->getBlockedStatus();
4564  return $this->mBlock && $this->mBlock->prevents( 'upload' );
4565  }
4566 
4571  public function isAllowedToCreateAccount() {
4572  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4573  }
4574 
4580  public function getUserPage() {
4581  return Title::makeTitle( NS_USER, $this->getName() );
4582  }
4583 
4589  public function getTalkPage() {
4590  $title = $this->getUserPage();
4591  return $title->getTalkPage();
4592  }
4593 
4599  public function isNewbie() {
4600  return !$this->isAllowed( 'autoconfirmed' );
4601  }
4602 
4609  public function checkPassword( $password ) {
4610  wfDeprecated( __METHOD__, '1.27' );
4611 
4612  $manager = AuthManager::singleton();
4613  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4614  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4615  [
4616  'username' => $this->getName(),
4617  'password' => $password,
4618  ]
4619  );
4620  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4621  switch ( $res->status ) {
4622  case AuthenticationResponse::PASS:
4623  return true;
4624  case AuthenticationResponse::FAIL:
4625  // Hope it's not a PreAuthenticationProvider that failed...
4627  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4628  return false;
4629  default:
4630  throw new BadMethodCallException(
4631  'AuthManager returned a response unsupported by ' . __METHOD__
4632  );
4633  }
4634  }
4635 
4644  public function checkTemporaryPassword( $plaintext ) {
4645  wfDeprecated( __METHOD__, '1.27' );
4646  // Can't check the temporary password individually.
4647  return $this->checkPassword( $plaintext );
4648  }
4649 
4661  public function getEditTokenObject( $salt = '', $request = null ) {
4662  if ( $this->isAnon() ) {
4663  return new LoggedOutEditToken();
4664  }
4665 
4666  if ( !$request ) {
4667  $request = $this->getRequest();
4668  }
4669  return $request->getSession()->getToken( $salt );
4670  }
4671 
4685  public function getEditToken( $salt = '', $request = null ) {
4686  return $this->getEditTokenObject( $salt, $request )->toString();
4687  }
4688 
4701  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4702  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4703  }
4704 
4715  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4716  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4717  return $this->matchEditToken( $val, $salt, $request, $maxage );
4718  }
4719 
4727  public function sendConfirmationMail( $type = 'created' ) {
4728  global $wgLang;
4729  $expiration = null; // gets passed-by-ref and defined in next line.
4730  $token = $this->confirmationToken( $expiration );
4731  $url = $this->confirmationTokenUrl( $token );
4732  $invalidateURL = $this->invalidationTokenUrl( $token );
4733  $this->saveSettings();
4734 
4735  if ( $type == 'created' || $type === false ) {
4736  $message = 'confirmemail_body';
4737  } elseif ( $type === true ) {
4738  $message = 'confirmemail_body_changed';
4739  } else {
4740  // Messages: confirmemail_body_changed, confirmemail_body_set
4741  $message = 'confirmemail_body_' . $type;
4742  }
4743 
4744  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4745  wfMessage( $message,
4746  $this->getRequest()->getIP(),
4747  $this->getName(),
4748  $url,
4749  $wgLang->userTimeAndDate( $expiration, $this ),
4750  $invalidateURL,
4751  $wgLang->userDate( $expiration, $this ),
4752  $wgLang->userTime( $expiration, $this ) )->text() );
4753  }
4754 
4766  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4767  global $wgPasswordSender;
4768 
4769  if ( $from instanceof User ) {
4770  $sender = MailAddress::newFromUser( $from );
4771  } else {
4772  $sender = new MailAddress( $wgPasswordSender,
4773  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4774  }
4775  $to = MailAddress::newFromUser( $this );
4776 
4777  return UserMailer::send( $to, $sender, $subject, $body, [
4778  'replyTo' => $replyto,
4779  ] );
4780  }
4781 
4792  protected function confirmationToken( &$expiration ) {
4794  $now = time();
4795  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4796  $expiration = wfTimestamp( TS_MW, $expires );
4797  $this->load();
4798  $token = MWCryptRand::generateHex( 32 );
4799  $hash = md5( $token );
4800  $this->mEmailToken = $hash;
4801  $this->mEmailTokenExpires = $expiration;
4802  return $token;
4803  }
4804 
4810  protected function confirmationTokenUrl( $token ) {
4811  return $this->getTokenUrl( 'ConfirmEmail', $token );
4812  }
4813 
4819  protected function invalidationTokenUrl( $token ) {
4820  return $this->getTokenUrl( 'InvalidateEmail', $token );
4821  }
4822 
4837  protected function getTokenUrl( $page, $token ) {
4838  // Hack to bypass localization of 'Special:'
4839  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4840  return $title->getCanonicalURL();
4841  }
4842 
4850  public function confirmEmail() {
4851  // Check if it's already confirmed, so we don't touch the database
4852  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4853  if ( !$this->isEmailConfirmed() ) {
4855  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4856  }
4857  return true;
4858  }
4859 
4867  public function invalidateEmail() {
4868  $this->load();
4869  $this->mEmailToken = null;
4870  $this->mEmailTokenExpires = null;
4872  $this->mEmail = '';
4873  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4874  return true;
4875  }
4876 
4881  public function setEmailAuthenticationTimestamp( $timestamp ) {
4882  $this->load();
4883  $this->mEmailAuthenticated = $timestamp;
4884  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4885  }
4886 
4892  public function canSendEmail() {
4894  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4895  return false;
4896  }
4897  $canSend = $this->isEmailConfirmed();
4898  // Avoid PHP 7.1 warning of passing $this by reference
4899  $user = $this;
4900  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4901  return $canSend;
4902  }
4903 
4909  public function canReceiveEmail() {
4910  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4911  }
4912 
4923  public function isEmailConfirmed() {
4924  global $wgEmailAuthentication;
4925  $this->load();
4926  // Avoid PHP 7.1 warning of passing $this by reference
4927  $user = $this;
4928  $confirmed = true;
4929  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4930  if ( $this->isAnon() ) {
4931  return false;
4932  }
4933  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4934  return false;
4935  }
4936  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4937  return false;
4938  }
4939  return true;
4940  } else {
4941  return $confirmed;
4942  }
4943  }
4944 
4949  public function isEmailConfirmationPending() {
4950  global $wgEmailAuthentication;
4951  return $wgEmailAuthentication &&
4952  !$this->isEmailConfirmed() &&
4953  $this->mEmailToken &&
4954  $this->mEmailTokenExpires > wfTimestamp();
4955  }
4956 
4964  public function getRegistration() {
4965  if ( $this->isAnon() ) {
4966  return false;
4967  }
4968  $this->load();
4969  return $this->mRegistration;
4970  }
4971 
4978  public function getFirstEditTimestamp() {
4979  if ( $this->getId() == 0 ) {
4980  return false; // anons
4981  }
4982  $dbr = wfGetDB( DB_REPLICA );
4983  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4984  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4985  ? 'revactor_timestamp' : 'rev_timestamp';
4986  $time = $dbr->selectField(
4987  [ 'revision' ] + $actorWhere['tables'],
4988  $tsField,
4989  [ $actorWhere['conds'] ],
4990  __METHOD__,
4991  [ 'ORDER BY' => "$tsField ASC" ],
4992  $actorWhere['joins']
4993  );
4994  if ( !$time ) {
4995  return false; // no edits
4996  }
4997  return wfTimestamp( TS_MW, $time );
4998  }
4999 
5006  public static function getGroupPermissions( $groups ) {
5008  $rights = [];
5009  // grant every granted permission first
5010  foreach ( $groups as $group ) {
5011  if ( isset( $wgGroupPermissions[$group] ) ) {
5012  $rights = array_merge( $rights,
5013  // array_filter removes empty items
5014  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
5015  }
5016  }
5017  // now revoke the revoked permissions
5018  foreach ( $groups as $group ) {
5019  if ( isset( $wgRevokePermissions[$group] ) ) {
5020  $rights = array_diff( $rights,
5021  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
5022  }
5023  }
5024  return array_unique( $rights );
5025  }
5026 
5033  public static function getGroupsWithPermission( $role ) {
5034  global $wgGroupPermissions;
5035  $allowedGroups = [];
5036  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
5037  if ( self::groupHasPermission( $group, $role ) ) {
5038  $allowedGroups[] = $group;
5039  }
5040  }
5041  return $allowedGroups;
5042  }
5043 
5056  public static function groupHasPermission( $group, $role ) {
5058  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
5059  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
5060  }
5061 
5076  public static function isEveryoneAllowed( $right ) {
5078  static $cache = [];
5079 
5080  // Use the cached results, except in unit tests which rely on
5081  // being able change the permission mid-request
5082  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
5083  return $cache[$right];
5084  }
5085 
5086  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
5087  $cache[$right] = false;
5088  return false;
5089  }
5090 
5091  // If it's revoked anywhere, then everyone doesn't have it
5092  foreach ( $wgRevokePermissions as $rights ) {
5093  if ( isset( $rights[$right] ) && $rights[$right] ) {
5094  $cache[$right] = false;
5095  return false;
5096  }
5097  }
5098 
5099  // Remove any rights that aren't allowed to the global-session user,
5100  // unless there are no sessions for this endpoint.
5101  if ( !defined( 'MW_NO_SESSION' ) ) {
5102  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
5103  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
5104  $cache[$right] = false;
5105  return false;
5106  }
5107  }
5108 
5109  // Allow extensions to say false
5110  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
5111  $cache[$right] = false;
5112  return false;
5113  }
5114 
5115  $cache[$right] = true;
5116  return true;
5117  }
5118 
5126  public static function getGroupName( $group ) {
5127  wfDeprecated( __METHOD__, '1.29' );
5128  return UserGroupMembership::getGroupName( $group );
5129  }
5130 
5139  public static function getGroupMember( $group, $username = '#' ) {
5140  wfDeprecated( __METHOD__, '1.29' );
5142  }
5143 
5150  public static function getAllGroups() {
5152  return array_values( array_diff(
5153  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
5154  self::getImplicitGroups()
5155  ) );
5156  }
5157 
5162  public static function getAllRights() {
5163  if ( self::$mAllRights === false ) {
5164  global $wgAvailableRights;
5165  if ( count( $wgAvailableRights ) ) {
5166  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
5167  } else {
5168  self::$mAllRights = self::$mCoreRights;
5169  }
5170  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
5171  }
5172  return self::$mAllRights;
5173  }
5174 
5181  public static function getImplicitGroups() {
5182  global $wgImplicitGroups;
5183  return $wgImplicitGroups;
5184  }
5185 
5193  public static function getGroupPage( $group ) {
5194  wfDeprecated( __METHOD__, '1.29' );
5195  return UserGroupMembership::getGroupPage( $group );
5196  }
5197 
5208  public static function makeGroupLinkHTML( $group, $text = '' ) {
5209  wfDeprecated( __METHOD__, '1.29' );
5210 
5211  if ( $text == '' ) {
5212  $text = UserGroupMembership::getGroupName( $group );
5213  }
5215  if ( $title ) {
5216  return MediaWikiServices::getInstance()
5217  ->getLinkRenderer()->makeLink( $title, $text );
5218  } else {
5219  return htmlspecialchars( $text );
5220  }
5221  }
5222 
5233  public static function makeGroupLinkWiki( $group, $text = '' ) {
5234  wfDeprecated( __METHOD__, '1.29' );
5235 
5236  if ( $text == '' ) {
5237  $text = UserGroupMembership::getGroupName( $group );
5238  }
5240  if ( $title ) {
5241  $page = $title->getFullText();
5242  return "[[$page|$text]]";
5243  } else {
5244  return $text;
5245  }
5246  }
5247 
5257  public static function changeableByGroup( $group ) {
5259 
5260  $groups = [
5261  'add' => [],
5262  'remove' => [],
5263  'add-self' => [],
5264  'remove-self' => []
5265  ];
5266 
5267  if ( empty( $wgAddGroups[$group] ) ) {
5268  // Don't add anything to $groups
5269  } elseif ( $wgAddGroups[$group] === true ) {
5270  // You get everything
5271  $groups['add'] = self::getAllGroups();
5272  } elseif ( is_array( $wgAddGroups[$group] ) ) {
5273  $groups['add'] = $wgAddGroups[$group];
5274  }
5275 
5276  // Same thing for remove
5277  if ( empty( $wgRemoveGroups[$group] ) ) {
5278  // Do nothing
5279  } elseif ( $wgRemoveGroups[$group] === true ) {
5280  $groups['remove'] = self::getAllGroups();
5281  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5282  $groups['remove'] = $wgRemoveGroups[$group];
5283  }
5284 
5285  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5286  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5287  foreach ( $wgGroupsAddToSelf as $key => $value ) {
5288  if ( is_int( $key ) ) {
5289  $wgGroupsAddToSelf['user'][] = $value;
5290  }
5291  }
5292  }
5293 
5294  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5295  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5296  if ( is_int( $key ) ) {
5297  $wgGroupsRemoveFromSelf['user'][] = $value;
5298  }
5299  }
5300  }
5301 
5302  // Now figure out what groups the user can add to him/herself
5303  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5304  // Do nothing
5305  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5306  // No idea WHY this would be used, but it's there
5307  $groups['add-self'] = self::getAllGroups();
5308  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5309  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5310  }
5311 
5312  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5313  // Do nothing
5314  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5315  $groups['remove-self'] = self::getAllGroups();
5316  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5317  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5318  }
5319 
5320  return $groups;
5321  }
5322 
5330  public function changeableGroups() {
5331  if ( $this->isAllowed( 'userrights' ) ) {
5332  // This group gives the right to modify everything (reverse-
5333  // compatibility with old "userrights lets you change
5334  // everything")
5335  // Using array_merge to make the groups reindexed
5336  $all = array_merge( self::getAllGroups() );
5337  return [
5338  'add' => $all,
5339  'remove' => $all,
5340  'add-self' => [],
5341  'remove-self' => []
5342  ];
5343  }
5344 
5345  // Okay, it's not so simple, we will have to go through the arrays
5346  $groups = [
5347  'add' => [],
5348  'remove' => [],
5349  'add-self' => [],
5350  'remove-self' => []
5351  ];
5352  $addergroups = $this->getEffectiveGroups();
5353 
5354  foreach ( $addergroups as $addergroup ) {
5355  $groups = array_merge_recursive(
5356  $groups, $this->changeableByGroup( $addergroup )
5357  );
5358  $groups['add'] = array_unique( $groups['add'] );
5359  $groups['remove'] = array_unique( $groups['remove'] );
5360  $groups['add-self'] = array_unique( $groups['add-self'] );
5361  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5362  }
5363  return $groups;
5364  }
5365 
5369  public function incEditCount() {
5370  if ( $this->isAnon() ) {
5371  return; // sanity
5372  }
5373 
5375  new UserEditCountUpdate( $this, 1 ),
5377  );
5378  }
5379 
5385  public function setEditCountInternal( $count ) {
5386  $this->mEditCount = $count;
5387  }
5388 
5396  public function initEditCountInternal() {
5397  // Pull from a replica DB to be less cruel to servers
5398  // Accuracy isn't the point anyway here
5399  $dbr = wfGetDB( DB_REPLICA );
5400  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5401  $count = (int)$dbr->selectField(
5402  [ 'revision' ] + $actorWhere['tables'],
5403  'COUNT(*)',
5404  [ $actorWhere['conds'] ],
5405  __METHOD__,
5406  [],
5407  $actorWhere['joins']
5408  );
5409 
5410  $dbw = wfGetDB( DB_MASTER );
5411  $dbw->update(
5412  'user',
5413  [ 'user_editcount' => $count ],
5414  [
5415  'user_id' => $this->getId(),
5416  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5417  ],
5418  __METHOD__
5419  );
5420 
5421  return $count;
5422  }
5423 
5431  public static function getRightDescription( $right ) {
5432  $key = "right-$right";
5433  $msg = wfMessage( $key );
5434  return $msg->isDisabled() ? $right : $msg->text();
5435  }
5436 
5444  public static function getGrantName( $grant ) {
5445  $key = "grant-$grant";
5446  $msg = wfMessage( $key );
5447  return $msg->isDisabled() ? $grant : $msg->text();
5448  }
5449 
5470  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5471  return true; // disabled
5472  }
5473 
5482  public function addNewUserLogEntryAutoCreate() {
5483  $this->addNewUserLogEntry( 'autocreate' );
5484 
5485  return true;
5486  }
5487 
5493  protected function loadOptions( $data = null ) {
5494  $this->load();
5495 
5496  if ( $this->mOptionsLoaded ) {
5497  return;
5498  }
5499 
5500  $this->mOptions = self::getDefaultOptions();
5501 
5502  if ( !$this->getId() ) {
5503  // For unlogged-in users, load language/variant options from request.
5504  // There's no need to do it for logged-in users: they can set preferences,
5505  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5506  // so don't override user's choice (especially when the user chooses site default).
5507  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5508  $this->mOptions['variant'] = $variant;
5509  $this->mOptions['language'] = $variant;
5510  $this->mOptionsLoaded = true;
5511  return;
5512  }
5513 
5514  // Maybe load from the object
5515  if ( !is_null( $this->mOptionOverrides ) ) {
5516  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5517  foreach ( $this->mOptionOverrides as $key => $value ) {
5518  $this->mOptions[$key] = $value;
5519  }
5520  } else {
5521  if ( !is_array( $data ) ) {
5522  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5523  // Load from database
5524  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5525  ? wfGetDB( DB_MASTER )
5526  : wfGetDB( DB_REPLICA );
5527 
5528  $res = $dbr->select(
5529  'user_properties',
5530  [ 'up_property', 'up_value' ],
5531  [ 'up_user' => $this->getId() ],
5532  __METHOD__
5533  );
5534 
5535  $this->mOptionOverrides = [];
5536  $data = [];
5537  foreach ( $res as $row ) {
5538  // Convert '0' to 0. PHP's boolean conversion considers them both
5539  // false, but e.g. JavaScript considers the former as true.
5540  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5541  // and convert all values here.
5542  if ( $row->up_value === '0' ) {
5543  $row->up_value = 0;
5544  }
5545  $data[$row->up_property] = $row->up_value;
5546  }
5547  }
5548 
5549  foreach ( $data as $property => $value ) {
5550  $this->mOptionOverrides[$property] = $value;
5551  $this->mOptions[$property] = $value;
5552  }
5553  }
5554 
5555  // Replace deprecated language codes
5556  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5557  $this->mOptions['language']
5558  );
5559 
5560  $this->mOptionsLoaded = true;
5561 
5562  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5563  }
5564 
5570  protected function saveOptions() {
5571  $this->loadOptions();
5572 
5573  // Not using getOptions(), to keep hidden preferences in database
5574  $saveOptions = $this->mOptions;
5575 
5576  // Allow hooks to abort, for instance to save to a global profile.
5577  // Reset options to default state before saving.
5578  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5579  return;
5580  }
5581 
5582  $userId = $this->getId();
5583 
5584  $insert_rows = []; // all the new preference rows
5585  foreach ( $saveOptions as $key => $value ) {
5586  // Don't bother storing default values
5587  $defaultOption = self::getDefaultOption( $key );
5588  if ( ( $defaultOption === null && $value !== false && $value !== null )
5589  || $value != $defaultOption
5590  ) {
5591  $insert_rows[] = [
5592  'up_user' => $userId,
5593  'up_property' => $key,
5594  'up_value' => $value,
5595  ];
5596  }
5597  }
5598 
5599  $dbw = wfGetDB( DB_MASTER );
5600 
5601  $res = $dbw->select( 'user_properties',
5602  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5603 
5604  // Find prior rows that need to be removed or updated. These rows will
5605  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5606  $keysDelete = [];
5607  foreach ( $res as $row ) {
5608  if ( !isset( $saveOptions[$row->up_property] )
5609  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5610  ) {
5611  $keysDelete[] = $row->up_property;
5612  }
5613  }
5614 
5615  if ( count( $keysDelete ) ) {
5616  // Do the DELETE by PRIMARY KEY for prior rows.
5617  // In the past a very large portion of calls to this function are for setting
5618  // 'rememberpassword' for new accounts (a preference that has since been removed).
5619  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5620  // caused gap locks on [max user ID,+infinity) which caused high contention since
5621  // updates would pile up on each other as they are for higher (newer) user IDs.
5622  // It might not be necessary these days, but it shouldn't hurt either.
5623  $dbw->delete( 'user_properties',
5624  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5625  }
5626  // Insert the new preference rows
5627  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5628  }
5629 
5636  public static function selectFields() {
5637  wfDeprecated( __METHOD__, '1.31' );
5638  return [
5639  'user_id',
5640  'user_name',
5641  'user_real_name',
5642  'user_email',
5643  'user_touched',
5644  'user_token',
5645  'user_email_authenticated',
5646  'user_email_token',
5647  'user_email_token_expires',
5648  'user_registration',
5649  'user_editcount',
5650  ];
5651  }
5652 
5662  public static function getQueryInfo() {
5664 
5665  $ret = [
5666  'tables' => [ 'user' ],
5667  'fields' => [
5668  'user_id',
5669  'user_name',
5670  'user_real_name',
5671  'user_email',
5672  'user_touched',
5673  'user_token',
5674  'user_email_authenticated',
5675  'user_email_token',
5676  'user_email_token_expires',
5677  'user_registration',
5678  'user_editcount',
5679  ],
5680  'joins' => [],
5681  ];
5682 
5683  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
5684  // but it does little harm and might be needed for write callers loading a User.
5685  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
5686  $ret['tables']['user_actor'] = 'actor';
5687  $ret['fields'][] = 'user_actor.actor_id';
5688  $ret['joins']['user_actor'] = [
5689  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
5690  [ 'user_actor.actor_user = user_id' ]
5691  ];
5692  }
5693 
5694  return $ret;
5695  }
5696 
5704  static function newFatalPermissionDeniedStatus( $permission ) {
5705  global $wgLang;
5706 
5707  $groups = [];
5708  foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5709  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5710  }
5711 
5712  if ( $groups ) {
5713  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5714  } else {
5715  return Status::newFatal( 'badaccess-group0' );
5716  }
5717  }
5718 
5728  public function getInstanceForUpdate() {
5729  if ( !$this->getId() ) {
5730  return null; // anon
5731  }
5732 
5733  $user = self::newFromId( $this->getId() );
5734  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5735  return null;
5736  }
5737 
5738  return $user;
5739  }
5740 
5748  public function equals( UserIdentity $user ) {
5749  // XXX it's not clear whether central ID providers are supposed to obey this
5750  return $this->getName() === $user->getName();
5751  }
5752 }
getEmail()
Get the user&#39;s e-mail address.
Definition: User.php:3081
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
static randomPassword()
Return a random password.
Definition: User.php:1294
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
isHidden()
Check if user account is hidden.
Definition: User.php:2441
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition: User.php:1692
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition: User.php:5193
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:2937
string $mBlockedby
Definition: User.php:265
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:138
const VERSION
int Serialized record version.
Definition: User.php:61
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
static getMainWANInstance()
Get the main WAN cache object.
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2625
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:3185
$wgMaxArticleSize
Maximum article size in kilobytes.
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1356
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2636
isAllowedAll()
Definition: User.php:3871
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1748
string $mDatePreference
Definition: User.php:263
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4599
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:884
static newFromID( $id)
Load a blocked user from their block id.
Definition: Block.php:196
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:101
either a plain
Definition: hooks.txt:2056
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3964
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:4059
$property
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1392
$wgMaxNameChars
Maximum number of bytes in username.
UserGroupMembership [] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:229
const TYPE_RANGE
Definition: Block.php:95
static getGroupName( $group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:5126
$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:1367
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:5257
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4571
$success
Block $mBlockedFromCreateAccount
Definition: User.php:297
logout()
Log this user out.
Definition: User.php:4170
clearNotification(&$title, $oldid=0)
Clear the user&#39;s notification timestamp for the given title.
Definition: User.php:3998
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:5006
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:5181
saveSettings()
Save this user&#39;s settings into the database.
Definition: User.php:4218
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:2476
prevents( $action, $x=null)
Get/set whether the Block prevents a given action.
Definition: Block.php:1152
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:4978
static $mCoreRights
Array of Strings Core rights.
Definition: User.php:112
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:1995
static generateRandomPasswordString( $minLength=10)
Generate a random string suitable for a password.
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:5076
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:966
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:2868
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:67
initEditCountInternal()
Initialize user_editcount from data out of the revision table.
Definition: User.php:5396
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3856
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4551
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3646
const TOKEN_LENGTH
int Number of characters in user_token field.
Definition: User.php:51
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2853
Handles increment the edit count for a given set of users.
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
static getWikiIdFromDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:254
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2377
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:1272
setName( $str)
Set the user name.
Definition: User.php:2512
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1276
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3200
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2346
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:215
$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:297
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:4095
__toString()
Definition: User.php:322
static $idCacheByName
Definition: User.php:302
Exception thrown when an actor can&#39;t be created.
null for the local wiki Added in
Definition: hooks.txt:1598
getRealName()
Get the user&#39;s real name.
Definition: User.php:3173
$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:2694
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:4766
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:1710
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5330
clearAllNotifications()
Resets all of the given user&#39;s page-change notification timestamps.
Definition: User.php:4066
static getCurrentWikiDomain()
Definition: WikiMap.php:276
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:287
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4715
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3823
static clearCookie(WebResponse $response)
Unset the &#39;BlockID&#39; cookie.
Definition: Block.php:1651
const TYPE_IP
Definition: Block.php:94
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:3053
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:904
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:725
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:4562
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4644
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
isBlocked( $bFromReplica=true)
Check if user is blocked.
Definition: User.php:2277
$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:5662
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:946
array $mFormerGroups
Definition: User.php:277
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2902
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:623
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3287
static getGroupMember( $group, $username='#')
Get the localized descriptive name for a member of a group, if it exists.
Definition: User.php:5139
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2391
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1997
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1812
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Value object representing a logged-out user&#39;s edit token.
const DB_MASTER
Definition: defines.php:26
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition: IP.php:742
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user&#39;s session (e.g.
Definition: User.php:4134
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2485
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5636
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4479
string $mName
Cache variables.
Definition: User.php:204
string $mRegistration
Cache variables.
Definition: User.php:225
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
$wgDnsBlacklistUrls
List of DNS blacklists to use, if $wgEnableDnsBlacklist is true.
int $mEditCount
Cache variables.
Definition: User.php:227
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2988
int null $mActorId
Cache variables.
Definition: User.php:206
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3981
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:1993
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4609
getTitleKey()
Get the user&#39;s name escaped by underscores.
Definition: User.php:2590
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:5150
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3370
$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:1076
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:2124
Interface for objects representing user identity.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
string $mEmailTokenExpires
Cache variables.
Definition: User.php:223
static getAllRights()
Get a list of all available permissions.
Definition: User.php:5162
isEmailConfirmed()
Is this user&#39;s e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4923
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:1995
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:4685
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1238
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4529
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:457
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:894
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:238
makeGlobalKey( $class, $component=null)
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2709
string $mEmailToken
Cache variables.
Definition: User.php:221
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:3308
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:1640
$wgPasswordPolicy
Password policy for the wiki.
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4964
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects...
Definition: User.php:3633
$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:909
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5431
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:1116
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4810
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:510
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:5482
$wgExperiencedUserEdits
Name of the external diff engine to use.
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1146
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:5033
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:152
getBoolOption( $oname)
Get the user&#39;s current setting for a given option, as a boolean value.
Definition: User.php:3259
Block $mBlock
Definition: User.php:291
string $mBlockreason
Definition: User.php:271
isAnon()
Get whether the user is anonymous.
Definition: User.php:3831
static getGroupMemberName( $group, $username)
Gets the localized name for a member of a group, if it exists.
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4837
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:2064
Stores a single person&#39;s name and email address.
Definition: MailAddress.php:32
static purge( $wikiId, $userId)
Definition: User.php:487
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:286
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5444
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3336
static getGroupName( $group)
Gets the localized friendly name for a group, if it exists.
$res
Definition: database.txt:21
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:300
$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:4661
doLogout()
Clear the user&#39;s session, and reset the instance cache.
Definition: User.php:4182
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:357
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5748
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:522
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4949
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:54
incEditCount()
Schedule a deferred update to update the user&#39;s edit count.
Definition: User.php:5369
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2786
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:2018
$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:3393
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1436
$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:213
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4892
array $mEffectiveGroups
Definition: User.php:273
$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:2950
const IGNORE_USER_RIGHTS
Definition: User.php:77
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
$params
$wgProxyWhitelist
Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other methods mi...
string $mEmailAuthenticated
Cache variables.
Definition: User.php:219
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3886
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:1995
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:3950
WebRequest $mRequest
Definition: User.php:288
$wgRemoveGroups
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format...
Definition: MWCryptRand.php:74
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1346
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:1041
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:1402
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:935
requiresHTTPS()
Determine based on the wiki configuration and the user&#39;s options, whether this user must be over HTTP...
Definition: User.php:3543
$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:2299
array $mImplicitGroups
Definition: User.php:275
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:197
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1606
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition: User.php:1158
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they&#39;ve successfully logged in from...
Definition: User.php:4510
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
static hasFlags( $bitfield, $flags)
$wgClockSkewFudge
Clock skew or the one-second resolution of time() can occasionally cause cache problems when the user...
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
isBot()
Definition: User.php:3839
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4881
$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:1779
static getGroupPage( $group)
Gets the title of a page describing a particular user group.
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:5385
$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 probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt: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:651
clearSharedCache( $mode='changed')
Clear user data from memcached.
Definition: User.php:2807
string $mToken
Cache variables.
Definition: User.php:217
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:121
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:5056
getToken( $forceCreation=true)
Get the user&#39;s current token.
Definition: User.php:3015
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:963
static newInvalidPassword()
Create an InvalidPassword.
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3899
$wgPasswordSender
Sender email address for e-mail notifications.
$wgLearnerMemberSince
Name of the external diff engine to use.
loadDefaults( $name=false)
Set cached properties to default.
Definition: User.php:1307
array $mOptions
Definition: User.php:285
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1818
string [] $wgSoftBlockRanges
IP ranges that should be considered soft-blocked (anon-only, account creation allowed).
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:3073
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:261
invalidateEmail()
Invalidate the user&#39;s e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4867
loadGroups()
Load the groups from the database if they aren&#39;t already loaded.
Definition: User.php:1616
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5570
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:573
isIPRange()
Is the user an IP range?
Definition: User.php:977
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:545
$wgApplyIpBlocksToXff
Whether to look at the X-Forwarded-For header&#39;s list of (potentially spoofed) IPs and apply IP blocks...
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3562
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3933
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:211
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition: Block.php:1311
this hook is for auditing only $req
Definition: hooks.txt:989
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the &#39;BlockID&#39; cookie.
Definition: Block.php:1687
getNewtalk()
Check if the user has new messages.
Definition: User.php:2598
bool $mLocked
Definition: User.php:281
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:780
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:608
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1488
$wgUseEnotif
Definition: Setup.php:439
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:3620
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:675
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:315
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:3271
getBlockFromCookieValue( $blockCookieVal)
Try to load a Block from an ID given in a cookie value.
Definition: User.php:1946
idForName( $flags=0)
If only this user&#39;s username is known, and it exists, return the user ID.
Definition: User.php:4296
getOptions( $flags=0)
Get all user&#39;s options.
Definition: User.php:3228
The ContentHandler facility adds support for arbitrary content types on wiki pages
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
$wgMinimalPasswordLength
Specifies the minimal length of a user password.
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they&#39;ve successfully logged in from...
Definition: User.php:4497
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:310
getBlock( $bFromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2287
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:992
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3920
getId()
Get the user&#39;s ID.
Definition: User.php:2460
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4701
getEmailAuthenticationTimestamp()
Get the timestamp of the user&#39;s e-mail authentication.
Definition: User.php:3091
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:2135
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5493
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition: User.php:5208
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name. ...
Definition: User.php:5233
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4407
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:2523
Relational database abstraction object.
Definition: Database.php:48
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:771
bool $mAllowUsertalk
Definition: User.php:294
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2836
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:4850
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5704
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
bool $mHideName
Definition: User.php:283
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:749
getEditCount()
Get the user&#39;s edit count.
Definition: User.php:3722
getUserPage()
Get this user&#39;s personal page title.
Definition: User.php:4580
setNewtalk( $val, $curRev=null)
Update the &#39;You have new messages!&#39; status.
Definition: User.php:2754
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:5728
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3908
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:2625
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
const TYPE_USER
Definition: Block.php:93
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3523
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2364
array $mRights
Definition: User.php:269
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:668
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user&#39;s give...
Definition: User.php:4727
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2667
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:256
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:2424
$messages
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:746
string $mHash
Definition: User.php:267
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2734
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4909
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:4819
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1198
setEmailWithConfirmation( $str)
Set the user&#39;s e-mail address and a confirmation mail if needed.
Definition: User.php:3118
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4331
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3669
array $mOptionOverrides
Cache variables.
Definition: User.php:231
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:585
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1773
const INVALID_TOKEN
string An invalid value for user_token
Definition: User.php:56
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2924
string $mRealName
Cache variables.
Definition: User.php:208
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3757
isSafeToLoad()
Test if it&#39;s safe to load this User object.
Definition: User.php:340
getBlockedStatus( $bFromReplica=true)
Get blocking information.
Definition: User.php:1829
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:120
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:498
setEmail( $str)
Set the user&#39;s e-mail address.
Definition: User.php:3101
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:5470
getTalkPage()
Get this user&#39;s talk page title.
Definition: User.php:4589
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
Definition: Block.php:31
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:2110
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:2625
static singleton()
Definition: UserCache.php:34
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3793
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:85
const CHECK_USER_RIGHTS
Definition: User.php:72
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:1486
getRights()
Get the permissions this user has.
Definition: User.php:3577
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3698
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2355
$wgEnableDnsBlacklist
Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
Base class for the more common types of database errors.
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3478
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:812
int $mId
Cache variables.
Definition: User.php:202
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:4792
getTouched()
Get the user touched timestamp.
Definition: User.php:2880
Block $mGlobalBlock
Definition: User.php:279
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:243
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:280