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  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
500 
501  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
502  }
503 
510  $id = $this->getId();
511 
512  return $id ? [ $this->getCacheKey( $cache ) ] : [];
513  }
514 
521  protected function loadFromCache() {
522  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
523  $data = $cache->getWithSetCallback(
524  $this->getCacheKey( $cache ),
525  $cache::TTL_HOUR,
526  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
527  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
528  wfDebug( "User: cache miss for user {$this->mId}\n" );
529 
530  $this->loadFromDatabase( self::READ_NORMAL );
531  $this->loadGroups();
532  $this->loadOptions();
533 
534  $data = [];
535  foreach ( self::$mCacheVars as $name ) {
536  $data[$name] = $this->$name;
537  }
538 
539  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
540 
541  // if a user group membership is about to expire, the cache needs to
542  // expire at that time (T163691)
543  foreach ( $this->mGroupMemberships as $ugm ) {
544  if ( $ugm->getExpiry() ) {
545  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
546  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
547  $ttl = $secondsUntilExpiry;
548  }
549  }
550  }
551 
552  return $data;
553  },
554  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
555  );
556 
557  // Restore from cache
558  foreach ( self::$mCacheVars as $name ) {
559  $this->$name = $data[$name];
560  }
561 
562  return true;
563  }
564 
566  // @{
567 
584  public static function newFromName( $name, $validate = 'valid' ) {
585  if ( $validate === true ) {
586  $validate = 'valid';
587  }
588  $name = self::getCanonicalName( $name, $validate );
589  if ( $name === false ) {
590  return false;
591  }
592 
593  // Create unloaded user object
594  $u = new User;
595  $u->mName = $name;
596  $u->mFrom = 'name';
597  $u->setItemLoaded( 'name' );
598 
599  return $u;
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  preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name )
1131  ) {
1132  wfDebugLog( 'username', __METHOD__ .
1133  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1134  return false;
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  }
1165 
1166  $messages = [];
1167  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1168  $messages[] = $error['message'];
1169  }
1170  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1171  $messages[] = $warning['message'];
1172  }
1173  if ( count( $messages ) === 1 ) {
1174  return $messages[0];
1175  }
1176 
1177  return $messages;
1178  }
1179 
1201  public function checkPasswordValidity( $password ) {
1202  global $wgPasswordPolicy;
1203 
1204  $upp = new UserPasswordPolicy(
1205  $wgPasswordPolicy['policies'],
1206  $wgPasswordPolicy['checks']
1207  );
1208 
1209  $status = Status::newGood( [] );
1210  $result = false; // init $result to false for the internal checks
1211 
1212  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1213  $status->error( $result );
1214  return $status;
1215  }
1216 
1217  if ( $result === false ) {
1218  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1219  return $status;
1220  }
1221 
1222  if ( $result === true ) {
1223  return $status;
1224  }
1225 
1226  $status->error( $result );
1227  return $status; // the isValidPassword hook set a string $result and returned true
1228  }
1229 
1243  public static function getCanonicalName( $name, $validate = 'valid' ) {
1244  // Force usernames to capital
1245  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1246 
1247  # Reject names containing '#'; these will be cleaned up
1248  # with title normalisation, but then it's too late to
1249  # check elsewhere
1250  if ( strpos( $name, '#' ) !== false ) {
1251  return false;
1252  }
1253 
1254  // Clean up name according to title rules,
1255  // but only when validation is requested (T14654)
1256  $t = ( $validate !== false ) ?
1258  // Check for invalid titles
1259  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1260  return false;
1261  }
1262 
1263  $name = $t->getText();
1264 
1265  switch ( $validate ) {
1266  case false:
1267  break;
1268  case 'valid':
1269  if ( !self::isValidUserName( $name ) ) {
1270  $name = false;
1271  }
1272  break;
1273  case 'usable':
1274  if ( !self::isUsableName( $name ) ) {
1275  $name = false;
1276  }
1277  break;
1278  case 'creatable':
1279  if ( !self::isCreatableName( $name ) ) {
1280  $name = false;
1281  }
1282  break;
1283  default:
1284  throw new InvalidArgumentException(
1285  'Invalid parameter value for $validate in ' . __METHOD__ );
1286  }
1287  return $name;
1288  }
1289 
1296  public static function randomPassword() {
1297  global $wgMinimalPasswordLength;
1298  return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1299  }
1300 
1309  public function loadDefaults( $name = false ) {
1310  $this->mId = 0;
1311  $this->mName = $name;
1312  $this->mActorId = null;
1313  $this->mRealName = '';
1314  $this->mEmail = '';
1315  $this->mOptionOverrides = null;
1316  $this->mOptionsLoaded = false;
1317 
1318  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1319  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1320  if ( $loggedOut !== 0 ) {
1321  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1322  } else {
1323  $this->mTouched = '1'; # Allow any pages to be cached
1324  }
1325 
1326  $this->mToken = null; // Don't run cryptographic functions till we need a token
1327  $this->mEmailAuthenticated = null;
1328  $this->mEmailToken = '';
1329  $this->mEmailTokenExpires = null;
1330  $this->mRegistration = wfTimestamp( TS_MW );
1331  $this->mGroupMemberships = [];
1332 
1333  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1334  }
1335 
1348  public function isItemLoaded( $item, $all = 'all' ) {
1349  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1350  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1351  }
1352 
1358  protected function setItemLoaded( $item ) {
1359  if ( is_array( $this->mLoadedItems ) ) {
1360  $this->mLoadedItems[$item] = true;
1361  }
1362  }
1363 
1369  private function loadFromSession() {
1370  // Deprecated hook
1371  $result = null;
1372  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1373  if ( $result !== null ) {
1374  return $result;
1375  }
1376 
1377  // MediaWiki\Session\Session already did the necessary authentication of the user
1378  // returned here, so just use it if applicable.
1379  $session = $this->getRequest()->getSession();
1380  $user = $session->getUser();
1381  if ( $user->isLoggedIn() ) {
1382  $this->loadFromUserObject( $user );
1383  if ( $user->isBlocked() ) {
1384  // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1385  // every session load, because an autoblocked editor might not edit again from the same
1386  // IP address after being blocked.
1387  $this->trackBlockWithCookie();
1388  }
1389 
1390  // Other code expects these to be set in the session, so set them.
1391  $session->set( 'wsUserID', $this->getId() );
1392  $session->set( 'wsUserName', $this->getName() );
1393  $session->set( 'wsToken', $this->getToken() );
1394 
1395  return true;
1396  }
1397 
1398  return false;
1399  }
1400 
1404  public function trackBlockWithCookie() {
1405  $block = $this->getBlock();
1406  if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null ) {
1407  $config = RequestContext::getMain()->getConfig();
1408  $shouldSetCookie = false;
1409 
1410  if ( $this->isAnon() && $config->get( 'CookieSetOnIpBlock' ) ) {
1411  // If user is logged-out, set a cookie to track the Block
1412  $shouldSetCookie = in_array( $block->getType(), [
1414  ] );
1415  if ( $shouldSetCookie ) {
1416  $block->setCookie( $this->getRequest()->response() );
1417  }
1418  } elseif ( $this->isLoggedIn() && $config->get( 'CookieSetOnAutoblock' ) ) {
1419  $shouldSetCookie = $block->getType() === Block::TYPE_USER && $block->isAutoblocking();
1420  if ( $shouldSetCookie ) {
1421  $block->setCookie( $this->getRequest()->response() );
1422  }
1423  }
1424  }
1425  }
1426 
1434  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1435  // Paranoia
1436  $this->mId = intval( $this->mId );
1437 
1438  if ( !$this->mId ) {
1439  // Anonymous users are not in the database
1440  $this->loadDefaults();
1441  return false;
1442  }
1443 
1444  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1445  $db = wfGetDB( $index );
1446 
1447  $userQuery = self::getQueryInfo();
1448  $s = $db->selectRow(
1449  $userQuery['tables'],
1450  $userQuery['fields'],
1451  [ 'user_id' => $this->mId ],
1452  __METHOD__,
1453  $options,
1454  $userQuery['joins']
1455  );
1456 
1457  $this->queryFlagsUsed = $flags;
1458  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1459 
1460  if ( $s !== false ) {
1461  // Initialise user table data
1462  $this->loadFromRow( $s );
1463  $this->mGroupMemberships = null; // deferred
1464  $this->getEditCount(); // revalidation for nulls
1465  return true;
1466  }
1467 
1468  // Invalid user_id
1469  $this->mId = 0;
1470  $this->loadDefaults();
1471 
1472  return false;
1473  }
1474 
1487  protected function loadFromRow( $row, $data = null ) {
1489 
1490  if ( !is_object( $row ) ) {
1491  throw new InvalidArgumentException( '$row must be an object' );
1492  }
1493 
1494  $all = true;
1495 
1496  $this->mGroupMemberships = null; // deferred
1497 
1498  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
1499  // but it does little harm and might be needed for write callers loading a User.
1500  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
1501  if ( isset( $row->actor_id ) ) {
1502  $this->mActorId = (int)$row->actor_id;
1503  if ( $this->mActorId !== 0 ) {
1504  $this->mFrom = 'actor';
1505  }
1506  $this->setItemLoaded( 'actor' );
1507  } else {
1508  $all = false;
1509  }
1510  }
1511 
1512  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1513  $this->mName = $row->user_name;
1514  $this->mFrom = 'name';
1515  $this->setItemLoaded( 'name' );
1516  } else {
1517  $all = false;
1518  }
1519 
1520  if ( isset( $row->user_real_name ) ) {
1521  $this->mRealName = $row->user_real_name;
1522  $this->setItemLoaded( 'realname' );
1523  } else {
1524  $all = false;
1525  }
1526 
1527  if ( isset( $row->user_id ) ) {
1528  $this->mId = intval( $row->user_id );
1529  if ( $this->mId !== 0 ) {
1530  $this->mFrom = 'id';
1531  }
1532  $this->setItemLoaded( 'id' );
1533  } else {
1534  $all = false;
1535  }
1536 
1537  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1538  self::$idCacheByName[$row->user_name] = $row->user_id;
1539  }
1540 
1541  if ( isset( $row->user_editcount ) ) {
1542  $this->mEditCount = $row->user_editcount;
1543  } else {
1544  $all = false;
1545  }
1546 
1547  if ( isset( $row->user_touched ) ) {
1548  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1549  } else {
1550  $all = false;
1551  }
1552 
1553  if ( isset( $row->user_token ) ) {
1554  // The definition for the column is binary(32), so trim the NULs
1555  // that appends. The previous definition was char(32), so trim
1556  // spaces too.
1557  $this->mToken = rtrim( $row->user_token, " \0" );
1558  if ( $this->mToken === '' ) {
1559  $this->mToken = null;
1560  }
1561  } else {
1562  $all = false;
1563  }
1564 
1565  if ( isset( $row->user_email ) ) {
1566  $this->mEmail = $row->user_email;
1567  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1568  $this->mEmailToken = $row->user_email_token;
1569  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1570  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1571  } else {
1572  $all = false;
1573  }
1574 
1575  if ( $all ) {
1576  $this->mLoadedItems = true;
1577  }
1578 
1579  if ( is_array( $data ) ) {
1580  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1581  if ( $data['user_groups'] === [] ) {
1582  $this->mGroupMemberships = [];
1583  } else {
1584  $firstGroup = reset( $data['user_groups'] );
1585  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1586  $this->mGroupMemberships = [];
1587  foreach ( $data['user_groups'] as $row ) {
1588  $ugm = UserGroupMembership::newFromRow( (object)$row );
1589  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1590  }
1591  }
1592  }
1593  }
1594  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1595  $this->loadOptions( $data['user_properties'] );
1596  }
1597  }
1598  }
1599 
1605  protected function loadFromUserObject( $user ) {
1606  $user->load();
1607  foreach ( self::$mCacheVars as $var ) {
1608  $this->$var = $user->$var;
1609  }
1610  }
1611 
1615  private function loadGroups() {
1616  if ( is_null( $this->mGroupMemberships ) ) {
1617  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1618  ? wfGetDB( DB_MASTER )
1619  : wfGetDB( DB_REPLICA );
1620  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1621  $this->mId, $db );
1622  }
1623  }
1624 
1639  public function addAutopromoteOnceGroups( $event ) {
1641 
1642  if ( wfReadOnly() || !$this->getId() ) {
1643  return [];
1644  }
1645 
1646  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1647  if ( $toPromote === [] ) {
1648  return [];
1649  }
1650 
1651  if ( !$this->checkAndSetTouched() ) {
1652  return []; // raced out (bug T48834)
1653  }
1654 
1655  $oldGroups = $this->getGroups(); // previous groups
1656  $oldUGMs = $this->getGroupMemberships();
1657  foreach ( $toPromote as $group ) {
1658  $this->addGroup( $group );
1659  }
1660  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1661  $newUGMs = $this->getGroupMemberships();
1662 
1663  // update groups in external authentication database
1664  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1665 
1666  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1667  $logEntry->setPerformer( $this );
1668  $logEntry->setTarget( $this->getUserPage() );
1669  $logEntry->setParameters( [
1670  '4::oldgroups' => $oldGroups,
1671  '5::newgroups' => $newGroups,
1672  ] );
1673  $logid = $logEntry->insert();
1674  if ( $wgAutopromoteOnceLogInRC ) {
1675  $logEntry->publish( $logid );
1676  }
1677 
1678  return $toPromote;
1679  }
1680 
1690  protected function makeUpdateConditions( Database $db, array $conditions ) {
1691  if ( $this->mTouched ) {
1692  // CAS check: only update if the row wasn't changed sicne it was loaded.
1693  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1694  }
1695 
1696  return $conditions;
1697  }
1698 
1708  protected function checkAndSetTouched() {
1709  $this->load();
1710 
1711  if ( !$this->mId ) {
1712  return false; // anon
1713  }
1714 
1715  // Get a new user_touched that is higher than the old one
1716  $newTouched = $this->newTouchedTimestamp();
1717 
1718  $dbw = wfGetDB( DB_MASTER );
1719  $dbw->update( 'user',
1720  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1721  $this->makeUpdateConditions( $dbw, [
1722  'user_id' => $this->mId,
1723  ] ),
1724  __METHOD__
1725  );
1726  $success = ( $dbw->affectedRows() > 0 );
1727 
1728  if ( $success ) {
1729  $this->mTouched = $newTouched;
1730  $this->clearSharedCache();
1731  } else {
1732  // Clears on failure too since that is desired if the cache is stale
1733  $this->clearSharedCache( 'refresh' );
1734  }
1735 
1736  return $success;
1737  }
1738 
1746  public function clearInstanceCache( $reloadFrom = false ) {
1747  $this->mNewtalk = -1;
1748  $this->mDatePreference = null;
1749  $this->mBlockedby = -1; # Unset
1750  $this->mHash = false;
1751  $this->mRights = null;
1752  $this->mEffectiveGroups = null;
1753  $this->mImplicitGroups = null;
1754  $this->mGroupMemberships = null;
1755  $this->mOptions = null;
1756  $this->mOptionsLoaded = false;
1757  $this->mEditCount = null;
1758 
1759  if ( $reloadFrom ) {
1760  $this->mLoadedItems = [];
1761  $this->mFrom = $reloadFrom;
1762  }
1763  }
1764 
1771  public static function getDefaultOptions() {
1773 
1774  static $defOpt = null;
1775  static $defOptLang = null;
1776 
1777  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1778  if ( $defOpt !== null && $defOptLang === $contLang->getCode() ) {
1779  // The content language does not change (and should not change) mid-request, but the
1780  // unit tests change it anyway, and expect this method to return values relevant to the
1781  // current content language.
1782  return $defOpt;
1783  }
1784 
1785  $defOpt = $wgDefaultUserOptions;
1786  // Default language setting
1787  $defOptLang = $contLang->getCode();
1788  $defOpt['language'] = $defOptLang;
1789  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1790  if ( $langCode === $contLang->getCode() ) {
1791  $defOpt['variant'] = $langCode;
1792  } else {
1793  $defOpt["variant-$langCode"] = $langCode;
1794  }
1795  }
1796 
1797  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1798  // since extensions may change the set of searchable namespaces depending
1799  // on user groups/permissions.
1800  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1801  $defOpt['searchNs' . $nsnum] = (bool)$val;
1802  }
1803  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1804 
1805  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1806 
1807  return $defOpt;
1808  }
1809 
1816  public static function getDefaultOption( $opt ) {
1817  $defOpts = self::getDefaultOptions();
1818  return $defOpts[$opt] ?? null;
1819  }
1820 
1827  private function getBlockedStatus( $bFromReplica = true ) {
1829 
1830  if ( $this->mBlockedby != -1 ) {
1831  return;
1832  }
1833 
1834  wfDebug( __METHOD__ . ": checking...\n" );
1835 
1836  // Initialize data...
1837  // Otherwise something ends up stomping on $this->mBlockedby when
1838  // things get lazy-loaded later, causing false positive block hits
1839  // due to -1 !== 0. Probably session-related... Nothing should be
1840  // overwriting mBlockedby, surely?
1841  $this->load();
1842 
1843  # We only need to worry about passing the IP address to the Block generator if the
1844  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1845  # know which IP address they're actually coming from
1846  $ip = null;
1847  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1848  $sessionUser = RequestContext::getMain()->getUser();
1849  // the session user is set up towards the end of Setup.php. Until then,
1850  // assume it's a logged-out user.
1851  $globalUserName = $sessionUser->isSafeToLoad()
1852  ? $sessionUser->getName()
1853  : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1854  if ( $this->getName() === $globalUserName ) {
1855  $ip = $this->getRequest()->getIP();
1856  }
1857  }
1858 
1859  // User/IP blocking
1860  $block = Block::newFromTarget( $this, $ip, !$bFromReplica );
1861 
1862  // Cookie blocking
1863  if ( !$block instanceof Block ) {
1864  $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
1865  }
1866 
1867  // Proxy blocking
1868  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1869  // Local list
1870  if ( self::isLocallyBlockedProxy( $ip ) ) {
1871  $block = new Block( [
1872  'byText' => wfMessage( 'proxyblocker' )->text(),
1873  'reason' => wfMessage( 'proxyblockreason' )->plain(),
1874  'address' => $ip,
1875  'systemBlock' => 'proxy',
1876  ] );
1877  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1878  $block = new Block( [
1879  'byText' => wfMessage( 'sorbs' )->text(),
1880  'reason' => wfMessage( 'sorbsreason' )->plain(),
1881  'address' => $ip,
1882  'systemBlock' => 'dnsbl',
1883  ] );
1884  }
1885  }
1886 
1887  // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
1888  if ( !$block instanceof Block
1889  && $wgApplyIpBlocksToXff
1890  && $ip !== null
1891  && !in_array( $ip, $wgProxyWhitelist )
1892  ) {
1893  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1894  $xff = array_map( 'trim', explode( ',', $xff ) );
1895  $xff = array_diff( $xff, [ $ip ] );
1896  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromReplica );
1897  $block = Block::chooseBlock( $xffblocks, $xff );
1898  if ( $block instanceof Block ) {
1899  # Mangle the reason to alert the user that the block
1900  # originated from matching the X-Forwarded-For header.
1901  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->plain();
1902  }
1903  }
1904 
1905  if ( !$block instanceof Block
1906  && $ip !== null
1907  && $this->isAnon()
1908  && IP::isInRanges( $ip, $wgSoftBlockRanges )
1909  ) {
1910  $block = new Block( [
1911  'address' => $ip,
1912  'byText' => 'MediaWiki default',
1913  'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
1914  'anonOnly' => true,
1915  'systemBlock' => 'wgSoftBlockRanges',
1916  ] );
1917  }
1918 
1919  if ( $block instanceof Block ) {
1920  wfDebug( __METHOD__ . ": Found block.\n" );
1921  $this->mBlock = $block;
1922  $this->mBlockedby = $block->getByName();
1923  $this->mBlockreason = $block->mReason;
1924  $this->mHideName = $block->mHideName;
1925  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1926  } else {
1927  $this->mBlock = null;
1928  $this->mBlockedby = '';
1929  $this->mBlockreason = '';
1930  $this->mHideName = 0;
1931  $this->mAllowUsertalk = false;
1932  }
1933 
1934  // Avoid PHP 7.1 warning of passing $this by reference
1935  $thisUser = $this;
1936  // Extensions
1937  Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
1938  }
1939 
1945  protected function getBlockFromCookieValue( $blockCookieVal ) {
1946  // Make sure there's something to check. The cookie value must start with a number.
1947  if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
1948  return false;
1949  }
1950  // Load the Block from the ID in the cookie.
1951  $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
1952  if ( $blockCookieId !== null ) {
1953  // An ID was found in the cookie.
1954  $tmpBlock = Block::newFromID( $blockCookieId );
1955  if ( $tmpBlock instanceof Block ) {
1956  $config = RequestContext::getMain()->getConfig();
1957 
1958  switch ( $tmpBlock->getType() ) {
1959  case Block::TYPE_USER:
1960  $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
1961  $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
1962  break;
1963  case Block::TYPE_IP:
1964  case Block::TYPE_RANGE:
1965  // If block is type IP or IP range, load only if user is not logged in (T152462)
1966  $blockIsValid = !$tmpBlock->isExpired() && !$this->isLoggedIn();
1967  $useBlockCookie = ( $config->get( 'CookieSetOnIpBlock' ) === true );
1968  break;
1969  default:
1970  $blockIsValid = false;
1971  $useBlockCookie = false;
1972  }
1973 
1974  if ( $blockIsValid && $useBlockCookie ) {
1975  // Use the block.
1976  return $tmpBlock;
1977  }
1978 
1979  // If the block is not valid, remove the cookie.
1980  Block::clearCookie( $this->getRequest()->response() );
1981  } else {
1982  // If the block doesn't exist, remove the cookie.
1983  Block::clearCookie( $this->getRequest()->response() );
1984  }
1985  }
1986  return false;
1987  }
1988 
1996  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1998 
1999  if ( !$wgEnableDnsBlacklist ||
2000  ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) )
2001  ) {
2002  return false;
2003  }
2004 
2005  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
2006  }
2007 
2015  public function inDnsBlacklist( $ip, $bases ) {
2016  $found = false;
2017  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
2018  if ( IP::isIPv4( $ip ) ) {
2019  // Reverse IP, T23255
2020  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
2021 
2022  foreach ( (array)$bases as $base ) {
2023  // Make hostname
2024  // If we have an access key, use that too (ProjectHoneypot, etc.)
2025  $basename = $base;
2026  if ( is_array( $base ) ) {
2027  if ( count( $base ) >= 2 ) {
2028  // Access key is 1, base URL is 0
2029  $host = "{$base[1]}.$ipReversed.{$base[0]}";
2030  } else {
2031  $host = "$ipReversed.{$base[0]}";
2032  }
2033  $basename = $base[0];
2034  } else {
2035  $host = "$ipReversed.$base";
2036  }
2037 
2038  // Send query
2039  $ipList = gethostbynamel( $host );
2040 
2041  if ( $ipList ) {
2042  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
2043  $found = true;
2044  break;
2045  }
2046 
2047  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
2048  }
2049  }
2050 
2051  return $found;
2052  }
2053 
2061  public static function isLocallyBlockedProxy( $ip ) {
2062  global $wgProxyList;
2063 
2064  if ( !$wgProxyList ) {
2065  return false;
2066  }
2067 
2068  if ( !is_array( $wgProxyList ) ) {
2069  // Load values from the specified file
2070  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
2071  }
2072 
2073  $resultProxyList = [];
2074  $deprecatedIPEntries = [];
2075 
2076  // backward compatibility: move all ip addresses in keys to values
2077  foreach ( $wgProxyList as $key => $value ) {
2078  $keyIsIP = IP::isIPAddress( $key );
2079  $valueIsIP = IP::isIPAddress( $value );
2080  if ( $keyIsIP && !$valueIsIP ) {
2081  $deprecatedIPEntries[] = $key;
2082  $resultProxyList[] = $key;
2083  } elseif ( $keyIsIP && $valueIsIP ) {
2084  $deprecatedIPEntries[] = $key;
2085  $resultProxyList[] = $key;
2086  $resultProxyList[] = $value;
2087  } else {
2088  $resultProxyList[] = $value;
2089  }
2090  }
2091 
2092  if ( $deprecatedIPEntries ) {
2093  wfDeprecated(
2094  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
2095  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
2096  }
2097 
2098  $proxyListIPSet = new IPSet( $resultProxyList );
2099  return $proxyListIPSet->match( $ip );
2100  }
2101 
2107  public function isPingLimitable() {
2108  global $wgRateLimitsExcludedIPs;
2109  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
2110  // No other good way currently to disable rate limits
2111  // for specific IPs. :P
2112  // But this is a crappy hack and should die.
2113  return false;
2114  }
2115  return !$this->isAllowed( 'noratelimit' );
2116  }
2117 
2132  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
2133  // Avoid PHP 7.1 warning of passing $this by reference
2134  $user = $this;
2135  // Call the 'PingLimiter' hook
2136  $result = false;
2137  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
2138  return $result;
2139  }
2140 
2141  global $wgRateLimits;
2142  if ( !isset( $wgRateLimits[$action] ) ) {
2143  return false;
2144  }
2145 
2146  $limits = array_merge(
2147  [ '&can-bypass' => true ],
2148  $wgRateLimits[$action]
2149  );
2150 
2151  // Some groups shouldn't trigger the ping limiter, ever
2152  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
2153  return false;
2154  }
2155 
2156  $keys = [];
2157  $id = $this->getId();
2158  $userLimit = false;
2159  $isNewbie = $this->isNewbie();
2161 
2162  if ( $id == 0 ) {
2163  // limits for anons
2164  if ( isset( $limits['anon'] ) ) {
2165  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
2166  }
2167  } elseif ( isset( $limits['user'] ) ) {
2168  // limits for logged-in users
2169  $userLimit = $limits['user'];
2170  }
2171 
2172  // limits for anons and for newbie logged-in users
2173  if ( $isNewbie ) {
2174  // ip-based limits
2175  if ( isset( $limits['ip'] ) ) {
2176  $ip = $this->getRequest()->getIP();
2177  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
2178  }
2179  // subnet-based limits
2180  if ( isset( $limits['subnet'] ) ) {
2181  $ip = $this->getRequest()->getIP();
2182  $subnet = IP::getSubnet( $ip );
2183  if ( $subnet !== false ) {
2184  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
2185  }
2186  }
2187  }
2188 
2189  // Check for group-specific permissions
2190  // If more than one group applies, use the group with the highest limit ratio (max/period)
2191  foreach ( $this->getGroups() as $group ) {
2192  if ( isset( $limits[$group] ) ) {
2193  if ( $userLimit === false
2194  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2195  ) {
2196  $userLimit = $limits[$group];
2197  }
2198  }
2199  }
2200 
2201  // limits for newbie logged-in users (override all the normal user limits)
2202  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2203  $userLimit = $limits['newbie'];
2204  }
2205 
2206  // Set the user limit key
2207  if ( $userLimit !== false ) {
2208  list( $max, $period ) = $userLimit;
2209  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2210  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2211  }
2212 
2213  // ip-based limits for all ping-limitable users
2214  if ( isset( $limits['ip-all'] ) ) {
2215  $ip = $this->getRequest()->getIP();
2216  // ignore if user limit is more permissive
2217  if ( $isNewbie || $userLimit === false
2218  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2219  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2220  }
2221  }
2222 
2223  // subnet-based limits for all ping-limitable users
2224  if ( isset( $limits['subnet-all'] ) ) {
2225  $ip = $this->getRequest()->getIP();
2226  $subnet = IP::getSubnet( $ip );
2227  if ( $subnet !== false ) {
2228  // ignore if user limit is more permissive
2229  if ( $isNewbie || $userLimit === false
2230  || $limits['ip-all'][0] / $limits['ip-all'][1]
2231  > $userLimit[0] / $userLimit[1] ) {
2232  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2233  }
2234  }
2235  }
2236 
2237  $triggered = false;
2238  foreach ( $keys as $key => $limit ) {
2239  list( $max, $period ) = $limit;
2240  $summary = "(limit $max in {$period}s)";
2241  $count = $cache->get( $key );
2242  // Already pinged?
2243  if ( $count ) {
2244  if ( $count >= $max ) {
2245  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2246  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2247  $triggered = true;
2248  } else {
2249  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2250  }
2251  } else {
2252  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2253  if ( $incrBy > 0 ) {
2254  $cache->add( $key, 0, intval( $period ) ); // first ping
2255  }
2256  }
2257  if ( $incrBy > 0 ) {
2258  $cache->incr( $key, $incrBy );
2259  }
2260  }
2261 
2262  return $triggered;
2263  }
2264 
2272  public function isBlocked( $bFromReplica = true ) {
2273  return $this->getBlock( $bFromReplica ) instanceof Block &&
2274  $this->getBlock()->appliesToRight( 'edit' );
2275  }
2276 
2283  public function getBlock( $bFromReplica = true ) {
2284  $this->getBlockedStatus( $bFromReplica );
2285  return $this->mBlock instanceof Block ? $this->mBlock : null;
2286  }
2287 
2295  public function isBlockedFrom( $title, $fromReplica = false ) {
2296  $blocked = $this->isHidden();
2297 
2298  if ( !$blocked ) {
2299  $block = $this->getBlock( $fromReplica );
2300  if ( $block ) {
2301  // Special handling for a user's own talk page. The block is not aware
2302  // of the user, so this must be done here.
2303  if ( $title->equals( $this->getTalkPage() ) ) {
2304  $blocked = $block->appliesToUsertalk( $title );
2305  } else {
2306  $blocked = $block->appliesToTitle( $title );
2307  }
2308  }
2309  }
2310 
2311  // only for the purpose of the hook. We really don't need this here.
2312  $allowUsertalk = $this->mAllowUsertalk;
2313 
2314  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
2315 
2316  return $blocked;
2317  }
2318 
2323  public function blockedBy() {
2324  $this->getBlockedStatus();
2325  return $this->mBlockedby;
2326  }
2327 
2332  public function blockedFor() {
2333  $this->getBlockedStatus();
2334  return $this->mBlockreason;
2335  }
2336 
2341  public function getBlockId() {
2342  $this->getBlockedStatus();
2343  return ( $this->mBlock ? $this->mBlock->getId() : false );
2344  }
2345 
2354  public function isBlockedGlobally( $ip = '' ) {
2355  return $this->getGlobalBlock( $ip ) instanceof Block;
2356  }
2357 
2368  public function getGlobalBlock( $ip = '' ) {
2369  if ( $this->mGlobalBlock !== null ) {
2370  return $this->mGlobalBlock ?: null;
2371  }
2372  // User is already an IP?
2373  if ( IP::isIPAddress( $this->getName() ) ) {
2374  $ip = $this->getName();
2375  } elseif ( !$ip ) {
2376  $ip = $this->getRequest()->getIP();
2377  }
2378  // Avoid PHP 7.1 warning of passing $this by reference
2379  $user = $this;
2380  $blocked = false;
2381  $block = null;
2382  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2383 
2384  if ( $blocked && $block === null ) {
2385  // back-compat: UserIsBlockedGlobally didn't have $block param first
2386  $block = new Block( [
2387  'address' => $ip,
2388  'systemBlock' => 'global-block'
2389  ] );
2390  }
2391 
2392  $this->mGlobalBlock = $blocked ? $block : false;
2393  return $this->mGlobalBlock ?: null;
2394  }
2395 
2401  public function isLocked() {
2402  if ( $this->mLocked !== null ) {
2403  return $this->mLocked;
2404  }
2405  // Reset for hook
2406  $this->mLocked = false;
2407  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2408  return $this->mLocked;
2409  }
2410 
2416  public function isHidden() {
2417  if ( $this->mHideName !== null ) {
2418  return (bool)$this->mHideName;
2419  }
2420  $this->getBlockedStatus();
2421  if ( !$this->mHideName ) {
2422  // Reset for hook
2423  $this->mHideName = false;
2424  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2425  }
2426  return (bool)$this->mHideName;
2427  }
2428 
2433  public function getId() {
2434  if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2435  // Special case, we know the user is anonymous
2436  return 0;
2437  }
2438 
2439  if ( !$this->isItemLoaded( 'id' ) ) {
2440  // Don't load if this was initialized from an ID
2441  $this->load();
2442  }
2443 
2444  return (int)$this->mId;
2445  }
2446 
2451  public function setId( $v ) {
2452  $this->mId = $v;
2453  $this->clearInstanceCache( 'id' );
2454  }
2455 
2460  public function getName() {
2461  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2462  // Special case optimisation
2463  return $this->mName;
2464  }
2465 
2466  $this->load();
2467  if ( $this->mName === false ) {
2468  // Clean up IPs
2469  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2470  }
2471 
2472  return $this->mName;
2473  }
2474 
2488  public function setName( $str ) {
2489  $this->load();
2490  $this->mName = $str;
2491  }
2492 
2499  public function getActorId( IDatabase $dbw = null ) {
2501 
2502  // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
2503  // but it does little harm and might be needed for write callers loading a User.
2504  if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
2505  return 0;
2506  }
2507 
2508  if ( !$this->isItemLoaded( 'actor' ) ) {
2509  $this->load();
2510  }
2511 
2512  // Currently $this->mActorId might be null if $this was loaded from a
2513  // cache entry that was written when $wgActorTableSchemaMigrationStage
2514  // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
2515  // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
2516  // has been removed), that condition may be removed.
2517  if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2518  $q = [
2519  'actor_user' => $this->getId() ?: null,
2520  'actor_name' => (string)$this->getName(),
2521  ];
2522  if ( $dbw ) {
2523  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2524  throw new CannotCreateActorException(
2525  'Cannot create an actor for a usable name that is not an existing user'
2526  );
2527  }
2528  if ( $q['actor_name'] === '' ) {
2529  throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2530  }
2531  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2532  if ( $dbw->affectedRows() ) {
2533  $this->mActorId = (int)$dbw->insertId();
2534  } else {
2535  // Outdated cache?
2536  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2537  $this->mActorId = (int)$dbw->selectField(
2538  'actor',
2539  'actor_id',
2540  $q,
2541  __METHOD__,
2542  [ 'LOCK IN SHARE MODE' ]
2543  );
2544  if ( !$this->mActorId ) {
2545  throw new CannotCreateActorException(
2546  "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2547  );
2548  }
2549  }
2550  $this->invalidateCache();
2551  } else {
2552  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2553  $db = wfGetDB( $index );
2554  $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2555  }
2556  $this->setItemLoaded( 'actor' );
2557  }
2558 
2559  return (int)$this->mActorId;
2560  }
2561 
2566  public function getTitleKey() {
2567  return str_replace( ' ', '_', $this->getName() );
2568  }
2569 
2574  public function getNewtalk() {
2575  $this->load();
2576 
2577  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2578  if ( $this->mNewtalk === -1 ) {
2579  $this->mNewtalk = false; # reset talk page status
2580 
2581  // Check memcached separately for anons, who have no
2582  // entire User object stored in there.
2583  if ( !$this->mId ) {
2584  global $wgDisableAnonTalk;
2585  if ( $wgDisableAnonTalk ) {
2586  // Anon newtalk disabled by configuration.
2587  $this->mNewtalk = false;
2588  } else {
2589  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2590  }
2591  } else {
2592  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2593  }
2594  }
2595 
2596  return (bool)$this->mNewtalk;
2597  }
2598 
2612  public function getNewMessageLinks() {
2613  // Avoid PHP 7.1 warning of passing $this by reference
2614  $user = $this;
2615  $talks = [];
2616  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2617  return $talks;
2618  }
2619 
2620  if ( !$this->getNewtalk() ) {
2621  return [];
2622  }
2623  $utp = $this->getTalkPage();
2624  $dbr = wfGetDB( DB_REPLICA );
2625  // Get the "last viewed rev" timestamp from the oldest message notification
2626  $timestamp = $dbr->selectField( 'user_newtalk',
2627  'MIN(user_last_timestamp)',
2628  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2629  __METHOD__ );
2630  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2631  return [
2632  [
2634  'link' => $utp->getLocalURL(),
2635  'rev' => $rev
2636  ]
2637  ];
2638  }
2639 
2645  public function getNewMessageRevisionId() {
2646  $newMessageRevisionId = null;
2647  $newMessageLinks = $this->getNewMessageLinks();
2648 
2649  // Note: getNewMessageLinks() never returns more than a single link
2650  // and it is always for the same wiki, but we double-check here in
2651  // case that changes some time in the future.
2652  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2653  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2654  && $newMessageLinks[0]['rev']
2655  ) {
2657  $newMessageRevision = $newMessageLinks[0]['rev'];
2658  $newMessageRevisionId = $newMessageRevision->getId();
2659  }
2660 
2661  return $newMessageRevisionId;
2662  }
2663 
2672  protected function checkNewtalk( $field, $id ) {
2673  $dbr = wfGetDB( DB_REPLICA );
2674 
2675  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2676 
2677  return $ok !== false;
2678  }
2679 
2687  protected function updateNewtalk( $field, $id, $curRev = null ) {
2688  // Get timestamp of the talk page revision prior to the current one
2689  $prevRev = $curRev ? $curRev->getPrevious() : false;
2690  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2691  // Mark the user as having new messages since this revision
2692  $dbw = wfGetDB( DB_MASTER );
2693  $dbw->insert( 'user_newtalk',
2694  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2695  __METHOD__,
2696  'IGNORE' );
2697  if ( $dbw->affectedRows() ) {
2698  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2699  return true;
2700  }
2701 
2702  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2703  return false;
2704  }
2705 
2712  protected function deleteNewtalk( $field, $id ) {
2713  $dbw = wfGetDB( DB_MASTER );
2714  $dbw->delete( 'user_newtalk',
2715  [ $field => $id ],
2716  __METHOD__ );
2717  if ( $dbw->affectedRows() ) {
2718  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2719  return true;
2720  }
2721 
2722  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2723  return false;
2724  }
2725 
2732  public function setNewtalk( $val, $curRev = null ) {
2733  if ( wfReadOnly() ) {
2734  return;
2735  }
2736 
2737  $this->load();
2738  $this->mNewtalk = $val;
2739 
2740  if ( $this->isAnon() ) {
2741  $field = 'user_ip';
2742  $id = $this->getName();
2743  } else {
2744  $field = 'user_id';
2745  $id = $this->getId();
2746  }
2747 
2748  if ( $val ) {
2749  $changed = $this->updateNewtalk( $field, $id, $curRev );
2750  } else {
2751  $changed = $this->deleteNewtalk( $field, $id );
2752  }
2753 
2754  if ( $changed ) {
2755  $this->invalidateCache();
2756  }
2757  }
2758 
2765  private function newTouchedTimestamp() {
2766  $time = time();
2767  if ( $this->mTouched ) {
2768  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2769  }
2770 
2771  return wfTimestamp( TS_MW, $time );
2772  }
2773 
2784  public function clearSharedCache( $mode = 'changed' ) {
2785  if ( !$this->getId() ) {
2786  return;
2787  }
2788 
2789  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2790  $key = $this->getCacheKey( $cache );
2791  if ( $mode === 'refresh' ) {
2792  $cache->delete( $key, 1 );
2793  } else {
2794  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2795  if ( $lb->hasOrMadeRecentMasterChanges() ) {
2796  $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
2797  function () use ( $cache, $key ) {
2798  $cache->delete( $key );
2799  },
2800  __METHOD__
2801  );
2802  } else {
2803  $cache->delete( $key );
2804  }
2805  }
2806  }
2807 
2813  public function invalidateCache() {
2814  $this->touch();
2815  $this->clearSharedCache();
2816  }
2817 
2830  public function touch() {
2831  $id = $this->getId();
2832  if ( $id ) {
2833  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2834  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2835  $cache->touchCheckKey( $key );
2836  $this->mQuickTouched = null;
2837  }
2838  }
2839 
2845  public function validateCache( $timestamp ) {
2846  return ( $timestamp >= $this->getTouched() );
2847  }
2848 
2857  public function getTouched() {
2858  $this->load();
2859 
2860  if ( $this->mId ) {
2861  if ( $this->mQuickTouched === null ) {
2862  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2863  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2864 
2865  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2866  }
2867 
2868  return max( $this->mTouched, $this->mQuickTouched );
2869  }
2870 
2871  return $this->mTouched;
2872  }
2873 
2879  public function getDBTouched() {
2880  $this->load();
2881 
2882  return $this->mTouched;
2883  }
2884 
2901  public function setPassword( $str ) {
2902  wfDeprecated( __METHOD__, '1.27' );
2903  return $this->setPasswordInternal( $str );
2904  }
2905 
2914  public function setInternalPassword( $str ) {
2915  wfDeprecated( __METHOD__, '1.27' );
2916  $this->setPasswordInternal( $str );
2917  }
2918 
2927  private function setPasswordInternal( $str ) {
2928  $manager = AuthManager::singleton();
2929 
2930  // If the user doesn't exist yet, fail
2931  if ( !$manager->userExists( $this->getName() ) ) {
2932  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2933  }
2934 
2935  $status = $this->changeAuthenticationData( [
2936  'username' => $this->getName(),
2937  'password' => $str,
2938  'retype' => $str,
2939  ] );
2940  if ( !$status->isGood() ) {
2942  ->info( __METHOD__ . ': Password change rejected: '
2943  . $status->getWikiText( null, null, 'en' ) );
2944  return false;
2945  }
2946 
2947  $this->setOption( 'watchlisttoken', false );
2948  SessionManager::singleton()->invalidateSessionsForUser( $this );
2949 
2950  return true;
2951  }
2952 
2965  public function changeAuthenticationData( array $data ) {
2966  $manager = AuthManager::singleton();
2967  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2968  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2969 
2970  $status = Status::newGood( 'ignored' );
2971  foreach ( $reqs as $req ) {
2972  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2973  }
2974  if ( $status->getValue() === 'ignored' ) {
2975  $status->warning( 'authenticationdatachange-ignored' );
2976  }
2977 
2978  if ( $status->isGood() ) {
2979  foreach ( $reqs as $req ) {
2980  $manager->changeAuthenticationData( $req );
2981  }
2982  }
2983  return $status;
2984  }
2985 
2992  public function getToken( $forceCreation = true ) {
2994 
2995  $this->load();
2996  if ( !$this->mToken && $forceCreation ) {
2997  $this->setToken();
2998  }
2999 
3000  if ( !$this->mToken ) {
3001  // The user doesn't have a token, return null to indicate that.
3002  return null;
3003  }
3004 
3005  if ( $this->mToken === self::INVALID_TOKEN ) {
3006  // We return a random value here so existing token checks are very
3007  // likely to fail.
3008  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
3009  }
3010 
3011  if ( $wgAuthenticationTokenVersion === null ) {
3012  // $wgAuthenticationTokenVersion not in use, so return the raw secret
3013  return $this->mToken;
3014  }
3015 
3016  // $wgAuthenticationTokenVersion in use, so hmac it.
3017  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
3018 
3019  // The raw hash can be overly long. Shorten it up.
3020  $len = max( 32, self::TOKEN_LENGTH );
3021  if ( strlen( $ret ) < $len ) {
3022  // Should never happen, even md5 is 128 bits
3023  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
3024  }
3025 
3026  return substr( $ret, -$len );
3027  }
3028 
3035  public function setToken( $token = false ) {
3036  $this->load();
3037  if ( $this->mToken === self::INVALID_TOKEN ) {
3039  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
3040  } elseif ( !$token ) {
3041  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
3042  } else {
3043  $this->mToken = $token;
3044  }
3045  }
3046 
3055  public function setNewpassword( $str, $throttle = true ) {
3056  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
3057  }
3058 
3063  public function getEmail() {
3064  $this->load();
3065  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
3066  return $this->mEmail;
3067  }
3068 
3074  $this->load();
3075  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
3077  }
3078 
3083  public function setEmail( $str ) {
3084  $this->load();
3085  if ( $str == $this->mEmail ) {
3086  return;
3087  }
3088  $this->invalidateEmail();
3089  $this->mEmail = $str;
3090  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
3091  }
3092 
3100  public function setEmailWithConfirmation( $str ) {
3102 
3103  if ( !$wgEnableEmail ) {
3104  return Status::newFatal( 'emaildisabled' );
3105  }
3106 
3107  $oldaddr = $this->getEmail();
3108  if ( $str === $oldaddr ) {
3109  return Status::newGood( true );
3110  }
3111 
3112  $type = $oldaddr != '' ? 'changed' : 'set';
3113  $notificationResult = null;
3114 
3115  if ( $wgEmailAuthentication && $type === 'changed' ) {
3116  // Send the user an email notifying the user of the change in registered
3117  // email address on their previous email address
3118  $change = $str != '' ? 'changed' : 'removed';
3119  $notificationResult = $this->sendMail(
3120  wfMessage( 'notificationemail_subject_' . $change )->text(),
3121  wfMessage( 'notificationemail_body_' . $change,
3122  $this->getRequest()->getIP(),
3123  $this->getName(),
3124  $str )->text()
3125  );
3126  }
3127 
3128  $this->setEmail( $str );
3129 
3130  if ( $str !== '' && $wgEmailAuthentication ) {
3131  // Send a confirmation request to the new address if needed
3132  $result = $this->sendConfirmationMail( $type );
3133 
3134  if ( $notificationResult !== null ) {
3135  $result->merge( $notificationResult );
3136  }
3137 
3138  if ( $result->isGood() ) {
3139  // Say to the caller that a confirmation and notification mail has been sent
3140  $result->value = 'eauth';
3141  }
3142  } else {
3143  $result = Status::newGood( true );
3144  }
3145 
3146  return $result;
3147  }
3148 
3153  public function getRealName() {
3154  if ( !$this->isItemLoaded( 'realname' ) ) {
3155  $this->load();
3156  }
3157 
3158  return $this->mRealName;
3159  }
3160 
3165  public function setRealName( $str ) {
3166  $this->load();
3167  $this->mRealName = $str;
3168  }
3169 
3180  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3181  global $wgHiddenPrefs;
3182  $this->loadOptions();
3183 
3184  # We want 'disabled' preferences to always behave as the default value for
3185  # users, even if they have set the option explicitly in their settings (ie they
3186  # set it, and then it was disabled removing their ability to change it). But
3187  # we don't want to erase the preferences in the database in case the preference
3188  # is re-enabled again. So don't touch $mOptions, just override the returned value
3189  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3190  return self::getDefaultOption( $oname );
3191  }
3192 
3193  if ( array_key_exists( $oname, $this->mOptions ) ) {
3194  return $this->mOptions[$oname];
3195  }
3196 
3197  return $defaultOverride;
3198  }
3199 
3208  public function getOptions( $flags = 0 ) {
3209  global $wgHiddenPrefs;
3210  $this->loadOptions();
3212 
3213  # We want 'disabled' preferences to always behave as the default value for
3214  # users, even if they have set the option explicitly in their settings (ie they
3215  # set it, and then it was disabled removing their ability to change it). But
3216  # we don't want to erase the preferences in the database in case the preference
3217  # is re-enabled again. So don't touch $mOptions, just override the returned value
3218  foreach ( $wgHiddenPrefs as $pref ) {
3219  $default = self::getDefaultOption( $pref );
3220  if ( $default !== null ) {
3221  $options[$pref] = $default;
3222  }
3223  }
3224 
3225  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3226  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3227  }
3228 
3229  return $options;
3230  }
3231 
3239  public function getBoolOption( $oname ) {
3240  return (bool)$this->getOption( $oname );
3241  }
3242 
3251  public function getIntOption( $oname, $defaultOverride = 0 ) {
3252  $val = $this->getOption( $oname );
3253  if ( $val == '' ) {
3254  $val = $defaultOverride;
3255  }
3256  return intval( $val );
3257  }
3258 
3267  public function setOption( $oname, $val ) {
3268  $this->loadOptions();
3269 
3270  // Explicitly NULL values should refer to defaults
3271  if ( is_null( $val ) ) {
3272  $val = self::getDefaultOption( $oname );
3273  }
3274 
3275  $this->mOptions[$oname] = $val;
3276  }
3277 
3288  public function getTokenFromOption( $oname ) {
3289  global $wgHiddenPrefs;
3290 
3291  $id = $this->getId();
3292  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3293  return false;
3294  }
3295 
3296  $token = $this->getOption( $oname );
3297  if ( !$token ) {
3298  // Default to a value based on the user token to avoid space
3299  // wasted on storing tokens for all users. When this option
3300  // is set manually by the user, only then is it stored.
3301  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3302  }
3303 
3304  return $token;
3305  }
3306 
3316  public function resetTokenFromOption( $oname ) {
3317  global $wgHiddenPrefs;
3318  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3319  return false;
3320  }
3321 
3322  $token = MWCryptRand::generateHex( 40 );
3323  $this->setOption( $oname, $token );
3324  return $token;
3325  }
3326 
3350  public static function listOptionKinds() {
3351  return [
3352  'registered',
3353  'registered-multiselect',
3354  'registered-checkmatrix',
3355  'userjs',
3356  'special',
3357  'unused'
3358  ];
3359  }
3360 
3374  $this->loadOptions();
3375  if ( $options === null ) {
3377  }
3378 
3379  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3380  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3381  $mapping = [];
3382 
3383  // Pull out the "special" options, so they don't get converted as
3384  // multiselect or checkmatrix.
3385  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3386  foreach ( $specialOptions as $name => $value ) {
3387  unset( $prefs[$name] );
3388  }
3389 
3390  // Multiselect and checkmatrix options are stored in the database with
3391  // one key per option, each having a boolean value. Extract those keys.
3392  $multiselectOptions = [];
3393  foreach ( $prefs as $name => $info ) {
3394  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3395  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3396  $opts = HTMLFormField::flattenOptions( $info['options'] );
3397  $prefix = $info['prefix'] ?? $name;
3398 
3399  foreach ( $opts as $value ) {
3400  $multiselectOptions["$prefix$value"] = true;
3401  }
3402 
3403  unset( $prefs[$name] );
3404  }
3405  }
3406  $checkmatrixOptions = [];
3407  foreach ( $prefs as $name => $info ) {
3408  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3409  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3410  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3411  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3412  $prefix = $info['prefix'] ?? $name;
3413 
3414  foreach ( $columns as $column ) {
3415  foreach ( $rows as $row ) {
3416  $checkmatrixOptions["$prefix$column-$row"] = true;
3417  }
3418  }
3419 
3420  unset( $prefs[$name] );
3421  }
3422  }
3423 
3424  // $value is ignored
3425  foreach ( $options as $key => $value ) {
3426  if ( isset( $prefs[$key] ) ) {
3427  $mapping[$key] = 'registered';
3428  } elseif ( isset( $multiselectOptions[$key] ) ) {
3429  $mapping[$key] = 'registered-multiselect';
3430  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3431  $mapping[$key] = 'registered-checkmatrix';
3432  } elseif ( isset( $specialOptions[$key] ) ) {
3433  $mapping[$key] = 'special';
3434  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3435  $mapping[$key] = 'userjs';
3436  } else {
3437  $mapping[$key] = 'unused';
3438  }
3439  }
3440 
3441  return $mapping;
3442  }
3443 
3458  public function resetOptions(
3459  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3461  ) {
3462  $this->load();
3463  $defaultOptions = self::getDefaultOptions();
3464 
3465  if ( !is_array( $resetKinds ) ) {
3466  $resetKinds = [ $resetKinds ];
3467  }
3468 
3469  if ( in_array( 'all', $resetKinds ) ) {
3470  $newOptions = $defaultOptions;
3471  } else {
3472  if ( $context === null ) {
3474  }
3475 
3476  $optionKinds = $this->getOptionKinds( $context );
3477  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3478  $newOptions = [];
3479 
3480  // Use default values for the options that should be deleted, and
3481  // copy old values for the ones that shouldn't.
3482  foreach ( $this->mOptions as $key => $value ) {
3483  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3484  if ( array_key_exists( $key, $defaultOptions ) ) {
3485  $newOptions[$key] = $defaultOptions[$key];
3486  }
3487  } else {
3488  $newOptions[$key] = $value;
3489  }
3490  }
3491  }
3492 
3493  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3494 
3495  $this->mOptions = $newOptions;
3496  $this->mOptionsLoaded = true;
3497  }
3498 
3503  public function getDatePreference() {
3504  // Important migration for old data rows
3505  if ( is_null( $this->mDatePreference ) ) {
3506  global $wgLang;
3507  $value = $this->getOption( 'date' );
3508  $map = $wgLang->getDatePreferenceMigrationMap();
3509  if ( isset( $map[$value] ) ) {
3510  $value = $map[$value];
3511  }
3512  $this->mDatePreference = $value;
3513  }
3514  return $this->mDatePreference;
3515  }
3516 
3523  public function requiresHTTPS() {
3524  global $wgSecureLogin;
3525  if ( !$wgSecureLogin ) {
3526  return false;
3527  }
3528 
3529  $https = $this->getBoolOption( 'prefershttps' );
3530  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3531  if ( $https ) {
3532  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3533  }
3534 
3535  return $https;
3536  }
3537 
3543  public function getStubThreshold() {
3544  global $wgMaxArticleSize; # Maximum article size, in Kb
3545  $threshold = $this->getIntOption( 'stubthreshold' );
3546  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3547  // If they have set an impossible value, disable the preference
3548  // so we can use the parser cache again.
3549  $threshold = 0;
3550  }
3551  return $threshold;
3552  }
3553 
3558  public function getRights() {
3559  if ( is_null( $this->mRights ) ) {
3560  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3561  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3562 
3563  // Deny any rights denied by the user's session, unless this
3564  // endpoint has no sessions.
3565  if ( !defined( 'MW_NO_SESSION' ) ) {
3566  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3567  if ( $allowedRights !== null ) {
3568  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3569  }
3570  }
3571 
3572  Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
3573  // Force reindexation of rights when a hook has unset one of them
3574  $this->mRights = array_values( array_unique( $this->mRights ) );
3575 
3576  // If block disables login, we should also remove any
3577  // extra rights blocked users might have, in case the
3578  // blocked user has a pre-existing session (T129738).
3579  // This is checked here for cases where people only call
3580  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3581  // to give a better error message in the common case.
3582  $config = RequestContext::getMain()->getConfig();
3583  if (
3584  $this->isLoggedIn() &&
3585  $config->get( 'BlockDisablesLogin' ) &&
3586  $this->isBlocked()
3587  ) {
3588  $anon = new User;
3589  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3590  }
3591  }
3592  return $this->mRights;
3593  }
3594 
3601  public function getGroups() {
3602  $this->load();
3603  $this->loadGroups();
3604  return array_keys( $this->mGroupMemberships );
3605  }
3606 
3614  public function getGroupMemberships() {
3615  $this->load();
3616  $this->loadGroups();
3617  return $this->mGroupMemberships;
3618  }
3619 
3627  public function getEffectiveGroups( $recache = false ) {
3628  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3629  $this->mEffectiveGroups = array_unique( array_merge(
3630  $this->getGroups(), // explicit groups
3631  $this->getAutomaticGroups( $recache ) // implicit groups
3632  ) );
3633  // Avoid PHP 7.1 warning of passing $this by reference
3634  $user = $this;
3635  // Hook for additional groups
3636  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3637  // Force reindexation of groups when a hook has unset one of them
3638  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3639  }
3640  return $this->mEffectiveGroups;
3641  }
3642 
3650  public function getAutomaticGroups( $recache = false ) {
3651  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3652  $this->mImplicitGroups = [ '*' ];
3653  if ( $this->getId() ) {
3654  $this->mImplicitGroups[] = 'user';
3655 
3656  $this->mImplicitGroups = array_unique( array_merge(
3657  $this->mImplicitGroups,
3659  ) );
3660  }
3661  if ( $recache ) {
3662  // Assure data consistency with rights/groups,
3663  // as getEffectiveGroups() depends on this function
3664  $this->mEffectiveGroups = null;
3665  }
3666  }
3667  return $this->mImplicitGroups;
3668  }
3669 
3679  public function getFormerGroups() {
3680  $this->load();
3681 
3682  if ( is_null( $this->mFormerGroups ) ) {
3683  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3684  ? wfGetDB( DB_MASTER )
3685  : wfGetDB( DB_REPLICA );
3686  $res = $db->select( 'user_former_groups',
3687  [ 'ufg_group' ],
3688  [ 'ufg_user' => $this->mId ],
3689  __METHOD__ );
3690  $this->mFormerGroups = [];
3691  foreach ( $res as $row ) {
3692  $this->mFormerGroups[] = $row->ufg_group;
3693  }
3694  }
3695 
3696  return $this->mFormerGroups;
3697  }
3698 
3703  public function getEditCount() {
3704  if ( !$this->getId() ) {
3705  return null;
3706  }
3707 
3708  if ( $this->mEditCount === null ) {
3709  /* Populate the count, if it has not been populated yet */
3710  $dbr = wfGetDB( DB_REPLICA );
3711  // check if the user_editcount field has been initialized
3712  $count = $dbr->selectField(
3713  'user', 'user_editcount',
3714  [ 'user_id' => $this->mId ],
3715  __METHOD__
3716  );
3717 
3718  if ( $count === null ) {
3719  // it has not been initialized. do so.
3720  $count = $this->initEditCountInternal();
3721  }
3722  $this->mEditCount = $count;
3723  }
3724  return (int)$this->mEditCount;
3725  }
3726 
3738  public function addGroup( $group, $expiry = null ) {
3739  $this->load();
3740  $this->loadGroups();
3741 
3742  if ( $expiry ) {
3743  $expiry = wfTimestamp( TS_MW, $expiry );
3744  }
3745 
3746  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3747  return false;
3748  }
3749 
3750  // create the new UserGroupMembership and put it in the DB
3751  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3752  if ( !$ugm->insert( true ) ) {
3753  return false;
3754  }
3755 
3756  $this->mGroupMemberships[$group] = $ugm;
3757 
3758  // Refresh the groups caches, and clear the rights cache so it will be
3759  // refreshed on the next call to $this->getRights().
3760  $this->getEffectiveGroups( true );
3761  $this->mRights = null;
3762 
3763  $this->invalidateCache();
3764 
3765  return true;
3766  }
3767 
3774  public function removeGroup( $group ) {
3775  $this->load();
3776 
3777  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3778  return false;
3779  }
3780 
3781  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3782  // delete the membership entry
3783  if ( !$ugm || !$ugm->delete() ) {
3784  return false;
3785  }
3786 
3787  $this->loadGroups();
3788  unset( $this->mGroupMemberships[$group] );
3789 
3790  // Refresh the groups caches, and clear the rights cache so it will be
3791  // refreshed on the next call to $this->getRights().
3792  $this->getEffectiveGroups( true );
3793  $this->mRights = null;
3794 
3795  $this->invalidateCache();
3796 
3797  return true;
3798  }
3799 
3804  public function isLoggedIn() {
3805  return $this->getId() != 0;
3806  }
3807 
3812  public function isAnon() {
3813  return !$this->isLoggedIn();
3814  }
3815 
3820  public function isBot() {
3821  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3822  return true;
3823  }
3824 
3825  $isBot = false;
3826  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3827 
3828  return $isBot;
3829  }
3830 
3837  public function isAllowedAny() {
3838  $permissions = func_get_args();
3839  foreach ( $permissions as $permission ) {
3840  if ( $this->isAllowed( $permission ) ) {
3841  return true;
3842  }
3843  }
3844  return false;
3845  }
3846 
3852  public function isAllowedAll() {
3853  $permissions = func_get_args();
3854  foreach ( $permissions as $permission ) {
3855  if ( !$this->isAllowed( $permission ) ) {
3856  return false;
3857  }
3858  }
3859  return true;
3860  }
3861 
3867  public function isAllowed( $action = '' ) {
3868  if ( $action === '' ) {
3869  return true; // In the spirit of DWIM
3870  }
3871  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3872  // by misconfiguration: 0 == 'foo'
3873  return in_array( $action, $this->getRights(), true );
3874  }
3875 
3880  public function useRCPatrol() {
3881  global $wgUseRCPatrol;
3882  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3883  }
3884 
3889  public function useNPPatrol() {
3891  return (
3892  ( $wgUseRCPatrol || $wgUseNPPatrol )
3893  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3894  );
3895  }
3896 
3901  public function useFilePatrol() {
3903  return (
3904  ( $wgUseRCPatrol || $wgUseFilePatrol )
3905  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3906  );
3907  }
3908 
3914  public function getRequest() {
3915  if ( $this->mRequest ) {
3916  return $this->mRequest;
3917  }
3918 
3919  global $wgRequest;
3920  return $wgRequest;
3921  }
3922 
3931  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3932  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3933  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3934  }
3935  return false;
3936  }
3937 
3945  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3946  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3947  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3948  $this,
3949  [ $title->getSubjectPage(), $title->getTalkPage() ]
3950  );
3951  }
3952  $this->invalidateCache();
3953  }
3954 
3962  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3963  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3964  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3965  $store->removeWatch( $this, $title->getSubjectPage() );
3966  $store->removeWatch( $this, $title->getTalkPage() );
3967  }
3968  $this->invalidateCache();
3969  }
3970 
3979  public function clearNotification( &$title, $oldid = 0 ) {
3981 
3982  // Do nothing if the database is locked to writes
3983  if ( wfReadOnly() ) {
3984  return;
3985  }
3986 
3987  // Do nothing if not allowed to edit the watchlist
3988  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3989  return;
3990  }
3991 
3992  // If we're working on user's talk page, we should update the talk page message indicator
3993  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3994  // Avoid PHP 7.1 warning of passing $this by reference
3995  $user = $this;
3996  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3997  return;
3998  }
3999 
4000  // Try to update the DB post-send and only if needed...
4001  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
4002  if ( !$this->getNewtalk() ) {
4003  return; // no notifications to clear
4004  }
4005 
4006  // Delete the last notifications (they stack up)
4007  $this->setNewtalk( false );
4008 
4009  // If there is a new, unseen, revision, use its timestamp
4010  $nextid = $oldid
4011  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
4012  : null;
4013  if ( $nextid ) {
4014  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
4015  }
4016  } );
4017  }
4018 
4019  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4020  return;
4021  }
4022 
4023  if ( $this->isAnon() ) {
4024  // Nothing else to do...
4025  return;
4026  }
4027 
4028  // Only update the timestamp if the page is being watched.
4029  // The query to find out if it is watched is cached both in memcached and per-invocation,
4030  // and when it does have to be executed, it can be on a replica DB
4031  // If this is the user's newtalk page, we always update the timestamp
4032  $force = '';
4033  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
4034  $force = 'force';
4035  }
4036 
4037  MediaWikiServices::getInstance()->getWatchedItemStore()
4038  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
4039  }
4040 
4047  public function clearAllNotifications() {
4049  // Do nothing if not allowed to edit the watchlist
4050  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
4051  return;
4052  }
4053 
4054  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4055  $this->setNewtalk( false );
4056  return;
4057  }
4058 
4059  $id = $this->getId();
4060  if ( !$id ) {
4061  return;
4062  }
4063 
4064  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
4065  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
4066 
4067  // We also need to clear here the "you have new message" notification for the own
4068  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
4069  }
4070 
4076  public function getExperienceLevel() {
4077  global $wgLearnerEdits,
4081 
4082  if ( $this->isAnon() ) {
4083  return false;
4084  }
4085 
4086  $editCount = $this->getEditCount();
4087  $registration = $this->getRegistration();
4088  $now = time();
4089  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
4090  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
4091 
4092  if ( $editCount < $wgLearnerEdits ||
4093  $registration > $learnerRegistration ) {
4094  return 'newcomer';
4095  }
4096 
4097  if ( $editCount > $wgExperiencedUserEdits &&
4098  $registration <= $experiencedRegistration
4099  ) {
4100  return 'experienced';
4101  }
4102 
4103  return 'learner';
4104  }
4105 
4114  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
4115  $this->load();
4116  if ( $this->mId == 0 ) {
4117  return;
4118  }
4119 
4120  $session = $this->getRequest()->getSession();
4121  if ( $request && $session->getRequest() !== $request ) {
4122  $session = $session->sessionWithRequest( $request );
4123  }
4124  $delay = $session->delaySave();
4125 
4126  if ( !$session->getUser()->equals( $this ) ) {
4127  if ( !$session->canSetUser() ) {
4129  ->warning( __METHOD__ .
4130  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
4131  );
4132  return;
4133  }
4134  $session->setUser( $this );
4135  }
4136 
4137  $session->setRememberUser( $rememberMe );
4138  if ( $secure !== null ) {
4139  $session->setForceHTTPS( $secure );
4140  }
4141 
4142  $session->persist();
4143 
4144  ScopedCallback::consume( $delay );
4145  }
4146 
4150  public function logout() {
4151  // Avoid PHP 7.1 warning of passing $this by reference
4152  $user = $this;
4153  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
4154  $this->doLogout();
4155  }
4156  }
4157 
4162  public function doLogout() {
4163  $session = $this->getRequest()->getSession();
4164  if ( !$session->canSetUser() ) {
4166  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
4167  $error = 'immutable';
4168  } elseif ( !$session->getUser()->equals( $this ) ) {
4170  ->warning( __METHOD__ .
4171  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4172  );
4173  // But we still may as well make this user object anon
4174  $this->clearInstanceCache( 'defaults' );
4175  $error = 'wronguser';
4176  } else {
4177  $this->clearInstanceCache( 'defaults' );
4178  $delay = $session->delaySave();
4179  $session->unpersist(); // Clear cookies (T127436)
4180  $session->setLoggedOutTimestamp( time() );
4181  $session->setUser( new User );
4182  $session->set( 'wsUserID', 0 ); // Other code expects this
4183  $session->resetAllTokens();
4184  ScopedCallback::consume( $delay );
4185  $error = false;
4186  }
4187  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4188  'event' => 'logout',
4189  'successful' => $error === false,
4190  'status' => $error ?: 'success',
4191  ] );
4192  }
4193 
4198  public function saveSettings() {
4199  if ( wfReadOnly() ) {
4200  // @TODO: caller should deal with this instead!
4201  // This should really just be an exception.
4203  null,
4204  "Could not update user with ID '{$this->mId}'; DB is read-only."
4205  ) );
4206  return;
4207  }
4208 
4209  $this->load();
4210  if ( $this->mId == 0 ) {
4211  return; // anon
4212  }
4213 
4214  // Get a new user_touched that is higher than the old one.
4215  // This will be used for a CAS check as a last-resort safety
4216  // check against race conditions and replica DB lag.
4217  $newTouched = $this->newTouchedTimestamp();
4218 
4219  $dbw = wfGetDB( DB_MASTER );
4220  $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
4222 
4223  $dbw->update( 'user',
4224  [ /* SET */
4225  'user_name' => $this->mName,
4226  'user_real_name' => $this->mRealName,
4227  'user_email' => $this->mEmail,
4228  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4229  'user_touched' => $dbw->timestamp( $newTouched ),
4230  'user_token' => strval( $this->mToken ),
4231  'user_email_token' => $this->mEmailToken,
4232  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4233  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4234  'user_id' => $this->mId,
4235  ] ), $fname
4236  );
4237 
4238  if ( !$dbw->affectedRows() ) {
4239  // Maybe the problem was a missed cache update; clear it to be safe
4240  $this->clearSharedCache( 'refresh' );
4241  // User was changed in the meantime or loaded with stale data
4242  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4243  LoggerFactory::getInstance( 'preferences' )->warning(
4244  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4245  [ 'user_id' => $this->mId, 'db_flag' => $from ]
4246  );
4247  throw new MWException( "CAS update failed on user_touched. " .
4248  "The version of the user to be saved is older than the current version."
4249  );
4250  }
4251 
4252  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4253  $dbw->update(
4254  'actor',
4255  [ 'actor_name' => $this->mName ],
4256  [ 'actor_user' => $this->mId ],
4257  $fname
4258  );
4259  }
4260  } );
4261 
4262  $this->mTouched = $newTouched;
4263  $this->saveOptions();
4264 
4265  Hooks::run( 'UserSaveSettings', [ $this ] );
4266  $this->clearSharedCache();
4267  $this->getUserPage()->purgeSquid();
4268  }
4269 
4276  public function idForName( $flags = 0 ) {
4277  $s = trim( $this->getName() );
4278  if ( $s === '' ) {
4279  return 0;
4280  }
4281 
4282  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4283  ? wfGetDB( DB_MASTER )
4284  : wfGetDB( DB_REPLICA );
4285 
4286  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4287  ? [ 'LOCK IN SHARE MODE' ]
4288  : [];
4289 
4290  $id = $db->selectField( 'user',
4291  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4292 
4293  return (int)$id;
4294  }
4295 
4311  public static function createNew( $name, $params = [] ) {
4312  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4313  if ( isset( $params[$field] ) ) {
4314  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4315  unset( $params[$field] );
4316  }
4317  }
4318 
4319  $user = new User;
4320  $user->load();
4321  $user->setToken(); // init token
4322  if ( isset( $params['options'] ) ) {
4323  $user->mOptions = $params['options'] + (array)$user->mOptions;
4324  unset( $params['options'] );
4325  }
4326  $dbw = wfGetDB( DB_MASTER );
4327 
4328  $noPass = PasswordFactory::newInvalidPassword()->toString();
4329 
4330  $fields = [
4331  'user_name' => $name,
4332  'user_password' => $noPass,
4333  'user_newpassword' => $noPass,
4334  'user_email' => $user->mEmail,
4335  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4336  'user_real_name' => $user->mRealName,
4337  'user_token' => strval( $user->mToken ),
4338  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4339  'user_editcount' => 0,
4340  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4341  ];
4342  foreach ( $params as $name => $value ) {
4343  $fields["user_$name"] = $value;
4344  }
4345 
4346  return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
4347  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4348  if ( $dbw->affectedRows() ) {
4349  $newUser = self::newFromId( $dbw->insertId() );
4350  $newUser->mName = $fields['user_name'];
4351  $newUser->updateActorId( $dbw );
4352  // Load the user from master to avoid replica lag
4353  $newUser->load( self::READ_LATEST );
4354  } else {
4355  $newUser = null;
4356  }
4357  return $newUser;
4358  } );
4359  }
4360 
4387  public function addToDatabase() {
4388  $this->load();
4389  if ( !$this->mToken ) {
4390  $this->setToken(); // init token
4391  }
4392 
4393  if ( !is_string( $this->mName ) ) {
4394  throw new RuntimeException( "User name field is not set." );
4395  }
4396 
4397  $this->mTouched = $this->newTouchedTimestamp();
4398 
4399  $dbw = wfGetDB( DB_MASTER );
4400  $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
4401  $noPass = PasswordFactory::newInvalidPassword()->toString();
4402  $dbw->insert( 'user',
4403  [
4404  'user_name' => $this->mName,
4405  'user_password' => $noPass,
4406  'user_newpassword' => $noPass,
4407  'user_email' => $this->mEmail,
4408  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4409  'user_real_name' => $this->mRealName,
4410  'user_token' => strval( $this->mToken ),
4411  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4412  'user_editcount' => 0,
4413  'user_touched' => $dbw->timestamp( $this->mTouched ),
4414  ], $fname,
4415  [ 'IGNORE' ]
4416  );
4417  if ( !$dbw->affectedRows() ) {
4418  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4419  $this->mId = $dbw->selectField(
4420  'user',
4421  'user_id',
4422  [ 'user_name' => $this->mName ],
4423  $fname,
4424  [ 'LOCK IN SHARE MODE' ]
4425  );
4426  $loaded = false;
4427  if ( $this->mId ) {
4428  if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4429  $loaded = true;
4430  }
4431  }
4432  if ( !$loaded ) {
4433  throw new MWException( $fname . ": hit a key conflict attempting " .
4434  "to insert user '{$this->mName}' row, but it was not present in select!" );
4435  }
4436  return Status::newFatal( 'userexists' );
4437  }
4438  $this->mId = $dbw->insertId();
4439  self::$idCacheByName[$this->mName] = $this->mId;
4440  $this->updateActorId( $dbw );
4441 
4442  return Status::newGood();
4443  } );
4444  if ( !$status->isGood() ) {
4445  return $status;
4446  }
4447 
4448  // Clear instance cache other than user table data and actor, which is already accurate
4449  $this->clearInstanceCache();
4450 
4451  $this->saveOptions();
4452  return Status::newGood();
4453  }
4454 
4459  private function updateActorId( IDatabase $dbw ) {
4461 
4462  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
4463  $dbw->insert(
4464  'actor',
4465  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4466  __METHOD__
4467  );
4468  $this->mActorId = (int)$dbw->insertId();
4469  }
4470  }
4471 
4477  public function spreadAnyEditBlock() {
4478  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4479  return $this->spreadBlock();
4480  }
4481 
4482  return false;
4483  }
4484 
4490  protected function spreadBlock() {
4491  wfDebug( __METHOD__ . "()\n" );
4492  $this->load();
4493  if ( $this->mId == 0 ) {
4494  return false;
4495  }
4496 
4497  $userblock = Block::newFromTarget( $this->getName() );
4498  if ( !$userblock ) {
4499  return false;
4500  }
4501 
4502  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4503  }
4504 
4509  public function isBlockedFromCreateAccount() {
4510  $this->getBlockedStatus();
4511  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4512  return $this->mBlock;
4513  }
4514 
4515  # T15611: if the IP address the user is trying to create an account from is
4516  # blocked with createaccount disabled, prevent new account creation there even
4517  # when the user is logged in
4518  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4519  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4520  }
4521  return $this->mBlockedFromCreateAccount instanceof Block
4522  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4523  ? $this->mBlockedFromCreateAccount
4524  : false;
4525  }
4526 
4531  public function isBlockedFromEmailuser() {
4532  $this->getBlockedStatus();
4533  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4534  }
4535 
4542  public function isBlockedFromUpload() {
4543  $this->getBlockedStatus();
4544  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4545  }
4546 
4551  public function isAllowedToCreateAccount() {
4552  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4553  }
4554 
4560  public function getUserPage() {
4561  return Title::makeTitle( NS_USER, $this->getName() );
4562  }
4563 
4569  public function getTalkPage() {
4570  $title = $this->getUserPage();
4571  return $title->getTalkPage();
4572  }
4573 
4579  public function isNewbie() {
4580  return !$this->isAllowed( 'autoconfirmed' );
4581  }
4582 
4589  public function checkPassword( $password ) {
4590  wfDeprecated( __METHOD__, '1.27' );
4591 
4592  $manager = AuthManager::singleton();
4593  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4594  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4595  [
4596  'username' => $this->getName(),
4597  'password' => $password,
4598  ]
4599  );
4600  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4601  switch ( $res->status ) {
4602  case AuthenticationResponse::PASS:
4603  return true;
4604  case AuthenticationResponse::FAIL:
4605  // Hope it's not a PreAuthenticationProvider that failed...
4607  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4608  return false;
4609  default:
4610  throw new BadMethodCallException(
4611  'AuthManager returned a response unsupported by ' . __METHOD__
4612  );
4613  }
4614  }
4615 
4624  public function checkTemporaryPassword( $plaintext ) {
4625  wfDeprecated( __METHOD__, '1.27' );
4626  // Can't check the temporary password individually.
4627  return $this->checkPassword( $plaintext );
4628  }
4629 
4641  public function getEditTokenObject( $salt = '', $request = null ) {
4642  if ( $this->isAnon() ) {
4643  return new LoggedOutEditToken();
4644  }
4645 
4646  if ( !$request ) {
4647  $request = $this->getRequest();
4648  }
4649  return $request->getSession()->getToken( $salt );
4650  }
4651 
4665  public function getEditToken( $salt = '', $request = null ) {
4666  return $this->getEditTokenObject( $salt, $request )->toString();
4667  }
4668 
4681  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4682  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4683  }
4684 
4695  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4696  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4697  return $this->matchEditToken( $val, $salt, $request, $maxage );
4698  }
4699 
4707  public function sendConfirmationMail( $type = 'created' ) {
4708  global $wgLang;
4709  $expiration = null; // gets passed-by-ref and defined in next line.
4710  $token = $this->confirmationToken( $expiration );
4711  $url = $this->confirmationTokenUrl( $token );
4712  $invalidateURL = $this->invalidationTokenUrl( $token );
4713  $this->saveSettings();
4714 
4715  if ( $type == 'created' || $type === false ) {
4716  $message = 'confirmemail_body';
4717  $type = 'created';
4718  } elseif ( $type === true ) {
4719  $message = 'confirmemail_body_changed';
4720  $type = 'changed';
4721  } else {
4722  // Messages: confirmemail_body_changed, confirmemail_body_set
4723  $message = 'confirmemail_body_' . $type;
4724  }
4725 
4726  $mail = [
4727  'subject' => wfMessage( 'confirmemail_subject' )->text(),
4728  'body' => wfMessage( $message,
4729  $this->getRequest()->getIP(),
4730  $this->getName(),
4731  $url,
4732  $wgLang->userTimeAndDate( $expiration, $this ),
4733  $invalidateURL,
4734  $wgLang->userDate( $expiration, $this ),
4735  $wgLang->userTime( $expiration, $this ) )->text(),
4736  'from' => null,
4737  'replyTo' => null,
4738  ];
4739  $info = [
4740  'type' => $type,
4741  'ip' => $this->getRequest()->getIP(),
4742  'confirmURL' => $url,
4743  'invalidateURL' => $invalidateURL,
4744  'expiration' => $expiration
4745  ];
4746 
4747  Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4748  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4749  }
4750 
4762  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4763  global $wgPasswordSender;
4764 
4765  if ( $from instanceof User ) {
4766  $sender = MailAddress::newFromUser( $from );
4767  } else {
4768  $sender = new MailAddress( $wgPasswordSender,
4769  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4770  }
4771  $to = MailAddress::newFromUser( $this );
4772 
4773  return UserMailer::send( $to, $sender, $subject, $body, [
4774  'replyTo' => $replyto,
4775  ] );
4776  }
4777 
4788  protected function confirmationToken( &$expiration ) {
4790  $now = time();
4791  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4792  $expiration = wfTimestamp( TS_MW, $expires );
4793  $this->load();
4794  $token = MWCryptRand::generateHex( 32 );
4795  $hash = md5( $token );
4796  $this->mEmailToken = $hash;
4797  $this->mEmailTokenExpires = $expiration;
4798  return $token;
4799  }
4800 
4806  protected function confirmationTokenUrl( $token ) {
4807  return $this->getTokenUrl( 'ConfirmEmail', $token );
4808  }
4809 
4815  protected function invalidationTokenUrl( $token ) {
4816  return $this->getTokenUrl( 'InvalidateEmail', $token );
4817  }
4818 
4833  protected function getTokenUrl( $page, $token ) {
4834  // Hack to bypass localization of 'Special:'
4835  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4836  return $title->getCanonicalURL();
4837  }
4838 
4846  public function confirmEmail() {
4847  // Check if it's already confirmed, so we don't touch the database
4848  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4849  if ( !$this->isEmailConfirmed() ) {
4851  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4852  }
4853  return true;
4854  }
4855 
4863  public function invalidateEmail() {
4864  $this->load();
4865  $this->mEmailToken = null;
4866  $this->mEmailTokenExpires = null;
4868  $this->mEmail = '';
4869  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4870  return true;
4871  }
4872 
4877  public function setEmailAuthenticationTimestamp( $timestamp ) {
4878  $this->load();
4879  $this->mEmailAuthenticated = $timestamp;
4880  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4881  }
4882 
4888  public function canSendEmail() {
4890  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4891  return false;
4892  }
4893  $canSend = $this->isEmailConfirmed();
4894  // Avoid PHP 7.1 warning of passing $this by reference
4895  $user = $this;
4896  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4897  return $canSend;
4898  }
4899 
4905  public function canReceiveEmail() {
4906  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4907  }
4908 
4919  public function isEmailConfirmed() {
4920  global $wgEmailAuthentication;
4921  $this->load();
4922  // Avoid PHP 7.1 warning of passing $this by reference
4923  $user = $this;
4924  $confirmed = true;
4925  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4926  if ( $this->isAnon() ) {
4927  return false;
4928  }
4929  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4930  return false;
4931  }
4932  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4933  return false;
4934  }
4935  return true;
4936  }
4937 
4938  return $confirmed;
4939  }
4940 
4945  public function isEmailConfirmationPending() {
4946  global $wgEmailAuthentication;
4947  return $wgEmailAuthentication &&
4948  !$this->isEmailConfirmed() &&
4949  $this->mEmailToken &&
4950  $this->mEmailTokenExpires > wfTimestamp();
4951  }
4952 
4960  public function getRegistration() {
4961  if ( $this->isAnon() ) {
4962  return false;
4963  }
4964  $this->load();
4965  return $this->mRegistration;
4966  }
4967 
4974  public function getFirstEditTimestamp() {
4975  return $this->getEditTimestamp( true );
4976  }
4977 
4985  public function getLatestEditTimestamp() {
4986  return $this->getEditTimestamp( false );
4987  }
4988 
4996  private function getEditTimestamp( $first ) {
4997  if ( $this->getId() == 0 ) {
4998  return false; // anons
4999  }
5000  $dbr = wfGetDB( DB_REPLICA );
5001  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5002  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
5003  ? 'revactor_timestamp' : 'rev_timestamp';
5004  $sortOrder = $first ? 'ASC' : 'DESC';
5005  $time = $dbr->selectField(
5006  [ 'revision' ] + $actorWhere['tables'],
5007  $tsField,
5008  [ $actorWhere['conds'] ],
5009  __METHOD__,
5010  [ 'ORDER BY' => "$tsField $sortOrder" ],
5011  $actorWhere['joins']
5012  );
5013  if ( !$time ) {
5014  return false; // no edits
5015  }
5016  return wfTimestamp( TS_MW, $time );
5017  }
5018 
5025  public static function getGroupPermissions( $groups ) {
5027  $rights = [];
5028  // grant every granted permission first
5029  foreach ( $groups as $group ) {
5030  if ( isset( $wgGroupPermissions[$group] ) ) {
5031  $rights = array_merge( $rights,
5032  // array_filter removes empty items
5033  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
5034  }
5035  }
5036  // now revoke the revoked permissions
5037  foreach ( $groups as $group ) {
5038  if ( isset( $wgRevokePermissions[$group] ) ) {
5039  $rights = array_diff( $rights,
5040  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
5041  }
5042  }
5043  return array_unique( $rights );
5044  }
5045 
5052  public static function getGroupsWithPermission( $role ) {
5053  global $wgGroupPermissions;
5054  $allowedGroups = [];
5055  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
5056  if ( self::groupHasPermission( $group, $role ) ) {
5057  $allowedGroups[] = $group;
5058  }
5059  }
5060  return $allowedGroups;
5061  }
5062 
5075  public static function groupHasPermission( $group, $role ) {
5077  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
5078  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
5079  }
5080 
5095  public static function isEveryoneAllowed( $right ) {
5097  static $cache = [];
5098 
5099  // Use the cached results, except in unit tests which rely on
5100  // being able change the permission mid-request
5101  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
5102  return $cache[$right];
5103  }
5104 
5105  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
5106  $cache[$right] = false;
5107  return false;
5108  }
5109 
5110  // If it's revoked anywhere, then everyone doesn't have it
5111  foreach ( $wgRevokePermissions as $rights ) {
5112  if ( isset( $rights[$right] ) && $rights[$right] ) {
5113  $cache[$right] = false;
5114  return false;
5115  }
5116  }
5117 
5118  // Remove any rights that aren't allowed to the global-session user,
5119  // unless there are no sessions for this endpoint.
5120  if ( !defined( 'MW_NO_SESSION' ) ) {
5121  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
5122  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
5123  $cache[$right] = false;
5124  return false;
5125  }
5126  }
5127 
5128  // Allow extensions to say false
5129  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
5130  $cache[$right] = false;
5131  return false;
5132  }
5133 
5134  $cache[$right] = true;
5135  return true;
5136  }
5137 
5144  public static function getAllGroups() {
5146  return array_values( array_diff(
5147  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
5148  self::getImplicitGroups()
5149  ) );
5150  }
5151 
5156  public static function getAllRights() {
5157  if ( self::$mAllRights === false ) {
5158  global $wgAvailableRights;
5159  if ( count( $wgAvailableRights ) ) {
5160  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
5161  } else {
5162  self::$mAllRights = self::$mCoreRights;
5163  }
5164  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
5165  }
5166  return self::$mAllRights;
5167  }
5168 
5175  public static function getImplicitGroups() {
5176  global $wgImplicitGroups;
5177  return $wgImplicitGroups;
5178  }
5179 
5187  public static function getGroupPage( $group ) {
5188  wfDeprecated( __METHOD__, '1.29' );
5189  return UserGroupMembership::getGroupPage( $group );
5190  }
5191 
5202  public static function makeGroupLinkHTML( $group, $text = '' ) {
5203  wfDeprecated( __METHOD__, '1.29' );
5204 
5205  if ( $text == '' ) {
5206  $text = UserGroupMembership::getGroupName( $group );
5207  }
5209  if ( $title ) {
5210  return MediaWikiServices::getInstance()
5211  ->getLinkRenderer()->makeLink( $title, $text );
5212  }
5213 
5214  return htmlspecialchars( $text );
5215  }
5216 
5227  public static function makeGroupLinkWiki( $group, $text = '' ) {
5228  wfDeprecated( __METHOD__, '1.29' );
5229 
5230  if ( $text == '' ) {
5231  $text = UserGroupMembership::getGroupName( $group );
5232  }
5234  if ( $title ) {
5235  $page = $title->getFullText();
5236  return "[[$page|$text]]";
5237  }
5238 
5239  return $text;
5240  }
5241 
5251  public static function changeableByGroup( $group ) {
5253 
5254  $groups = [
5255  'add' => [],
5256  'remove' => [],
5257  'add-self' => [],
5258  'remove-self' => []
5259  ];
5260 
5261  if ( empty( $wgAddGroups[$group] ) ) {
5262  // Don't add anything to $groups
5263  } elseif ( $wgAddGroups[$group] === true ) {
5264  // You get everything
5265  $groups['add'] = self::getAllGroups();
5266  } elseif ( is_array( $wgAddGroups[$group] ) ) {
5267  $groups['add'] = $wgAddGroups[$group];
5268  }
5269 
5270  // Same thing for remove
5271  if ( empty( $wgRemoveGroups[$group] ) ) {
5272  // Do nothing
5273  } elseif ( $wgRemoveGroups[$group] === true ) {
5274  $groups['remove'] = self::getAllGroups();
5275  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5276  $groups['remove'] = $wgRemoveGroups[$group];
5277  }
5278 
5279  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5280  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5281  foreach ( $wgGroupsAddToSelf as $key => $value ) {
5282  if ( is_int( $key ) ) {
5283  $wgGroupsAddToSelf['user'][] = $value;
5284  }
5285  }
5286  }
5287 
5288  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5289  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5290  if ( is_int( $key ) ) {
5291  $wgGroupsRemoveFromSelf['user'][] = $value;
5292  }
5293  }
5294  }
5295 
5296  // Now figure out what groups the user can add to him/herself
5297  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5298  // Do nothing
5299  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5300  // No idea WHY this would be used, but it's there
5301  $groups['add-self'] = self::getAllGroups();
5302  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5303  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5304  }
5305 
5306  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5307  // Do nothing
5308  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5309  $groups['remove-self'] = self::getAllGroups();
5310  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5311  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5312  }
5313 
5314  return $groups;
5315  }
5316 
5324  public function changeableGroups() {
5325  if ( $this->isAllowed( 'userrights' ) ) {
5326  // This group gives the right to modify everything (reverse-
5327  // compatibility with old "userrights lets you change
5328  // everything")
5329  // Using array_merge to make the groups reindexed
5330  $all = array_merge( self::getAllGroups() );
5331  return [
5332  'add' => $all,
5333  'remove' => $all,
5334  'add-self' => [],
5335  'remove-self' => []
5336  ];
5337  }
5338 
5339  // Okay, it's not so simple, we will have to go through the arrays
5340  $groups = [
5341  'add' => [],
5342  'remove' => [],
5343  'add-self' => [],
5344  'remove-self' => []
5345  ];
5346  $addergroups = $this->getEffectiveGroups();
5347 
5348  foreach ( $addergroups as $addergroup ) {
5349  $groups = array_merge_recursive(
5350  $groups, $this->changeableByGroup( $addergroup )
5351  );
5352  $groups['add'] = array_unique( $groups['add'] );
5353  $groups['remove'] = array_unique( $groups['remove'] );
5354  $groups['add-self'] = array_unique( $groups['add-self'] );
5355  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5356  }
5357  return $groups;
5358  }
5359 
5363  public function incEditCount() {
5364  if ( $this->isAnon() ) {
5365  return; // sanity
5366  }
5367 
5369  new UserEditCountUpdate( $this, 1 ),
5371  );
5372  }
5373 
5379  public function setEditCountInternal( $count ) {
5380  $this->mEditCount = $count;
5381  }
5382 
5390  public function initEditCountInternal() {
5391  // Pull from a replica DB to be less cruel to servers
5392  // Accuracy isn't the point anyway here
5393  $dbr = wfGetDB( DB_REPLICA );
5394  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5395  $count = (int)$dbr->selectField(
5396  [ 'revision' ] + $actorWhere['tables'],
5397  'COUNT(*)',
5398  [ $actorWhere['conds'] ],
5399  __METHOD__,
5400  [],
5401  $actorWhere['joins']
5402  );
5403 
5404  $dbw = wfGetDB( DB_MASTER );
5405  $dbw->update(
5406  'user',
5407  [ 'user_editcount' => $count ],
5408  [
5409  'user_id' => $this->getId(),
5410  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5411  ],
5412  __METHOD__
5413  );
5414 
5415  return $count;
5416  }
5417 
5425  public static function getRightDescription( $right ) {
5426  $key = "right-$right";
5427  $msg = wfMessage( $key );
5428  return $msg->isDisabled() ? $right : $msg->text();
5429  }
5430 
5438  public static function getGrantName( $grant ) {
5439  $key = "grant-$grant";
5440  $msg = wfMessage( $key );
5441  return $msg->isDisabled() ? $grant : $msg->text();
5442  }
5443 
5464  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5465  return true; // disabled
5466  }
5467 
5476  public function addNewUserLogEntryAutoCreate() {
5477  $this->addNewUserLogEntry( 'autocreate' );
5478 
5479  return true;
5480  }
5481 
5487  protected function loadOptions( $data = null ) {
5488  $this->load();
5489 
5490  if ( $this->mOptionsLoaded ) {
5491  return;
5492  }
5493 
5494  $this->mOptions = self::getDefaultOptions();
5495 
5496  if ( !$this->getId() ) {
5497  // For unlogged-in users, load language/variant options from request.
5498  // There's no need to do it for logged-in users: they can set preferences,
5499  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5500  // so don't override user's choice (especially when the user chooses site default).
5501  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5502  $this->mOptions['variant'] = $variant;
5503  $this->mOptions['language'] = $variant;
5504  $this->mOptionsLoaded = true;
5505  return;
5506  }
5507 
5508  // Maybe load from the object
5509  if ( !is_null( $this->mOptionOverrides ) ) {
5510  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5511  foreach ( $this->mOptionOverrides as $key => $value ) {
5512  $this->mOptions[$key] = $value;
5513  }
5514  } else {
5515  if ( !is_array( $data ) ) {
5516  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5517  // Load from database
5518  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5519  ? wfGetDB( DB_MASTER )
5520  : wfGetDB( DB_REPLICA );
5521 
5522  $res = $dbr->select(
5523  'user_properties',
5524  [ 'up_property', 'up_value' ],
5525  [ 'up_user' => $this->getId() ],
5526  __METHOD__
5527  );
5528 
5529  $this->mOptionOverrides = [];
5530  $data = [];
5531  foreach ( $res as $row ) {
5532  // Convert '0' to 0. PHP's boolean conversion considers them both
5533  // false, but e.g. JavaScript considers the former as true.
5534  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5535  // and convert all values here.
5536  if ( $row->up_value === '0' ) {
5537  $row->up_value = 0;
5538  }
5539  $data[$row->up_property] = $row->up_value;
5540  }
5541  }
5542 
5543  foreach ( $data as $property => $value ) {
5544  $this->mOptionOverrides[$property] = $value;
5545  $this->mOptions[$property] = $value;
5546  }
5547  }
5548 
5549  // Replace deprecated language codes
5550  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5551  $this->mOptions['language']
5552  );
5553 
5554  $this->mOptionsLoaded = true;
5555 
5556  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5557  }
5558 
5564  protected function saveOptions() {
5565  $this->loadOptions();
5566 
5567  // Not using getOptions(), to keep hidden preferences in database
5568  $saveOptions = $this->mOptions;
5569 
5570  // Allow hooks to abort, for instance to save to a global profile.
5571  // Reset options to default state before saving.
5572  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5573  return;
5574  }
5575 
5576  $userId = $this->getId();
5577 
5578  $insert_rows = []; // all the new preference rows
5579  foreach ( $saveOptions as $key => $value ) {
5580  // Don't bother storing default values
5581  $defaultOption = self::getDefaultOption( $key );
5582  if ( ( $defaultOption === null && $value !== false && $value !== null )
5583  || $value != $defaultOption
5584  ) {
5585  $insert_rows[] = [
5586  'up_user' => $userId,
5587  'up_property' => $key,
5588  'up_value' => $value,
5589  ];
5590  }
5591  }
5592 
5593  $dbw = wfGetDB( DB_MASTER );
5594 
5595  $res = $dbw->select( 'user_properties',
5596  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5597 
5598  // Find prior rows that need to be removed or updated. These rows will
5599  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5600  $keysDelete = [];
5601  foreach ( $res as $row ) {
5602  if ( !isset( $saveOptions[$row->up_property] )
5603  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5604  ) {
5605  $keysDelete[] = $row->up_property;
5606  }
5607  }
5608 
5609  if ( count( $keysDelete ) ) {
5610  // Do the DELETE by PRIMARY KEY for prior rows.
5611  // In the past a very large portion of calls to this function are for setting
5612  // 'rememberpassword' for new accounts (a preference that has since been removed).
5613  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5614  // caused gap locks on [max user ID,+infinity) which caused high contention since
5615  // updates would pile up on each other as they are for higher (newer) user IDs.
5616  // It might not be necessary these days, but it shouldn't hurt either.
5617  $dbw->delete( 'user_properties',
5618  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5619  }
5620  // Insert the new preference rows
5621  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5622  }
5623 
5630  public static function selectFields() {
5631  wfDeprecated( __METHOD__, '1.31' );
5632  return [
5633  'user_id',
5634  'user_name',
5635  'user_real_name',
5636  'user_email',
5637  'user_touched',
5638  'user_token',
5639  'user_email_authenticated',
5640  'user_email_token',
5641  'user_email_token_expires',
5642  'user_registration',
5643  'user_editcount',
5644  ];
5645  }
5646 
5656  public static function getQueryInfo() {
5658 
5659  $ret = [
5660  'tables' => [ 'user' ],
5661  'fields' => [
5662  'user_id',
5663  'user_name',
5664  'user_real_name',
5665  'user_email',
5666  'user_touched',
5667  'user_token',
5668  'user_email_authenticated',
5669  'user_email_token',
5670  'user_email_token_expires',
5671  'user_registration',
5672  'user_editcount',
5673  ],
5674  'joins' => [],
5675  ];
5676 
5677  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
5678  // but it does little harm and might be needed for write callers loading a User.
5679  if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
5680  $ret['tables']['user_actor'] = 'actor';
5681  $ret['fields'][] = 'user_actor.actor_id';
5682  $ret['joins']['user_actor'] = [
5683  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
5684  [ 'user_actor.actor_user = user_id' ]
5685  ];
5686  }
5687 
5688  return $ret;
5689  }
5690 
5698  static function newFatalPermissionDeniedStatus( $permission ) {
5699  global $wgLang;
5700 
5701  $groups = [];
5702  foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5703  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5704  }
5705 
5706  if ( $groups ) {
5707  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5708  }
5709 
5710  return Status::newFatal( 'badaccess-group0' );
5711  }
5712 
5722  public function getInstanceForUpdate() {
5723  if ( !$this->getId() ) {
5724  return null; // anon
5725  }
5726 
5727  $user = self::newFromId( $this->getId() );
5728  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5729  return null;
5730  }
5731 
5732  return $user;
5733  }
5734 
5742  public function equals( UserIdentity $user ) {
5743  // XXX it's not clear whether central ID providers are supposed to obey this
5744  return $this->getName() === $user->getName();
5745  }
5746 }
getEmail()
Get the user&#39;s e-mail address.
Definition: User.php:3063
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
static randomPassword()
Return a random password.
Definition: User.php:1296
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
isHidden()
Check if user account is hidden.
Definition: User.php:2416
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition: User.php:1690
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition: User.php:5187
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:2914
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
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:2621
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:3165
$wgMaxArticleSize
Maximum article size in kilobytes.
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1358
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2612
isAllowedAll()
Definition: User.php:3852
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1746
string $mDatePreference
Definition: User.php:263
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4579
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:884
static newFromID( $id)
Load a block from the block id.
Definition: Block.php:200
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:2046
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3945
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:4106
$property
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1484
$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:98
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
loadFromSession()
Load user data from the session.
Definition: User.php:1369
const NS_MAIN
Definition: Defines.php:64
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:5251
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4551
$success
Block $mBlockedFromCreateAccount
Definition: User.php:297
logout()
Log this user out.
Definition: User.php:4150
clearNotification(&$title, $oldid=0)
Clear the user&#39;s notification timestamp for the given title.
Definition: User.php:3979
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:5025
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:5175
saveSettings()
Save this user&#39;s settings into the database.
Definition: User.php:4198
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:2451
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:4974
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:1985
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:5095
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:2845
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:5390
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3837
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4531
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3627
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:2830
Handles increment the edit count for a given set of users.
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2354
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target&#39;s type, get an existing Block object if possible.
Definition: Block.php:1364
setName( $str)
Set the user name.
Definition: User.php:2488
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:1266
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3180
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2323
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php: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:295
static getWikiIdFromDbDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:255
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:4076
__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:1588
getRealName()
Get the user&#39;s real name.
Definition: User.php:3153
$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:2672
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:4762
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:1708
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5324
clearAllNotifications()
Resets all of the given user&#39;s page-change notification timestamps.
Definition: User.php:4047
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:4695
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3804
static clearCookie(WebResponse $response)
Unset the &#39;BlockID&#39; cookie.
Definition: Block.php:1745
const TYPE_IP
Definition: Block.php:97
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:3035
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:4542
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4624
$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:2272
$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:5656
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:2879
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:3267
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2368
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition: User.php:4996
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1996
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1802
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:4114
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2460
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5630
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4459
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:2965
int null $mActorId
Cache variables.
Definition: User.php:206
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3962
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:1983
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4589
getTitleKey()
Get the user&#39;s name escaped by underscores.
Definition: User.php:2566
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:5144
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3350
$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:2118
Interface for objects representing user identity.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
string $mEmailTokenExpires
Cache variables.
Definition: User.php:223
static getAllRights()
Get a list of all available permissions.
Definition: User.php:5156
isEmailConfirmed()
Is this user&#39;s e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4919
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:1985
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:4665
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1243
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:4509
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:2687
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:3288
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:1639
$wgPasswordPolicy
Password policy for the wiki.
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4960
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects...
Definition: User.php:3614
$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:875
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5425
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:4806
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:509
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:5476
$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:5052
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:3239
Block $mBlock
Definition: User.php:291
string $mBlockreason
Definition: User.php:271
isAnon()
Get whether the user is anonymous.
Definition: User.php:3812
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4833
$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:2061
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:5438
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3316
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:4641
doLogout()
Clear the user&#39;s session, and reset the instance cache.
Definition: User.php:4162
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:5742
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:521
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4945
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:5363
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2765
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:2015
$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:3373
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1434
$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:4888
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4985
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:2927
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:3867
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:1985
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:3931
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:1348
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:1404
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
requiresHTTPS()
Determine based on the wiki configuration and the user&#39;s options, whether this user must be over HTTP...
Definition: User.php:3523
$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:2295
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:1605
$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:4490
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
static hasFlags( $bitfield, $flags)
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
isBot()
Definition: User.php:3820
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4877
$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:1769
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:5379
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history...
appliesToRight( $right)
Determine whether the Block prevents a given right.
Definition: Block.php:1194
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:780
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:651
clearSharedCache( $mode='changed')
Clear user data from memcached.
Definition: User.php:2784
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:123
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:5075
getToken( $forceCreation=true)
Get the user&#39;s current token.
Definition: User.php:2992
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:929
static newInvalidPassword()
Create an InvalidPassword.
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3880
$wgPasswordSender
Sender email address for e-mail notifications.
$wgLearnerMemberSince
Name of the external diff engine to use.
loadDefaults( $name=false)
Set cached properties to default.
Definition: User.php:1309
array $mOptions
Definition: User.php:285
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1816
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:3055
$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:4863
loadGroups()
Load the groups from the database if they aren&#39;t already loaded.
Definition: User.php:1615
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5564
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:574
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:546
$wgApplyIpBlocksToXff
Whether to look at the X-Forwarded-For header&#39;s list of (potentially spoofed) IPs and apply IP blocks...
static getCurrentWikiDbDomain()
Definition: WikiMap.php:286
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3543
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3914
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:1403
this hook is for auditing only $req
Definition: hooks.txt:979
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the &#39;BlockID&#39; cookie.
Definition: Block.php:1781
getNewtalk()
Check if the user has new messages.
Definition: User.php:2574
bool $mLocked
Definition: User.php:281
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:1487
$wgUseEnotif
Definition: Setup.php:437
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3601
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:3251
getBlockFromCookieValue( $blockCookieVal)
Try to load a Block from an ID given in a cookie value.
Definition: User.php:1945
idForName( $flags=0)
If only this user&#39;s username is known, and it exists, return the user ID.
Definition: User.php:4276
getOptions( $flags=0)
Get all user&#39;s options.
Definition: User.php:3208
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:4477
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:320
getBlock( $bFromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2283
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:3901
getId()
Get the user&#39;s ID.
Definition: User.php:2433
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4681
getEmailAuthenticationTimestamp()
Get the timestamp of the user&#39;s e-mail authentication.
Definition: User.php:3073
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:2132
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5487
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition: User.php:5202
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name. ...
Definition: User.php:5227
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4387
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:2499
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:2813
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:4846
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5698
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:3703
getUserPage()
Get this user&#39;s personal page title.
Definition: User.php:4560
setNewtalk( $val, $curRev=null)
Update the &#39;You have new messages!&#39; status.
Definition: User.php:2732
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:5722
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3889
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:2621
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
const TYPE_USER
Definition: Block.php:96
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3503
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2341
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:4707
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2645
$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:2401
$messages
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:728
string $mHash
Definition: User.php:267
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2712
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4905
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:4815
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1201
setEmailWithConfirmation( $str)
Set the user&#39;s e-mail address and a confirmation mail if needed.
Definition: User.php:3100
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4311
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3650
array $mOptionOverrides
Cache variables.
Definition: User.php:231
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:584
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1771
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:2901
string $mRealName
Cache variables.
Definition: User.php:208
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3738
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:1827
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:118
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
getCacheKey(WANObjectCache $cache)
Definition: User.php:498
setEmail( $str)
Set the user&#39;s e-mail address.
Definition: User.php:3083
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:5464
getTalkPage()
Get this user&#39;s talk page title.
Definition: User.php:4569
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:2107
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:2621
static singleton()
Definition: UserCache.php:34
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3774
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:1476
getRights()
Get the permissions this user has.
Definition: User.php:3558
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3679
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2332
$wgEnableDnsBlacklist
Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
Base class for the more common types of database errors.
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3458
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:4788
getTouched()
Get the user touched timestamp.
Definition: User.php:2857
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