MediaWiki  1.33.0
User.php
Go to the documentation of this file.
1 <?php
31 use Wikimedia\Assert\Assert;
32 use Wikimedia\IPSet;
33 use Wikimedia\ScopedCallback;
37 
48 class User implements IDBAccessObject, UserIdentity {
52  const TOKEN_LENGTH = 32;
53 
57  const INVALID_TOKEN = '*** INVALID ***';
58 
62  const VERSION = 13;
63 
69 
73  const CHECK_USER_RIGHTS = true;
74 
78  const IGNORE_USER_RIGHTS = false;
79 
86  protected static $mCacheVars = [
87  // user table
88  'mId',
89  'mName',
90  'mRealName',
91  'mEmail',
92  'mTouched',
93  'mToken',
94  'mEmailAuthenticated',
95  'mEmailToken',
96  'mEmailTokenExpires',
97  'mRegistration',
98  'mEditCount',
99  // user_groups table
100  'mGroupMemberships',
101  // user_properties table
102  'mOptionOverrides',
103  // actor table
104  'mActorId',
105  ];
106 
113  protected static $mCoreRights = [
114  'apihighlimits',
115  'applychangetags',
116  'autoconfirmed',
117  'autocreateaccount',
118  'autopatrol',
119  'bigdelete',
120  'block',
121  'blockemail',
122  'bot',
123  'browsearchive',
124  'changetags',
125  'createaccount',
126  'createpage',
127  'createtalk',
128  'delete',
129  'deletechangetags',
130  'deletedhistory',
131  'deletedtext',
132  'deletelogentry',
133  'deleterevision',
134  'edit',
135  'editcontentmodel',
136  'editinterface',
137  'editprotected',
138  'editmyoptions',
139  'editmyprivateinfo',
140  'editmyusercss',
141  'editmyuserjson',
142  'editmyuserjs',
143  'editmywatchlist',
144  'editsemiprotected',
145  'editsitecss',
146  'editsitejson',
147  'editsitejs',
148  'editusercss',
149  'edituserjson',
150  'edituserjs',
151  'hideuser',
152  'import',
153  'importupload',
154  'ipblock-exempt',
155  'managechangetags',
156  'markbotedits',
157  'mergehistory',
158  'minoredit',
159  'move',
160  'movefile',
161  'move-categorypages',
162  'move-rootuserpages',
163  'move-subpages',
164  'nominornewtalk',
165  'noratelimit',
166  'override-export-depth',
167  'pagelang',
168  'patrol',
169  'patrolmarks',
170  'protect',
171  'purge',
172  'read',
173  'reupload',
174  'reupload-own',
175  'reupload-shared',
176  'rollback',
177  'sendemail',
178  'siteadmin',
179  'suppressionlog',
180  'suppressredirect',
181  'suppressrevision',
182  'unblockself',
183  'undelete',
184  'unwatchedpages',
185  'upload',
186  'upload_by_url',
187  'userrights',
188  'userrights-interwiki',
189  'viewmyprivateinfo',
190  'viewmywatchlist',
191  'viewsuppressed',
192  'writeapi',
193  ];
194 
198  protected static $mAllRights = false;
199 
201  // @{
203  public $mId;
205  public $mName;
207  protected $mActorId;
209  public $mRealName;
210 
212  public $mEmail;
214  public $mTouched;
216  protected $mQuickTouched;
218  protected $mToken;
222  protected $mEmailToken;
226  protected $mRegistration;
228  protected $mEditCount;
232  protected $mOptionOverrides;
233  // @}
234 
238  // @{
240 
244  protected $mLoadedItems = [];
245  // @}
246 
257  public $mFrom;
258 
262  protected $mNewtalk;
264  protected $mDatePreference;
266  public $mBlockedby;
268  protected $mHash;
270  public $mRights;
272  protected $mBlockreason;
274  protected $mEffectiveGroups;
276  protected $mImplicitGroups;
278  protected $mFormerGroups;
280  protected $mGlobalBlock;
282  protected $mLocked;
284  public $mHideName;
286  public $mOptions;
287 
289  private $mRequest;
290 
292  public $mBlock;
293 
295  protected $mAllowUsertalk;
296 
298  private $mBlockedFromCreateAccount = false;
299 
301  protected $queryFlagsUsed = self::READ_NORMAL;
302 
303  public static $idCacheByName = [];
304 
316  public function __construct() {
317  $this->clearInstanceCache( 'defaults' );
318  }
319 
323  public function __toString() {
324  return (string)$this->getName();
325  }
326 
341  public function isSafeToLoad() {
342  global $wgFullyInitialised;
343 
344  // The user is safe to load if:
345  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
346  // * mLoadedItems === true (already loaded)
347  // * mFrom !== 'session' (sessions not involved at all)
348 
349  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
350  $this->mLoadedItems === true || $this->mFrom !== 'session';
351  }
352 
358  public function load( $flags = self::READ_NORMAL ) {
359  global $wgFullyInitialised;
360 
361  if ( $this->mLoadedItems === true ) {
362  return;
363  }
364 
365  // Set it now to avoid infinite recursion in accessors
366  $oldLoadedItems = $this->mLoadedItems;
367  $this->mLoadedItems = true;
368  $this->queryFlagsUsed = $flags;
369 
370  // If this is called too early, things are likely to break.
371  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
373  ->warning( 'User::loadFromSession called before the end of Setup.php', [
374  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
375  ] );
376  $this->loadDefaults();
377  $this->mLoadedItems = $oldLoadedItems;
378  return;
379  }
380 
381  switch ( $this->mFrom ) {
382  case 'defaults':
383  $this->loadDefaults();
384  break;
385  case 'name':
386  // Make sure this thread sees its own changes
387  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
388  if ( $lb->hasOrMadeRecentMasterChanges() ) {
389  $flags |= self::READ_LATEST;
390  $this->queryFlagsUsed = $flags;
391  }
392 
393  $this->mId = self::idFromName( $this->mName, $flags );
394  if ( !$this->mId ) {
395  // Nonexistent user placeholder object
396  $this->loadDefaults( $this->mName );
397  } else {
398  $this->loadFromId( $flags );
399  }
400  break;
401  case 'id':
402  // Make sure this thread sees its own changes, if the ID isn't 0
403  if ( $this->mId != 0 ) {
404  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
405  if ( $lb->hasOrMadeRecentMasterChanges() ) {
406  $flags |= self::READ_LATEST;
407  $this->queryFlagsUsed = $flags;
408  }
409  }
410 
411  $this->loadFromId( $flags );
412  break;
413  case 'actor':
414  // Make sure this thread sees its own changes
415  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
416  if ( $lb->hasOrMadeRecentMasterChanges() ) {
417  $flags |= self::READ_LATEST;
418  $this->queryFlagsUsed = $flags;
419  }
420 
421  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
422  $row = wfGetDB( $index )->selectRow(
423  'actor',
424  [ 'actor_user', 'actor_name' ],
425  [ 'actor_id' => $this->mActorId ],
426  __METHOD__,
427  $options
428  );
429 
430  if ( !$row ) {
431  // Ugh.
432  $this->loadDefaults();
433  } elseif ( $row->actor_user ) {
434  $this->mId = $row->actor_user;
435  $this->loadFromId( $flags );
436  } else {
437  $this->loadDefaults( $row->actor_name );
438  }
439  break;
440  case 'session':
441  if ( !$this->loadFromSession() ) {
442  // Loading from session failed. Load defaults.
443  $this->loadDefaults();
444  }
445  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
446  break;
447  default:
448  throw new UnexpectedValueException(
449  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
450  }
451  }
452 
458  public function loadFromId( $flags = self::READ_NORMAL ) {
459  if ( $this->mId == 0 ) {
460  // Anonymous users are not in the database (don't need cache)
461  $this->loadDefaults();
462  return false;
463  }
464 
465  // Try cache (unless this needs data from the master DB).
466  // NOTE: if this thread called saveSettings(), the cache was cleared.
467  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
468  if ( $latest ) {
469  if ( !$this->loadFromDatabase( $flags ) ) {
470  // Can't load from ID
471  return false;
472  }
473  } else {
474  $this->loadFromCache();
475  }
476 
477  $this->mLoadedItems = true;
478  $this->queryFlagsUsed = $flags;
479 
480  return true;
481  }
482 
488  public static function purge( $wikiId, $userId ) {
489  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
490  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
491  $cache->delete( $key );
492  }
493 
499  protected function getCacheKey( WANObjectCache $cache ) {
500  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
501 
502  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
503  }
504 
511  $id = $this->getId();
512 
513  return $id ? [ $this->getCacheKey( $cache ) ] : [];
514  }
515 
522  protected function loadFromCache() {
523  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
524  $data = $cache->getWithSetCallback(
525  $this->getCacheKey( $cache ),
526  $cache::TTL_HOUR,
527  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
528  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
529  wfDebug( "User: cache miss for user {$this->mId}\n" );
530 
531  $this->loadFromDatabase( self::READ_NORMAL );
532  $this->loadGroups();
533  $this->loadOptions();
534 
535  $data = [];
536  foreach ( self::$mCacheVars as $name ) {
537  $data[$name] = $this->$name;
538  }
539 
540  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
541 
542  // if a user group membership is about to expire, the cache needs to
543  // expire at that time (T163691)
544  foreach ( $this->mGroupMemberships as $ugm ) {
545  if ( $ugm->getExpiry() ) {
546  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
547  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
548  $ttl = $secondsUntilExpiry;
549  }
550  }
551  }
552 
553  return $data;
554  },
555  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
556  );
557 
558  // Restore from cache
559  foreach ( self::$mCacheVars as $name ) {
560  $this->$name = $data[$name];
561  }
562 
563  return true;
564  }
565 
567  // @{
568 
585  public static function newFromName( $name, $validate = 'valid' ) {
586  if ( $validate === true ) {
587  $validate = 'valid';
588  }
589  $name = self::getCanonicalName( $name, $validate );
590  if ( $name === false ) {
591  return false;
592  }
593 
594  // Create unloaded user object
595  $u = new User;
596  $u->mName = $name;
597  $u->mFrom = 'name';
598  $u->setItemLoaded( 'name' );
599 
600  return $u;
601  }
602 
609  public static function newFromId( $id ) {
610  $u = new User;
611  $u->mId = $id;
612  $u->mFrom = 'id';
613  $u->setItemLoaded( 'id' );
614  return $u;
615  }
616 
624  public static function newFromActorId( $id ) {
626 
627  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
628  // but it does little harm and might be needed for write callers loading a User.
630  throw new BadMethodCallException(
631  'Cannot use ' . __METHOD__
632  . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
633  );
634  }
635 
636  $u = new User;
637  $u->mActorId = $id;
638  $u->mFrom = 'actor';
639  $u->setItemLoaded( 'actor' );
640  return $u;
641  }
642 
652  public static function newFromIdentity( UserIdentity $identity ) {
653  if ( $identity instanceof User ) {
654  return $identity;
655  }
656 
657  return self::newFromAnyId(
658  $identity->getId() === 0 ? null : $identity->getId(),
659  $identity->getName() === '' ? null : $identity->getName(),
660  $identity->getActorId() === 0 ? null : $identity->getActorId()
661  );
662  }
663 
676  public static function newFromAnyId( $userId, $userName, $actorId ) {
678 
679  $user = new User;
680  $user->mFrom = 'defaults';
681 
682  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
683  // but it does little harm and might be needed for write callers loading a User.
684  if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
685  $user->mActorId = (int)$actorId;
686  if ( $user->mActorId !== 0 ) {
687  $user->mFrom = 'actor';
688  }
689  $user->setItemLoaded( 'actor' );
690  }
691 
692  if ( $userName !== null && $userName !== '' ) {
693  $user->mName = $userName;
694  $user->mFrom = 'name';
695  $user->setItemLoaded( 'name' );
696  }
697 
698  if ( $userId !== null ) {
699  $user->mId = (int)$userId;
700  if ( $user->mId !== 0 ) {
701  $user->mFrom = 'id';
702  }
703  $user->setItemLoaded( 'id' );
704  }
705 
706  if ( $user->mFrom === 'defaults' ) {
707  throw new InvalidArgumentException(
708  'Cannot create a user with no name, no ID, and no actor ID'
709  );
710  }
711 
712  return $user;
713  }
714 
726  public static function newFromConfirmationCode( $code, $flags = 0 ) {
727  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
728  ? wfGetDB( DB_MASTER )
729  : wfGetDB( DB_REPLICA );
730 
731  $id = $db->selectField(
732  'user',
733  'user_id',
734  [
735  'user_email_token' => md5( $code ),
736  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
737  ]
738  );
739 
740  return $id ? self::newFromId( $id ) : null;
741  }
742 
750  public static function newFromSession( WebRequest $request = null ) {
751  $user = new User;
752  $user->mFrom = 'session';
753  $user->mRequest = $request;
754  return $user;
755  }
756 
772  public static function newFromRow( $row, $data = null ) {
773  $user = new User;
774  $user->loadFromRow( $row, $data );
775  return $user;
776  }
777 
813  public static function newSystemUser( $name, $options = [] ) {
814  $options += [
815  'validate' => 'valid',
816  'create' => true,
817  'steal' => false,
818  ];
819 
820  $name = self::getCanonicalName( $name, $options['validate'] );
821  if ( $name === false ) {
822  return null;
823  }
824 
825  $dbr = wfGetDB( DB_REPLICA );
826  $userQuery = self::getQueryInfo();
827  $row = $dbr->selectRow(
828  $userQuery['tables'],
829  $userQuery['fields'],
830  [ 'user_name' => $name ],
831  __METHOD__,
832  [],
833  $userQuery['joins']
834  );
835  if ( !$row ) {
836  // Try the master database...
837  $dbw = wfGetDB( DB_MASTER );
838  $row = $dbw->selectRow(
839  $userQuery['tables'],
840  $userQuery['fields'],
841  [ 'user_name' => $name ],
842  __METHOD__,
843  [],
844  $userQuery['joins']
845  );
846  }
847 
848  if ( !$row ) {
849  // No user. Create it?
850  return $options['create']
851  ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
852  : null;
853  }
854 
855  $user = self::newFromRow( $row );
856 
857  // A user is considered to exist as a non-system user if it can
858  // authenticate, or has an email set, or has a non-invalid token.
859  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
860  AuthManager::singleton()->userCanAuthenticate( $name )
861  ) {
862  // User exists. Steal it?
863  if ( !$options['steal'] ) {
864  return null;
865  }
866 
867  AuthManager::singleton()->revokeAccessForUser( $name );
868 
869  $user->invalidateEmail();
870  $user->mToken = self::INVALID_TOKEN;
871  $user->saveSettings();
872  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
873  }
874 
875  return $user;
876  }
877 
878  // @}
879 
885  public static function whoIs( $id ) {
886  return UserCache::singleton()->getProp( $id, 'name' );
887  }
888 
895  public static function whoIsReal( $id ) {
896  return UserCache::singleton()->getProp( $id, 'real_name' );
897  }
898 
905  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
906  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
907  $name = (string)$name;
909  if ( is_null( $nt ) ) {
910  // Illegal name
911  return null;
912  }
913 
914  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
915  return self::$idCacheByName[$name];
916  }
917 
918  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
919  $db = wfGetDB( $index );
920 
921  $s = $db->selectRow(
922  'user',
923  [ 'user_id' ],
924  [ 'user_name' => $nt->getText() ],
925  __METHOD__,
926  $options
927  );
928 
929  if ( $s === false ) {
930  $result = null;
931  } else {
932  $result = (int)$s->user_id;
933  }
934 
935  self::$idCacheByName[$name] = $result;
936 
937  if ( count( self::$idCacheByName ) > 1000 ) {
938  self::$idCacheByName = [];
939  }
940 
941  return $result;
942  }
943 
947  public static function resetIdByNameCache() {
948  self::$idCacheByName = [];
949  }
950 
967  public static function isIP( $name ) {
968  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
969  || IP::isIPv6( $name );
970  }
971 
978  public function isIPRange() {
979  return IP::isValidRange( $this->mName );
980  }
981 
993  public static function isValidUserName( $name ) {
994  global $wgMaxNameChars;
995 
996  if ( $name == ''
997  || self::isIP( $name )
998  || strpos( $name, '/' ) !== false
999  || strlen( $name ) > $wgMaxNameChars
1000  || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
1001  ) {
1002  return false;
1003  }
1004 
1005  // Ensure that the name can't be misresolved as a different title,
1006  // such as with extra namespace keys at the start.
1007  $parsed = Title::newFromText( $name );
1008  if ( is_null( $parsed )
1009  || $parsed->getNamespace()
1010  || strcmp( $name, $parsed->getPrefixedText() ) ) {
1011  return false;
1012  }
1013 
1014  // Check an additional blacklist of troublemaker characters.
1015  // Should these be merged into the title char list?
1016  $unicodeBlacklist = '/[' .
1017  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
1018  '\x{00a0}' . # non-breaking space
1019  '\x{2000}-\x{200f}' . # various whitespace
1020  '\x{2028}-\x{202f}' . # breaks and control chars
1021  '\x{3000}' . # ideographic space
1022  '\x{e000}-\x{f8ff}' . # private use
1023  ']/u';
1024  if ( preg_match( $unicodeBlacklist, $name ) ) {
1025  return false;
1026  }
1027 
1028  return true;
1029  }
1030 
1042  public static function isUsableName( $name ) {
1043  global $wgReservedUsernames;
1044  // Must be a valid username, obviously ;)
1045  if ( !self::isValidUserName( $name ) ) {
1046  return false;
1047  }
1048 
1049  static $reservedUsernames = false;
1050  if ( !$reservedUsernames ) {
1051  $reservedUsernames = $wgReservedUsernames;
1052  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
1053  }
1054 
1055  // Certain names may be reserved for batch processes.
1056  foreach ( $reservedUsernames as $reserved ) {
1057  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1058  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1059  }
1060  if ( $reserved == $name ) {
1061  return false;
1062  }
1063  }
1064  return true;
1065  }
1066 
1077  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1078  if ( $groups === [] ) {
1079  return UserArrayFromResult::newFromIDs( [] );
1080  }
1081 
1082  $groups = array_unique( (array)$groups );
1083  $limit = min( 5000, $limit );
1084 
1085  $conds = [ 'ug_group' => $groups ];
1086  if ( $after !== null ) {
1087  $conds[] = 'ug_user > ' . (int)$after;
1088  }
1089 
1090  $dbr = wfGetDB( DB_REPLICA );
1091  $ids = $dbr->selectFieldValues(
1092  'user_groups',
1093  'ug_user',
1094  $conds,
1095  __METHOD__,
1096  [
1097  'DISTINCT' => true,
1098  'ORDER BY' => 'ug_user',
1099  'LIMIT' => $limit,
1100  ]
1101  ) ?: [];
1102  return UserArray::newFromIDs( $ids );
1103  }
1104 
1117  public static function isCreatableName( $name ) {
1119 
1120  // Ensure that the username isn't longer than 235 bytes, so that
1121  // (at least for the builtin skins) user javascript and css files
1122  // will work. (T25080)
1123  if ( strlen( $name ) > 235 ) {
1124  wfDebugLog( 'username', __METHOD__ .
1125  ": '$name' invalid due to length" );
1126  return false;
1127  }
1128 
1129  // Preg yells if you try to give it an empty string
1130  if ( $wgInvalidUsernameCharacters !== '' &&
1131  preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name )
1132  ) {
1133  wfDebugLog( 'username', __METHOD__ .
1134  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1135  return false;
1136  }
1137 
1138  return self::isUsableName( $name );
1139  }
1140 
1147  public function isValidPassword( $password ) {
1148  // simple boolean wrapper for checkPasswordValidity
1149  return $this->checkPasswordValidity( $password )->isGood();
1150  }
1151 
1159  public function getPasswordValidity( $password ) {
1160  wfDeprecated( __METHOD__, '1.33' );
1161 
1162  $result = $this->checkPasswordValidity( $password );
1163  if ( $result->isGood() ) {
1164  return true;
1165  }
1166 
1167  $messages = [];
1168  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1169  $messages[] = $error['message'];
1170  }
1171  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1172  $messages[] = $warning['message'];
1173  }
1174  if ( count( $messages ) === 1 ) {
1175  return $messages[0];
1176  }
1177 
1178  return $messages;
1179  }
1180 
1202  public function checkPasswordValidity( $password ) {
1203  global $wgPasswordPolicy;
1204 
1205  $upp = new UserPasswordPolicy(
1206  $wgPasswordPolicy['policies'],
1207  $wgPasswordPolicy['checks']
1208  );
1209 
1210  $status = Status::newGood( [] );
1211  $result = false; // init $result to false for the internal checks
1212 
1213  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1214  $status->error( $result );
1215  return $status;
1216  }
1217 
1218  if ( $result === false ) {
1219  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1220  return $status;
1221  }
1222 
1223  if ( $result === true ) {
1224  return $status;
1225  }
1226 
1227  $status->error( $result );
1228  return $status; // the isValidPassword hook set a string $result and returned true
1229  }
1230 
1244  public static function getCanonicalName( $name, $validate = 'valid' ) {
1245  // Force usernames to capital
1246  $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1247 
1248  # Reject names containing '#'; these will be cleaned up
1249  # with title normalisation, but then it's too late to
1250  # check elsewhere
1251  if ( strpos( $name, '#' ) !== false ) {
1252  return false;
1253  }
1254 
1255  // Clean up name according to title rules,
1256  // but only when validation is requested (T14654)
1257  $t = ( $validate !== false ) ?
1259  // Check for invalid titles
1260  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1261  return false;
1262  }
1263 
1264  $name = $t->getText();
1265 
1266  switch ( $validate ) {
1267  case false:
1268  break;
1269  case 'valid':
1270  if ( !self::isValidUserName( $name ) ) {
1271  $name = false;
1272  }
1273  break;
1274  case 'usable':
1275  if ( !self::isUsableName( $name ) ) {
1276  $name = false;
1277  }
1278  break;
1279  case 'creatable':
1280  if ( !self::isCreatableName( $name ) ) {
1281  $name = false;
1282  }
1283  break;
1284  default:
1285  throw new InvalidArgumentException(
1286  'Invalid parameter value for $validate in ' . __METHOD__ );
1287  }
1288  return $name;
1289  }
1290 
1297  public static function randomPassword() {
1298  global $wgMinimalPasswordLength;
1300  }
1301 
1310  public function loadDefaults( $name = false ) {
1311  $this->mId = 0;
1312  $this->mName = $name;
1313  $this->mActorId = null;
1314  $this->mRealName = '';
1315  $this->mEmail = '';
1316  $this->mOptionOverrides = null;
1317  $this->mOptionsLoaded = false;
1318 
1319  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1320  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1321  if ( $loggedOut !== 0 ) {
1322  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1323  } else {
1324  $this->mTouched = '1'; # Allow any pages to be cached
1325  }
1326 
1327  $this->mToken = null; // Don't run cryptographic functions till we need a token
1328  $this->mEmailAuthenticated = null;
1329  $this->mEmailToken = '';
1330  $this->mEmailTokenExpires = null;
1331  $this->mRegistration = wfTimestamp( TS_MW );
1332  $this->mGroupMemberships = [];
1333 
1334  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1335  }
1336 
1349  public function isItemLoaded( $item, $all = 'all' ) {
1350  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1351  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1352  }
1353 
1359  protected function setItemLoaded( $item ) {
1360  if ( is_array( $this->mLoadedItems ) ) {
1361  $this->mLoadedItems[$item] = true;
1362  }
1363  }
1364 
1370  private function loadFromSession() {
1371  // Deprecated hook
1372  $result = null;
1373  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1374  if ( $result !== null ) {
1375  return $result;
1376  }
1377 
1378  // MediaWiki\Session\Session already did the necessary authentication of the user
1379  // returned here, so just use it if applicable.
1380  $session = $this->getRequest()->getSession();
1381  $user = $session->getUser();
1382  if ( $user->isLoggedIn() ) {
1383  $this->loadFromUserObject( $user );
1384  if ( $user->isBlocked() ) {
1385  // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1386  // every session load, because an autoblocked editor might not edit again from the same
1387  // IP address after being blocked.
1388  $this->trackBlockWithCookie();
1389  }
1390 
1391  // Other code expects these to be set in the session, so set them.
1392  $session->set( 'wsUserID', $this->getId() );
1393  $session->set( 'wsUserName', $this->getName() );
1394  $session->set( 'wsToken', $this->getToken() );
1395 
1396  return true;
1397  }
1398 
1399  return false;
1400  }
1401 
1405  public function trackBlockWithCookie() {
1406  $block = $this->getBlock();
1407 
1408  if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null
1409  && $block->shouldTrackWithCookie( $this->isAnon() )
1410  ) {
1411  $block->setCookie( $this->getRequest()->response() );
1412  }
1413  }
1414 
1422  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1423  // Paranoia
1424  $this->mId = intval( $this->mId );
1425 
1426  if ( !$this->mId ) {
1427  // Anonymous users are not in the database
1428  $this->loadDefaults();
1429  return false;
1430  }
1431 
1432  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1433  $db = wfGetDB( $index );
1434 
1435  $userQuery = self::getQueryInfo();
1436  $s = $db->selectRow(
1437  $userQuery['tables'],
1438  $userQuery['fields'],
1439  [ 'user_id' => $this->mId ],
1440  __METHOD__,
1441  $options,
1442  $userQuery['joins']
1443  );
1444 
1445  $this->queryFlagsUsed = $flags;
1446  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1447 
1448  if ( $s !== false ) {
1449  // Initialise user table data
1450  $this->loadFromRow( $s );
1451  $this->mGroupMemberships = null; // deferred
1452  $this->getEditCount(); // revalidation for nulls
1453  return true;
1454  }
1455 
1456  // Invalid user_id
1457  $this->mId = 0;
1458  $this->loadDefaults();
1459 
1460  return false;
1461  }
1462 
1475  protected function loadFromRow( $row, $data = null ) {
1477 
1478  if ( !is_object( $row ) ) {
1479  throw new InvalidArgumentException( '$row must be an object' );
1480  }
1481 
1482  $all = true;
1483 
1484  $this->mGroupMemberships = null; // deferred
1485 
1486  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
1487  // but it does little harm and might be needed for write callers loading a User.
1489  if ( isset( $row->actor_id ) ) {
1490  $this->mActorId = (int)$row->actor_id;
1491  if ( $this->mActorId !== 0 ) {
1492  $this->mFrom = 'actor';
1493  }
1494  $this->setItemLoaded( 'actor' );
1495  } else {
1496  $all = false;
1497  }
1498  }
1499 
1500  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1501  $this->mName = $row->user_name;
1502  $this->mFrom = 'name';
1503  $this->setItemLoaded( 'name' );
1504  } else {
1505  $all = false;
1506  }
1507 
1508  if ( isset( $row->user_real_name ) ) {
1509  $this->mRealName = $row->user_real_name;
1510  $this->setItemLoaded( 'realname' );
1511  } else {
1512  $all = false;
1513  }
1514 
1515  if ( isset( $row->user_id ) ) {
1516  $this->mId = intval( $row->user_id );
1517  if ( $this->mId !== 0 ) {
1518  $this->mFrom = 'id';
1519  }
1520  $this->setItemLoaded( 'id' );
1521  } else {
1522  $all = false;
1523  }
1524 
1525  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1526  self::$idCacheByName[$row->user_name] = $row->user_id;
1527  }
1528 
1529  if ( isset( $row->user_editcount ) ) {
1530  $this->mEditCount = $row->user_editcount;
1531  } else {
1532  $all = false;
1533  }
1534 
1535  if ( isset( $row->user_touched ) ) {
1536  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1537  } else {
1538  $all = false;
1539  }
1540 
1541  if ( isset( $row->user_token ) ) {
1542  // The definition for the column is binary(32), so trim the NULs
1543  // that appends. The previous definition was char(32), so trim
1544  // spaces too.
1545  $this->mToken = rtrim( $row->user_token, " \0" );
1546  if ( $this->mToken === '' ) {
1547  $this->mToken = null;
1548  }
1549  } else {
1550  $all = false;
1551  }
1552 
1553  if ( isset( $row->user_email ) ) {
1554  $this->mEmail = $row->user_email;
1555  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1556  $this->mEmailToken = $row->user_email_token;
1557  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1558  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1559  } else {
1560  $all = false;
1561  }
1562 
1563  if ( $all ) {
1564  $this->mLoadedItems = true;
1565  }
1566 
1567  if ( is_array( $data ) ) {
1568  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1569  if ( $data['user_groups'] === [] ) {
1570  $this->mGroupMemberships = [];
1571  } else {
1572  $firstGroup = reset( $data['user_groups'] );
1573  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1574  $this->mGroupMemberships = [];
1575  foreach ( $data['user_groups'] as $row ) {
1576  $ugm = UserGroupMembership::newFromRow( (object)$row );
1577  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1578  }
1579  }
1580  }
1581  }
1582  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1583  $this->loadOptions( $data['user_properties'] );
1584  }
1585  }
1586  }
1587 
1593  protected function loadFromUserObject( $user ) {
1594  $user->load();
1595  foreach ( self::$mCacheVars as $var ) {
1596  $this->$var = $user->$var;
1597  }
1598  }
1599 
1603  private function loadGroups() {
1604  if ( is_null( $this->mGroupMemberships ) ) {
1605  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1606  ? wfGetDB( DB_MASTER )
1607  : wfGetDB( DB_REPLICA );
1608  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1609  $this->mId, $db );
1610  }
1611  }
1612 
1627  public function addAutopromoteOnceGroups( $event ) {
1629 
1630  if ( wfReadOnly() || !$this->getId() ) {
1631  return [];
1632  }
1633 
1634  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1635  if ( $toPromote === [] ) {
1636  return [];
1637  }
1638 
1639  if ( !$this->checkAndSetTouched() ) {
1640  return []; // raced out (bug T48834)
1641  }
1642 
1643  $oldGroups = $this->getGroups(); // previous groups
1644  $oldUGMs = $this->getGroupMemberships();
1645  foreach ( $toPromote as $group ) {
1646  $this->addGroup( $group );
1647  }
1648  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1649  $newUGMs = $this->getGroupMemberships();
1650 
1651  // update groups in external authentication database
1652  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1653 
1654  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1655  $logEntry->setPerformer( $this );
1656  $logEntry->setTarget( $this->getUserPage() );
1657  $logEntry->setParameters( [
1658  '4::oldgroups' => $oldGroups,
1659  '5::newgroups' => $newGroups,
1660  ] );
1661  $logid = $logEntry->insert();
1662  if ( $wgAutopromoteOnceLogInRC ) {
1663  $logEntry->publish( $logid );
1664  }
1665 
1666  return $toPromote;
1667  }
1668 
1678  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1679  if ( $this->mTouched ) {
1680  // CAS check: only update if the row wasn't changed sicne it was loaded.
1681  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1682  }
1683 
1684  return $conditions;
1685  }
1686 
1696  protected function checkAndSetTouched() {
1697  $this->load();
1698 
1699  if ( !$this->mId ) {
1700  return false; // anon
1701  }
1702 
1703  // Get a new user_touched that is higher than the old one
1704  $newTouched = $this->newTouchedTimestamp();
1705 
1706  $dbw = wfGetDB( DB_MASTER );
1707  $dbw->update( 'user',
1708  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1709  $this->makeUpdateConditions( $dbw, [
1710  'user_id' => $this->mId,
1711  ] ),
1712  __METHOD__
1713  );
1714  $success = ( $dbw->affectedRows() > 0 );
1715 
1716  if ( $success ) {
1717  $this->mTouched = $newTouched;
1718  $this->clearSharedCache();
1719  } else {
1720  // Clears on failure too since that is desired if the cache is stale
1721  $this->clearSharedCache( 'refresh' );
1722  }
1723 
1724  return $success;
1725  }
1726 
1734  public function clearInstanceCache( $reloadFrom = false ) {
1735  $this->mNewtalk = -1;
1736  $this->mDatePreference = null;
1737  $this->mBlockedby = -1; # Unset
1738  $this->mHash = false;
1739  $this->mRights = null;
1740  $this->mEffectiveGroups = null;
1741  $this->mImplicitGroups = null;
1742  $this->mGroupMemberships = null;
1743  $this->mOptions = null;
1744  $this->mOptionsLoaded = false;
1745  $this->mEditCount = null;
1746 
1747  if ( $reloadFrom ) {
1748  $this->mLoadedItems = [];
1749  $this->mFrom = $reloadFrom;
1750  }
1751  }
1752 
1754  private static $defOpt = null;
1756  private static $defOptLang = null;
1757 
1764  public static function resetGetDefaultOptionsForTestsOnly() {
1765  Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
1766  self::$defOpt = null;
1767  self::$defOptLang = null;
1768  }
1769 
1776  public static function getDefaultOptions() {
1778 
1779  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1780  if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
1781  // The content language does not change (and should not change) mid-request, but the
1782  // unit tests change it anyway, and expect this method to return values relevant to the
1783  // current content language.
1784  return self::$defOpt;
1785  }
1786 
1787  self::$defOpt = $wgDefaultUserOptions;
1788  // Default language setting
1789  self::$defOptLang = $contLang->getCode();
1790  self::$defOpt['language'] = self::$defOptLang;
1791  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1792  if ( $langCode === $contLang->getCode() ) {
1793  self::$defOpt['variant'] = $langCode;
1794  } else {
1795  self::$defOpt["variant-$langCode"] = $langCode;
1796  }
1797  }
1798 
1799  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1800  // since extensions may change the set of searchable namespaces depending
1801  // on user groups/permissions.
1802  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1803  self::$defOpt['searchNs' . $nsnum] = (bool)$val;
1804  }
1805  self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1806 
1807  Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
1808 
1809  return self::$defOpt;
1810  }
1811 
1818  public static function getDefaultOption( $opt ) {
1819  $defOpts = self::getDefaultOptions();
1820  return $defOpts[$opt] ?? null;
1821  }
1822 
1829  private function getBlockedStatus( $fromReplica = true ) {
1831 
1832  if ( $this->mBlockedby != -1 ) {
1833  return;
1834  }
1835 
1836  wfDebug( __METHOD__ . ": checking...\n" );
1837 
1838  // Initialize data...
1839  // Otherwise something ends up stomping on $this->mBlockedby when
1840  // things get lazy-loaded later, causing false positive block hits
1841  // due to -1 !== 0. Probably session-related... Nothing should be
1842  // overwriting mBlockedby, surely?
1843  $this->load();
1844 
1845  # We only need to worry about passing the IP address to the Block generator if the
1846  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1847  # know which IP address they're actually coming from
1848  $ip = null;
1849  $sessionUser = RequestContext::getMain()->getUser();
1850  // the session user is set up towards the end of Setup.php. Until then,
1851  // assume it's a logged-out user.
1852  $globalUserName = $sessionUser->isSafeToLoad()
1853  ? $sessionUser->getName()
1854  : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1855  if ( $this->getName() === $globalUserName && !$this->isAllowed( 'ipblock-exempt' ) ) {
1856  $ip = $this->getRequest()->getIP();
1857  }
1858 
1859  // User/IP blocking
1860  $block = Block::newFromTarget( $this, $ip, !$fromReplica );
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
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(), !$fromReplica );
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->setReason( wfMessage( 'xffblockreason', $block->getReason() )->plain() );
1902  }
1903  }
1904 
1905  if ( !$block instanceof Block
1906  && $ip !== null
1907  && $this->isAnon()
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->getReason();
1924  $this->mHideName = $block->getHideName();
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  // phan is confused because &can-bypass's value is a bool, so it assumes
2209  // that $userLimit is also a bool here.
2210  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2211  list( $max, $period ) = $userLimit;
2212  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2213  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2214  }
2215 
2216  // ip-based limits for all ping-limitable users
2217  if ( isset( $limits['ip-all'] ) ) {
2218  $ip = $this->getRequest()->getIP();
2219  // ignore if user limit is more permissive
2220  if ( $isNewbie || $userLimit === false
2221  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2222  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2223  }
2224  }
2225 
2226  // subnet-based limits for all ping-limitable users
2227  if ( isset( $limits['subnet-all'] ) ) {
2228  $ip = $this->getRequest()->getIP();
2229  $subnet = IP::getSubnet( $ip );
2230  if ( $subnet !== false ) {
2231  // ignore if user limit is more permissive
2232  if ( $isNewbie || $userLimit === false
2233  || $limits['ip-all'][0] / $limits['ip-all'][1]
2234  > $userLimit[0] / $userLimit[1] ) {
2235  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2236  }
2237  }
2238  }
2239 
2240  $triggered = false;
2241  foreach ( $keys as $key => $limit ) {
2242  // phan is confused because &can-bypass's value is a bool, so it assumes
2243  // that $userLimit is also a bool here.
2244  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2245  list( $max, $period ) = $limit;
2246  $summary = "(limit $max in {$period}s)";
2247  $count = $cache->get( $key );
2248  // Already pinged?
2249  if ( $count ) {
2250  if ( $count >= $max ) {
2251  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2252  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2253  $triggered = true;
2254  } else {
2255  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2256  }
2257  } else {
2258  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2259  if ( $incrBy > 0 ) {
2260  $cache->add( $key, 0, intval( $period ) ); // first ping
2261  }
2262  }
2263  if ( $incrBy > 0 ) {
2264  $cache->incr( $key, $incrBy );
2265  }
2266  }
2267 
2268  return $triggered;
2269  }
2270 
2278  public function isBlocked( $fromReplica = true ) {
2279  return $this->getBlock( $fromReplica ) instanceof Block &&
2280  $this->getBlock()->appliesToRight( 'edit' );
2281  }
2282 
2289  public function getBlock( $fromReplica = true ) {
2290  $this->getBlockedStatus( $fromReplica );
2291  return $this->mBlock instanceof Block ? $this->mBlock : null;
2292  }
2293 
2306  public function isBlockedFrom( $title, $fromReplica = false ) {
2307  return MediaWikiServices::getInstance()->getPermissionManager()
2308  ->isBlockedFrom( $this, $title, $fromReplica );
2309  }
2310 
2315  public function blockedBy() {
2316  $this->getBlockedStatus();
2317  return $this->mBlockedby;
2318  }
2319 
2324  public function blockedFor() {
2325  $this->getBlockedStatus();
2326  return $this->mBlockreason;
2327  }
2328 
2333  public function getBlockId() {
2334  $this->getBlockedStatus();
2335  return ( $this->mBlock ? $this->mBlock->getId() : false );
2336  }
2337 
2346  public function isBlockedGlobally( $ip = '' ) {
2347  return $this->getGlobalBlock( $ip ) instanceof Block;
2348  }
2349 
2360  public function getGlobalBlock( $ip = '' ) {
2361  if ( $this->mGlobalBlock !== null ) {
2362  return $this->mGlobalBlock ?: null;
2363  }
2364  // User is already an IP?
2365  if ( IP::isIPAddress( $this->getName() ) ) {
2366  $ip = $this->getName();
2367  } elseif ( !$ip ) {
2368  $ip = $this->getRequest()->getIP();
2369  }
2370  // Avoid PHP 7.1 warning of passing $this by reference
2371  $user = $this;
2372  $blocked = false;
2373  $block = null;
2374  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2375 
2376  if ( $blocked && $block === null ) {
2377  // back-compat: UserIsBlockedGlobally didn't have $block param first
2378  $block = new Block( [
2379  'address' => $ip,
2380  'systemBlock' => 'global-block'
2381  ] );
2382  }
2383 
2384  $this->mGlobalBlock = $blocked ? $block : false;
2385  return $this->mGlobalBlock ?: null;
2386  }
2387 
2393  public function isLocked() {
2394  if ( $this->mLocked !== null ) {
2395  return $this->mLocked;
2396  }
2397  // Reset for hook
2398  $this->mLocked = false;
2399  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2400  return $this->mLocked;
2401  }
2402 
2408  public function isHidden() {
2409  if ( $this->mHideName !== null ) {
2410  return (bool)$this->mHideName;
2411  }
2412  $this->getBlockedStatus();
2413  if ( !$this->mHideName ) {
2414  // Reset for hook
2415  $this->mHideName = false;
2416  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2417  }
2418  return (bool)$this->mHideName;
2419  }
2420 
2425  public function getId() {
2426  if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2427  // Special case, we know the user is anonymous
2428  return 0;
2429  }
2430 
2431  if ( !$this->isItemLoaded( 'id' ) ) {
2432  // Don't load if this was initialized from an ID
2433  $this->load();
2434  }
2435 
2436  return (int)$this->mId;
2437  }
2438 
2443  public function setId( $v ) {
2444  $this->mId = $v;
2445  $this->clearInstanceCache( 'id' );
2446  }
2447 
2452  public function getName() {
2453  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2454  // Special case optimisation
2455  return $this->mName;
2456  }
2457 
2458  $this->load();
2459  if ( $this->mName === false ) {
2460  // Clean up IPs
2461  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2462  }
2463 
2464  return $this->mName;
2465  }
2466 
2480  public function setName( $str ) {
2481  $this->load();
2482  $this->mName = $str;
2483  }
2484 
2491  public function getActorId( IDatabase $dbw = null ) {
2493 
2494  // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
2495  // but it does little harm and might be needed for write callers loading a User.
2497  return 0;
2498  }
2499 
2500  if ( !$this->isItemLoaded( 'actor' ) ) {
2501  $this->load();
2502  }
2503 
2504  // Currently $this->mActorId might be null if $this was loaded from a
2505  // cache entry that was written when $wgActorTableSchemaMigrationStage
2506  // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
2507  // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
2508  // has been removed), that condition may be removed.
2509  if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2510  $q = [
2511  'actor_user' => $this->getId() ?: null,
2512  'actor_name' => (string)$this->getName(),
2513  ];
2514  if ( $dbw ) {
2515  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2516  throw new CannotCreateActorException(
2517  'Cannot create an actor for a usable name that is not an existing user'
2518  );
2519  }
2520  if ( $q['actor_name'] === '' ) {
2521  throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2522  }
2523  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2524  if ( $dbw->affectedRows() ) {
2525  $this->mActorId = (int)$dbw->insertId();
2526  } else {
2527  // Outdated cache?
2528  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2529  $this->mActorId = (int)$dbw->selectField(
2530  'actor',
2531  'actor_id',
2532  $q,
2533  __METHOD__,
2534  [ 'LOCK IN SHARE MODE' ]
2535  );
2536  if ( !$this->mActorId ) {
2537  throw new CannotCreateActorException(
2538  "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2539  );
2540  }
2541  }
2542  $this->invalidateCache();
2543  } else {
2544  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2545  $db = wfGetDB( $index );
2546  $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2547  }
2548  $this->setItemLoaded( 'actor' );
2549  }
2550 
2551  return (int)$this->mActorId;
2552  }
2553 
2558  public function getTitleKey() {
2559  return str_replace( ' ', '_', $this->getName() );
2560  }
2561 
2566  public function getNewtalk() {
2567  $this->load();
2568 
2569  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2570  if ( $this->mNewtalk === -1 ) {
2571  $this->mNewtalk = false; # reset talk page status
2572 
2573  // Check memcached separately for anons, who have no
2574  // entire User object stored in there.
2575  if ( !$this->mId ) {
2576  global $wgDisableAnonTalk;
2577  if ( $wgDisableAnonTalk ) {
2578  // Anon newtalk disabled by configuration.
2579  $this->mNewtalk = false;
2580  } else {
2581  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2582  }
2583  } else {
2584  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2585  }
2586  }
2587 
2588  return (bool)$this->mNewtalk;
2589  }
2590 
2604  public function getNewMessageLinks() {
2605  // Avoid PHP 7.1 warning of passing $this by reference
2606  $user = $this;
2607  $talks = [];
2608  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2609  return $talks;
2610  }
2611 
2612  if ( !$this->getNewtalk() ) {
2613  return [];
2614  }
2615  $utp = $this->getTalkPage();
2616  $dbr = wfGetDB( DB_REPLICA );
2617  // Get the "last viewed rev" timestamp from the oldest message notification
2618  $timestamp = $dbr->selectField( 'user_newtalk',
2619  'MIN(user_last_timestamp)',
2620  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2621  __METHOD__ );
2622  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2623  return [
2624  [
2626  'link' => $utp->getLocalURL(),
2627  'rev' => $rev
2628  ]
2629  ];
2630  }
2631 
2637  public function getNewMessageRevisionId() {
2638  $newMessageRevisionId = null;
2639  $newMessageLinks = $this->getNewMessageLinks();
2640 
2641  // Note: getNewMessageLinks() never returns more than a single link
2642  // and it is always for the same wiki, but we double-check here in
2643  // case that changes some time in the future.
2644  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2645  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2646  && $newMessageLinks[0]['rev']
2647  ) {
2649  $newMessageRevision = $newMessageLinks[0]['rev'];
2650  $newMessageRevisionId = $newMessageRevision->getId();
2651  }
2652 
2653  return $newMessageRevisionId;
2654  }
2655 
2664  protected function checkNewtalk( $field, $id ) {
2665  $dbr = wfGetDB( DB_REPLICA );
2666 
2667  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2668 
2669  return $ok !== false;
2670  }
2671 
2679  protected function updateNewtalk( $field, $id, $curRev = null ) {
2680  // Get timestamp of the talk page revision prior to the current one
2681  $prevRev = $curRev ? $curRev->getPrevious() : false;
2682  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2683  // Mark the user as having new messages since this revision
2684  $dbw = wfGetDB( DB_MASTER );
2685  $dbw->insert( 'user_newtalk',
2686  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2687  __METHOD__,
2688  'IGNORE' );
2689  if ( $dbw->affectedRows() ) {
2690  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2691  return true;
2692  }
2693 
2694  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2695  return false;
2696  }
2697 
2704  protected function deleteNewtalk( $field, $id ) {
2705  $dbw = wfGetDB( DB_MASTER );
2706  $dbw->delete( 'user_newtalk',
2707  [ $field => $id ],
2708  __METHOD__ );
2709  if ( $dbw->affectedRows() ) {
2710  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2711  return true;
2712  }
2713 
2714  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2715  return false;
2716  }
2717 
2724  public function setNewtalk( $val, $curRev = null ) {
2725  if ( wfReadOnly() ) {
2726  return;
2727  }
2728 
2729  $this->load();
2730  $this->mNewtalk = $val;
2731 
2732  if ( $this->isAnon() ) {
2733  $field = 'user_ip';
2734  $id = $this->getName();
2735  } else {
2736  $field = 'user_id';
2737  $id = $this->getId();
2738  }
2739 
2740  if ( $val ) {
2741  $changed = $this->updateNewtalk( $field, $id, $curRev );
2742  } else {
2743  $changed = $this->deleteNewtalk( $field, $id );
2744  }
2745 
2746  if ( $changed ) {
2747  $this->invalidateCache();
2748  }
2749  }
2750 
2757  private function newTouchedTimestamp() {
2758  $time = time();
2759  if ( $this->mTouched ) {
2760  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2761  }
2762 
2763  return wfTimestamp( TS_MW, $time );
2764  }
2765 
2776  public function clearSharedCache( $mode = 'changed' ) {
2777  if ( !$this->getId() ) {
2778  return;
2779  }
2780 
2781  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2782  $key = $this->getCacheKey( $cache );
2783  if ( $mode === 'refresh' ) {
2784  $cache->delete( $key, 1 );
2785  } else {
2786  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2787  if ( $lb->hasOrMadeRecentMasterChanges() ) {
2788  $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
2789  function () use ( $cache, $key ) {
2790  $cache->delete( $key );
2791  },
2792  __METHOD__
2793  );
2794  } else {
2795  $cache->delete( $key );
2796  }
2797  }
2798  }
2799 
2805  public function invalidateCache() {
2806  $this->touch();
2807  $this->clearSharedCache();
2808  }
2809 
2822  public function touch() {
2823  $id = $this->getId();
2824  if ( $id ) {
2825  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2826  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2827  $cache->touchCheckKey( $key );
2828  $this->mQuickTouched = null;
2829  }
2830  }
2831 
2837  public function validateCache( $timestamp ) {
2838  return ( $timestamp >= $this->getTouched() );
2839  }
2840 
2849  public function getTouched() {
2850  $this->load();
2851 
2852  if ( $this->mId ) {
2853  if ( $this->mQuickTouched === null ) {
2854  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2855  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2856 
2857  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2858  }
2859 
2860  return max( $this->mTouched, $this->mQuickTouched );
2861  }
2862 
2863  return $this->mTouched;
2864  }
2865 
2871  public function getDBTouched() {
2872  $this->load();
2873 
2874  return $this->mTouched;
2875  }
2876 
2893  public function setPassword( $str ) {
2894  wfDeprecated( __METHOD__, '1.27' );
2895  return $this->setPasswordInternal( $str );
2896  }
2897 
2906  public function setInternalPassword( $str ) {
2907  wfDeprecated( __METHOD__, '1.27' );
2908  $this->setPasswordInternal( $str );
2909  }
2910 
2919  private function setPasswordInternal( $str ) {
2920  $manager = AuthManager::singleton();
2921 
2922  // If the user doesn't exist yet, fail
2923  if ( !$manager->userExists( $this->getName() ) ) {
2924  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2925  }
2926 
2927  $status = $this->changeAuthenticationData( [
2928  'username' => $this->getName(),
2929  'password' => $str,
2930  'retype' => $str,
2931  ] );
2932  if ( !$status->isGood() ) {
2934  ->info( __METHOD__ . ': Password change rejected: '
2935  . $status->getWikiText( null, null, 'en' ) );
2936  return false;
2937  }
2938 
2939  $this->setOption( 'watchlisttoken', false );
2940  SessionManager::singleton()->invalidateSessionsForUser( $this );
2941 
2942  return true;
2943  }
2944 
2957  public function changeAuthenticationData( array $data ) {
2958  $manager = AuthManager::singleton();
2959  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2960  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2961 
2962  $status = Status::newGood( 'ignored' );
2963  foreach ( $reqs as $req ) {
2964  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2965  }
2966  if ( $status->getValue() === 'ignored' ) {
2967  $status->warning( 'authenticationdatachange-ignored' );
2968  }
2969 
2970  if ( $status->isGood() ) {
2971  foreach ( $reqs as $req ) {
2972  $manager->changeAuthenticationData( $req );
2973  }
2974  }
2975  return $status;
2976  }
2977 
2984  public function getToken( $forceCreation = true ) {
2986 
2987  $this->load();
2988  if ( !$this->mToken && $forceCreation ) {
2989  $this->setToken();
2990  }
2991 
2992  if ( !$this->mToken ) {
2993  // The user doesn't have a token, return null to indicate that.
2994  return null;
2995  }
2996 
2997  if ( $this->mToken === self::INVALID_TOKEN ) {
2998  // We return a random value here so existing token checks are very
2999  // likely to fail.
3000  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
3001  }
3002 
3003  if ( $wgAuthenticationTokenVersion === null ) {
3004  // $wgAuthenticationTokenVersion not in use, so return the raw secret
3005  return $this->mToken;
3006  }
3007 
3008  // $wgAuthenticationTokenVersion in use, so hmac it.
3009  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
3010 
3011  // The raw hash can be overly long. Shorten it up.
3012  $len = max( 32, self::TOKEN_LENGTH );
3013  if ( strlen( $ret ) < $len ) {
3014  // Should never happen, even md5 is 128 bits
3015  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
3016  }
3017 
3018  return substr( $ret, -$len );
3019  }
3020 
3027  public function setToken( $token = false ) {
3028  $this->load();
3029  if ( $this->mToken === self::INVALID_TOKEN ) {
3031  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
3032  } elseif ( !$token ) {
3033  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
3034  } else {
3035  $this->mToken = $token;
3036  }
3037  }
3038 
3047  public function setNewpassword( $str, $throttle = true ) {
3048  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
3049  }
3050 
3055  public function getEmail() {
3056  $this->load();
3057  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
3058  return $this->mEmail;
3059  }
3060 
3066  $this->load();
3067  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
3069  }
3070 
3075  public function setEmail( $str ) {
3076  $this->load();
3077  if ( $str == $this->mEmail ) {
3078  return;
3079  }
3080  $this->invalidateEmail();
3081  $this->mEmail = $str;
3082  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
3083  }
3084 
3092  public function setEmailWithConfirmation( $str ) {
3094 
3095  if ( !$wgEnableEmail ) {
3096  return Status::newFatal( 'emaildisabled' );
3097  }
3098 
3099  $oldaddr = $this->getEmail();
3100  if ( $str === $oldaddr ) {
3101  return Status::newGood( true );
3102  }
3103 
3104  $type = $oldaddr != '' ? 'changed' : 'set';
3105  $notificationResult = null;
3106 
3107  if ( $wgEmailAuthentication && $type === 'changed' ) {
3108  // Send the user an email notifying the user of the change in registered
3109  // email address on their previous email address
3110  $change = $str != '' ? 'changed' : 'removed';
3111  $notificationResult = $this->sendMail(
3112  wfMessage( 'notificationemail_subject_' . $change )->text(),
3113  wfMessage( 'notificationemail_body_' . $change,
3114  $this->getRequest()->getIP(),
3115  $this->getName(),
3116  $str )->text()
3117  );
3118  }
3119 
3120  $this->setEmail( $str );
3121 
3122  if ( $str !== '' && $wgEmailAuthentication ) {
3123  // Send a confirmation request to the new address if needed
3124  $result = $this->sendConfirmationMail( $type );
3125 
3126  if ( $notificationResult !== null ) {
3127  $result->merge( $notificationResult );
3128  }
3129 
3130  if ( $result->isGood() ) {
3131  // Say to the caller that a confirmation and notification mail has been sent
3132  $result->value = 'eauth';
3133  }
3134  } else {
3135  $result = Status::newGood( true );
3136  }
3137 
3138  return $result;
3139  }
3140 
3145  public function getRealName() {
3146  if ( !$this->isItemLoaded( 'realname' ) ) {
3147  $this->load();
3148  }
3149 
3150  return $this->mRealName;
3151  }
3152 
3157  public function setRealName( $str ) {
3158  $this->load();
3159  $this->mRealName = $str;
3160  }
3161 
3172  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3173  global $wgHiddenPrefs;
3174  $this->loadOptions();
3175 
3176  # We want 'disabled' preferences to always behave as the default value for
3177  # users, even if they have set the option explicitly in their settings (ie they
3178  # set it, and then it was disabled removing their ability to change it). But
3179  # we don't want to erase the preferences in the database in case the preference
3180  # is re-enabled again. So don't touch $mOptions, just override the returned value
3181  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3182  return self::getDefaultOption( $oname );
3183  }
3184 
3185  if ( array_key_exists( $oname, $this->mOptions ) ) {
3186  return $this->mOptions[$oname];
3187  }
3188 
3189  return $defaultOverride;
3190  }
3191 
3200  public function getOptions( $flags = 0 ) {
3201  global $wgHiddenPrefs;
3202  $this->loadOptions();
3204 
3205  # We want 'disabled' preferences to always behave as the default value for
3206  # users, even if they have set the option explicitly in their settings (ie they
3207  # set it, and then it was disabled removing their ability to change it). But
3208  # we don't want to erase the preferences in the database in case the preference
3209  # is re-enabled again. So don't touch $mOptions, just override the returned value
3210  foreach ( $wgHiddenPrefs as $pref ) {
3211  $default = self::getDefaultOption( $pref );
3212  if ( $default !== null ) {
3213  $options[$pref] = $default;
3214  }
3215  }
3216 
3217  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3218  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3219  }
3220 
3221  return $options;
3222  }
3223 
3231  public function getBoolOption( $oname ) {
3232  return (bool)$this->getOption( $oname );
3233  }
3234 
3243  public function getIntOption( $oname, $defaultOverride = 0 ) {
3244  $val = $this->getOption( $oname );
3245  if ( $val == '' ) {
3246  $val = $defaultOverride;
3247  }
3248  return intval( $val );
3249  }
3250 
3259  public function setOption( $oname, $val ) {
3260  $this->loadOptions();
3261 
3262  // Explicitly NULL values should refer to defaults
3263  if ( is_null( $val ) ) {
3264  $val = self::getDefaultOption( $oname );
3265  }
3266 
3267  $this->mOptions[$oname] = $val;
3268  }
3269 
3280  public function getTokenFromOption( $oname ) {
3281  global $wgHiddenPrefs;
3282 
3283  $id = $this->getId();
3284  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3285  return false;
3286  }
3287 
3288  $token = $this->getOption( $oname );
3289  if ( !$token ) {
3290  // Default to a value based on the user token to avoid space
3291  // wasted on storing tokens for all users. When this option
3292  // is set manually by the user, only then is it stored.
3293  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3294  }
3295 
3296  return $token;
3297  }
3298 
3308  public function resetTokenFromOption( $oname ) {
3309  global $wgHiddenPrefs;
3310  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3311  return false;
3312  }
3313 
3314  $token = MWCryptRand::generateHex( 40 );
3315  $this->setOption( $oname, $token );
3316  return $token;
3317  }
3318 
3342  public static function listOptionKinds() {
3343  return [
3344  'registered',
3345  'registered-multiselect',
3346  'registered-checkmatrix',
3347  'userjs',
3348  'special',
3349  'unused'
3350  ];
3351  }
3352 
3365  public function getOptionKinds( IContextSource $context, $options = null ) {
3366  $this->loadOptions();
3367  if ( $options === null ) {
3369  }
3370 
3371  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3372  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3373  $mapping = [];
3374 
3375  // Pull out the "special" options, so they don't get converted as
3376  // multiselect or checkmatrix.
3377  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3378  foreach ( $specialOptions as $name => $value ) {
3379  unset( $prefs[$name] );
3380  }
3381 
3382  // Multiselect and checkmatrix options are stored in the database with
3383  // one key per option, each having a boolean value. Extract those keys.
3384  $multiselectOptions = [];
3385  foreach ( $prefs as $name => $info ) {
3386  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3387  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3388  $opts = HTMLFormField::flattenOptions( $info['options'] );
3389  $prefix = $info['prefix'] ?? $name;
3390 
3391  foreach ( $opts as $value ) {
3392  $multiselectOptions["$prefix$value"] = true;
3393  }
3394 
3395  unset( $prefs[$name] );
3396  }
3397  }
3398  $checkmatrixOptions = [];
3399  foreach ( $prefs as $name => $info ) {
3400  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3401  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3402  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3403  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3404  $prefix = $info['prefix'] ?? $name;
3405 
3406  foreach ( $columns as $column ) {
3407  foreach ( $rows as $row ) {
3408  $checkmatrixOptions["$prefix$column-$row"] = true;
3409  }
3410  }
3411 
3412  unset( $prefs[$name] );
3413  }
3414  }
3415 
3416  // $value is ignored
3417  foreach ( $options as $key => $value ) {
3418  if ( isset( $prefs[$key] ) ) {
3419  $mapping[$key] = 'registered';
3420  } elseif ( isset( $multiselectOptions[$key] ) ) {
3421  $mapping[$key] = 'registered-multiselect';
3422  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3423  $mapping[$key] = 'registered-checkmatrix';
3424  } elseif ( isset( $specialOptions[$key] ) ) {
3425  $mapping[$key] = 'special';
3426  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3427  $mapping[$key] = 'userjs';
3428  } else {
3429  $mapping[$key] = 'unused';
3430  }
3431  }
3432 
3433  return $mapping;
3434  }
3435 
3450  public function resetOptions(
3451  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3452  IContextSource $context = null
3453  ) {
3454  $this->load();
3455  $defaultOptions = self::getDefaultOptions();
3456 
3457  if ( !is_array( $resetKinds ) ) {
3458  $resetKinds = [ $resetKinds ];
3459  }
3460 
3461  if ( in_array( 'all', $resetKinds ) ) {
3462  $newOptions = $defaultOptions;
3463  } else {
3464  if ( $context === null ) {
3466  }
3467 
3468  $optionKinds = $this->getOptionKinds( $context );
3469  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3470  $newOptions = [];
3471 
3472  // Use default values for the options that should be deleted, and
3473  // copy old values for the ones that shouldn't.
3474  foreach ( $this->mOptions as $key => $value ) {
3475  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3476  if ( array_key_exists( $key, $defaultOptions ) ) {
3477  $newOptions[$key] = $defaultOptions[$key];
3478  }
3479  } else {
3480  $newOptions[$key] = $value;
3481  }
3482  }
3483  }
3484 
3485  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3486 
3487  $this->mOptions = $newOptions;
3488  $this->mOptionsLoaded = true;
3489  }
3490 
3495  public function getDatePreference() {
3496  // Important migration for old data rows
3497  if ( is_null( $this->mDatePreference ) ) {
3498  global $wgLang;
3499  $value = $this->getOption( 'date' );
3500  $map = $wgLang->getDatePreferenceMigrationMap();
3501  if ( isset( $map[$value] ) ) {
3502  $value = $map[$value];
3503  }
3504  $this->mDatePreference = $value;
3505  }
3506  return $this->mDatePreference;
3507  }
3508 
3515  public function requiresHTTPS() {
3516  global $wgSecureLogin;
3517  if ( !$wgSecureLogin ) {
3518  return false;
3519  }
3520 
3521  $https = $this->getBoolOption( 'prefershttps' );
3522  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3523  if ( $https ) {
3524  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3525  }
3526 
3527  return $https;
3528  }
3529 
3535  public function getStubThreshold() {
3536  global $wgMaxArticleSize; # Maximum article size, in Kb
3537  $threshold = $this->getIntOption( 'stubthreshold' );
3538  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3539  // If they have set an impossible value, disable the preference
3540  // so we can use the parser cache again.
3541  $threshold = 0;
3542  }
3543  return $threshold;
3544  }
3545 
3550  public function getRights() {
3551  if ( is_null( $this->mRights ) ) {
3552  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3553  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3554 
3555  // Deny any rights denied by the user's session, unless this
3556  // endpoint has no sessions.
3557  if ( !defined( 'MW_NO_SESSION' ) ) {
3558  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3559  if ( $allowedRights !== null ) {
3560  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3561  }
3562  }
3563 
3564  Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
3565  // Force reindexation of rights when a hook has unset one of them
3566  $this->mRights = array_values( array_unique( $this->mRights ) );
3567 
3568  // If block disables login, we should also remove any
3569  // extra rights blocked users might have, in case the
3570  // blocked user has a pre-existing session (T129738).
3571  // This is checked here for cases where people only call
3572  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3573  // to give a better error message in the common case.
3574  $config = RequestContext::getMain()->getConfig();
3575  if (
3576  $this->isLoggedIn() &&
3577  $config->get( 'BlockDisablesLogin' ) &&
3578  $this->isBlocked()
3579  ) {
3580  $anon = new User;
3581  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3582  }
3583  }
3584  return $this->mRights;
3585  }
3586 
3593  public function getGroups() {
3594  $this->load();
3595  $this->loadGroups();
3596  return array_keys( $this->mGroupMemberships );
3597  }
3598 
3606  public function getGroupMemberships() {
3607  $this->load();
3608  $this->loadGroups();
3609  return $this->mGroupMemberships;
3610  }
3611 
3619  public function getEffectiveGroups( $recache = false ) {
3620  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3621  $this->mEffectiveGroups = array_unique( array_merge(
3622  $this->getGroups(), // explicit groups
3623  $this->getAutomaticGroups( $recache ) // implicit groups
3624  ) );
3625  // Avoid PHP 7.1 warning of passing $this by reference
3626  $user = $this;
3627  // Hook for additional groups
3628  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3629  // Force reindexation of groups when a hook has unset one of them
3630  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3631  }
3632  return $this->mEffectiveGroups;
3633  }
3634 
3642  public function getAutomaticGroups( $recache = false ) {
3643  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3644  $this->mImplicitGroups = [ '*' ];
3645  if ( $this->getId() ) {
3646  $this->mImplicitGroups[] = 'user';
3647 
3648  $this->mImplicitGroups = array_unique( array_merge(
3649  $this->mImplicitGroups,
3651  ) );
3652  }
3653  if ( $recache ) {
3654  // Assure data consistency with rights/groups,
3655  // as getEffectiveGroups() depends on this function
3656  $this->mEffectiveGroups = null;
3657  }
3658  }
3659  return $this->mImplicitGroups;
3660  }
3661 
3671  public function getFormerGroups() {
3672  $this->load();
3673 
3674  if ( is_null( $this->mFormerGroups ) ) {
3675  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3676  ? wfGetDB( DB_MASTER )
3677  : wfGetDB( DB_REPLICA );
3678  $res = $db->select( 'user_former_groups',
3679  [ 'ufg_group' ],
3680  [ 'ufg_user' => $this->mId ],
3681  __METHOD__ );
3682  $this->mFormerGroups = [];
3683  foreach ( $res as $row ) {
3684  $this->mFormerGroups[] = $row->ufg_group;
3685  }
3686  }
3687 
3688  return $this->mFormerGroups;
3689  }
3690 
3695  public function getEditCount() {
3696  if ( !$this->getId() ) {
3697  return null;
3698  }
3699 
3700  if ( $this->mEditCount === null ) {
3701  /* Populate the count, if it has not been populated yet */
3702  $dbr = wfGetDB( DB_REPLICA );
3703  // check if the user_editcount field has been initialized
3704  $count = $dbr->selectField(
3705  'user', 'user_editcount',
3706  [ 'user_id' => $this->mId ],
3707  __METHOD__
3708  );
3709 
3710  if ( $count === null ) {
3711  // it has not been initialized. do so.
3712  $count = $this->initEditCountInternal();
3713  }
3714  $this->mEditCount = $count;
3715  }
3716  return (int)$this->mEditCount;
3717  }
3718 
3730  public function addGroup( $group, $expiry = null ) {
3731  $this->load();
3732  $this->loadGroups();
3733 
3734  if ( $expiry ) {
3735  $expiry = wfTimestamp( TS_MW, $expiry );
3736  }
3737 
3738  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3739  return false;
3740  }
3741 
3742  // create the new UserGroupMembership and put it in the DB
3743  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3744  if ( !$ugm->insert( true ) ) {
3745  return false;
3746  }
3747 
3748  $this->mGroupMemberships[$group] = $ugm;
3749 
3750  // Refresh the groups caches, and clear the rights cache so it will be
3751  // refreshed on the next call to $this->getRights().
3752  $this->getEffectiveGroups( true );
3753  $this->mRights = null;
3754 
3755  $this->invalidateCache();
3756 
3757  return true;
3758  }
3759 
3766  public function removeGroup( $group ) {
3767  $this->load();
3768 
3769  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3770  return false;
3771  }
3772 
3773  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3774  // delete the membership entry
3775  if ( !$ugm || !$ugm->delete() ) {
3776  return false;
3777  }
3778 
3779  $this->loadGroups();
3780  unset( $this->mGroupMemberships[$group] );
3781 
3782  // Refresh the groups caches, and clear the rights cache so it will be
3783  // refreshed on the next call to $this->getRights().
3784  $this->getEffectiveGroups( true );
3785  $this->mRights = null;
3786 
3787  $this->invalidateCache();
3788 
3789  return true;
3790  }
3791 
3796  public function isLoggedIn() {
3797  return $this->getId() != 0;
3798  }
3799 
3804  public function isAnon() {
3805  return !$this->isLoggedIn();
3806  }
3807 
3812  public function isBot() {
3813  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3814  return true;
3815  }
3816 
3817  $isBot = false;
3818  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3819 
3820  return $isBot;
3821  }
3822 
3829  public function isAllowedAny() {
3830  $permissions = func_get_args();
3831  foreach ( $permissions as $permission ) {
3832  if ( $this->isAllowed( $permission ) ) {
3833  return true;
3834  }
3835  }
3836  return false;
3837  }
3838 
3844  public function isAllowedAll() {
3845  $permissions = func_get_args();
3846  foreach ( $permissions as $permission ) {
3847  if ( !$this->isAllowed( $permission ) ) {
3848  return false;
3849  }
3850  }
3851  return true;
3852  }
3853 
3859  public function isAllowed( $action = '' ) {
3860  if ( $action === '' ) {
3861  return true; // In the spirit of DWIM
3862  }
3863  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3864  // by misconfiguration: 0 == 'foo'
3865  return in_array( $action, $this->getRights(), true );
3866  }
3867 
3872  public function useRCPatrol() {
3873  global $wgUseRCPatrol;
3874  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3875  }
3876 
3881  public function useNPPatrol() {
3883  return (
3885  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3886  );
3887  }
3888 
3893  public function useFilePatrol() {
3895  return (
3897  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3898  );
3899  }
3900 
3906  public function getRequest() {
3907  if ( $this->mRequest ) {
3908  return $this->mRequest;
3909  }
3910 
3911  global $wgRequest;
3912  return $wgRequest;
3913  }
3914 
3923  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3924  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3925  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3926  }
3927  return false;
3928  }
3929 
3937  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3938  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3939  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3940  $this,
3941  [ $title->getSubjectPage(), $title->getTalkPage() ]
3942  );
3943  }
3944  $this->invalidateCache();
3945  }
3946 
3954  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3955  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3956  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3957  $store->removeWatch( $this, $title->getSubjectPage() );
3958  $store->removeWatch( $this, $title->getTalkPage() );
3959  }
3960  $this->invalidateCache();
3961  }
3962 
3971  public function clearNotification( &$title, $oldid = 0 ) {
3973 
3974  // Do nothing if the database is locked to writes
3975  if ( wfReadOnly() ) {
3976  return;
3977  }
3978 
3979  // Do nothing if not allowed to edit the watchlist
3980  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3981  return;
3982  }
3983 
3984  // If we're working on user's talk page, we should update the talk page message indicator
3985  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3986  // Avoid PHP 7.1 warning of passing $this by reference
3987  $user = $this;
3988  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3989  return;
3990  }
3991 
3992  // Try to update the DB post-send and only if needed...
3993  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3994  if ( !$this->getNewtalk() ) {
3995  return; // no notifications to clear
3996  }
3997 
3998  // Delete the last notifications (they stack up)
3999  $this->setNewtalk( false );
4000 
4001  // If there is a new, unseen, revision, use its timestamp
4002  $nextid = $oldid
4003  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
4004  : null;
4005  if ( $nextid ) {
4006  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
4007  }
4008  } );
4009  }
4010 
4011  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4012  return;
4013  }
4014 
4015  if ( $this->isAnon() ) {
4016  // Nothing else to do...
4017  return;
4018  }
4019 
4020  // Only update the timestamp if the page is being watched.
4021  // The query to find out if it is watched is cached both in memcached and per-invocation,
4022  // and when it does have to be executed, it can be on a replica DB
4023  // If this is the user's newtalk page, we always update the timestamp
4024  $force = '';
4025  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
4026  $force = 'force';
4027  }
4028 
4029  MediaWikiServices::getInstance()->getWatchedItemStore()
4030  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
4031  }
4032 
4039  public function clearAllNotifications() {
4041  // Do nothing if not allowed to edit the watchlist
4042  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
4043  return;
4044  }
4045 
4046  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4047  $this->setNewtalk( false );
4048  return;
4049  }
4050 
4051  $id = $this->getId();
4052  if ( !$id ) {
4053  return;
4054  }
4055 
4056  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
4057  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
4058 
4059  // We also need to clear here the "you have new message" notification for the own
4060  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
4061  }
4062 
4068  public function getExperienceLevel() {
4069  global $wgLearnerEdits,
4073 
4074  if ( $this->isAnon() ) {
4075  return false;
4076  }
4077 
4078  $editCount = $this->getEditCount();
4079  $registration = $this->getRegistration();
4080  $now = time();
4081  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
4082  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
4083 
4084  if ( $editCount < $wgLearnerEdits ||
4085  $registration > $learnerRegistration ) {
4086  return 'newcomer';
4087  }
4088 
4089  if ( $editCount > $wgExperiencedUserEdits &&
4090  $registration <= $experiencedRegistration
4091  ) {
4092  return 'experienced';
4093  }
4094 
4095  return 'learner';
4096  }
4097 
4106  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
4107  $this->load();
4108  if ( $this->mId == 0 ) {
4109  return;
4110  }
4111 
4112  $session = $this->getRequest()->getSession();
4113  if ( $request && $session->getRequest() !== $request ) {
4114  $session = $session->sessionWithRequest( $request );
4115  }
4116  $delay = $session->delaySave();
4117 
4118  if ( !$session->getUser()->equals( $this ) ) {
4119  if ( !$session->canSetUser() ) {
4121  ->warning( __METHOD__ .
4122  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
4123  );
4124  return;
4125  }
4126  $session->setUser( $this );
4127  }
4128 
4129  $session->setRememberUser( $rememberMe );
4130  if ( $secure !== null ) {
4131  $session->setForceHTTPS( $secure );
4132  }
4133 
4134  $session->persist();
4135 
4136  ScopedCallback::consume( $delay );
4137  }
4138 
4142  public function logout() {
4143  // Avoid PHP 7.1 warning of passing $this by reference
4144  $user = $this;
4145  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
4146  $this->doLogout();
4147  }
4148  }
4149 
4154  public function doLogout() {
4155  $session = $this->getRequest()->getSession();
4156  if ( !$session->canSetUser() ) {
4158  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
4159  $error = 'immutable';
4160  } elseif ( !$session->getUser()->equals( $this ) ) {
4162  ->warning( __METHOD__ .
4163  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4164  );
4165  // But we still may as well make this user object anon
4166  $this->clearInstanceCache( 'defaults' );
4167  $error = 'wronguser';
4168  } else {
4169  $this->clearInstanceCache( 'defaults' );
4170  $delay = $session->delaySave();
4171  $session->unpersist(); // Clear cookies (T127436)
4172  $session->setLoggedOutTimestamp( time() );
4173  $session->setUser( new User );
4174  $session->set( 'wsUserID', 0 ); // Other code expects this
4175  $session->resetAllTokens();
4176  ScopedCallback::consume( $delay );
4177  $error = false;
4178  }
4179  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4180  'event' => 'logout',
4181  'successful' => $error === false,
4182  'status' => $error ?: 'success',
4183  ] );
4184  }
4185 
4190  public function saveSettings() {
4191  if ( wfReadOnly() ) {
4192  // @TODO: caller should deal with this instead!
4193  // This should really just be an exception.
4195  null,
4196  "Could not update user with ID '{$this->mId}'; DB is read-only."
4197  ) );
4198  return;
4199  }
4200 
4201  $this->load();
4202  if ( $this->mId == 0 ) {
4203  return; // anon
4204  }
4205 
4206  // Get a new user_touched that is higher than the old one.
4207  // This will be used for a CAS check as a last-resort safety
4208  // check against race conditions and replica DB lag.
4209  $newTouched = $this->newTouchedTimestamp();
4210 
4211  $dbw = wfGetDB( DB_MASTER );
4212  $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
4214 
4215  $dbw->update( 'user',
4216  [ /* SET */
4217  'user_name' => $this->mName,
4218  'user_real_name' => $this->mRealName,
4219  'user_email' => $this->mEmail,
4220  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4221  'user_touched' => $dbw->timestamp( $newTouched ),
4222  'user_token' => strval( $this->mToken ),
4223  'user_email_token' => $this->mEmailToken,
4224  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4225  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4226  'user_id' => $this->mId,
4227  ] ), $fname
4228  );
4229 
4230  if ( !$dbw->affectedRows() ) {
4231  // Maybe the problem was a missed cache update; clear it to be safe
4232  $this->clearSharedCache( 'refresh' );
4233  // User was changed in the meantime or loaded with stale data
4234  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4235  LoggerFactory::getInstance( 'preferences' )->warning(
4236  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4237  [ 'user_id' => $this->mId, 'db_flag' => $from ]
4238  );
4239  throw new MWException( "CAS update failed on user_touched. " .
4240  "The version of the user to be saved is older than the current version."
4241  );
4242  }
4243 
4245  $dbw->update(
4246  'actor',
4247  [ 'actor_name' => $this->mName ],
4248  [ 'actor_user' => $this->mId ],
4249  $fname
4250  );
4251  }
4252  } );
4253 
4254  $this->mTouched = $newTouched;
4255  $this->saveOptions();
4256 
4257  Hooks::run( 'UserSaveSettings', [ $this ] );
4258  $this->clearSharedCache();
4259  $this->getUserPage()->purgeSquid();
4260  }
4261 
4268  public function idForName( $flags = 0 ) {
4269  $s = trim( $this->getName() );
4270  if ( $s === '' ) {
4271  return 0;
4272  }
4273 
4274  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4275  ? wfGetDB( DB_MASTER )
4276  : wfGetDB( DB_REPLICA );
4277 
4278  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4279  ? [ 'LOCK IN SHARE MODE' ]
4280  : [];
4281 
4282  $id = $db->selectField( 'user',
4283  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4284 
4285  return (int)$id;
4286  }
4287 
4303  public static function createNew( $name, $params = [] ) {
4304  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4305  if ( isset( $params[$field] ) ) {
4306  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4307  unset( $params[$field] );
4308  }
4309  }
4310 
4311  $user = new User;
4312  $user->load();
4313  $user->setToken(); // init token
4314  if ( isset( $params['options'] ) ) {
4315  $user->mOptions = $params['options'] + (array)$user->mOptions;
4316  unset( $params['options'] );
4317  }
4318  $dbw = wfGetDB( DB_MASTER );
4319 
4320  $noPass = PasswordFactory::newInvalidPassword()->toString();
4321 
4322  $fields = [
4323  'user_name' => $name,
4324  'user_password' => $noPass,
4325  'user_newpassword' => $noPass,
4326  'user_email' => $user->mEmail,
4327  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4328  'user_real_name' => $user->mRealName,
4329  'user_token' => strval( $user->mToken ),
4330  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4331  'user_editcount' => 0,
4332  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4333  ];
4334  foreach ( $params as $name => $value ) {
4335  $fields["user_$name"] = $value;
4336  }
4337 
4338  return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
4339  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4340  if ( $dbw->affectedRows() ) {
4341  $newUser = self::newFromId( $dbw->insertId() );
4342  $newUser->mName = $fields['user_name'];
4343  $newUser->updateActorId( $dbw );
4344  // Load the user from master to avoid replica lag
4345  $newUser->load( self::READ_LATEST );
4346  } else {
4347  $newUser = null;
4348  }
4349  return $newUser;
4350  } );
4351  }
4352 
4379  public function addToDatabase() {
4380  $this->load();
4381  if ( !$this->mToken ) {
4382  $this->setToken(); // init token
4383  }
4384 
4385  if ( !is_string( $this->mName ) ) {
4386  throw new RuntimeException( "User name field is not set." );
4387  }
4388 
4389  $this->mTouched = $this->newTouchedTimestamp();
4390 
4391  $dbw = wfGetDB( DB_MASTER );
4392  $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
4393  $noPass = PasswordFactory::newInvalidPassword()->toString();
4394  $dbw->insert( 'user',
4395  [
4396  'user_name' => $this->mName,
4397  'user_password' => $noPass,
4398  'user_newpassword' => $noPass,
4399  'user_email' => $this->mEmail,
4400  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4401  'user_real_name' => $this->mRealName,
4402  'user_token' => strval( $this->mToken ),
4403  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4404  'user_editcount' => 0,
4405  'user_touched' => $dbw->timestamp( $this->mTouched ),
4406  ], $fname,
4407  [ 'IGNORE' ]
4408  );
4409  if ( !$dbw->affectedRows() ) {
4410  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4411  $this->mId = $dbw->selectField(
4412  'user',
4413  'user_id',
4414  [ 'user_name' => $this->mName ],
4415  $fname,
4416  [ 'LOCK IN SHARE MODE' ]
4417  );
4418  $loaded = false;
4419  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
4420  $loaded = true;
4421  }
4422  if ( !$loaded ) {
4423  throw new MWException( $fname . ": hit a key conflict attempting " .
4424  "to insert user '{$this->mName}' row, but it was not present in select!" );
4425  }
4426  return Status::newFatal( 'userexists' );
4427  }
4428  $this->mId = $dbw->insertId();
4429  self::$idCacheByName[$this->mName] = $this->mId;
4430  $this->updateActorId( $dbw );
4431 
4432  return Status::newGood();
4433  } );
4434  if ( !$status->isGood() ) {
4435  return $status;
4436  }
4437 
4438  // Clear instance cache other than user table data and actor, which is already accurate
4439  $this->clearInstanceCache();
4440 
4441  $this->saveOptions();
4442  return Status::newGood();
4443  }
4444 
4449  private function updateActorId( IDatabase $dbw ) {
4451 
4453  $dbw->insert(
4454  'actor',
4455  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4456  __METHOD__
4457  );
4458  $this->mActorId = (int)$dbw->insertId();
4459  }
4460  }
4461 
4467  public function spreadAnyEditBlock() {
4468  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4469  return $this->spreadBlock();
4470  }
4471 
4472  return false;
4473  }
4474 
4480  protected function spreadBlock() {
4481  wfDebug( __METHOD__ . "()\n" );
4482  $this->load();
4483  if ( $this->mId == 0 ) {
4484  return false;
4485  }
4486 
4487  $userblock = Block::newFromTarget( $this->getName() );
4488  if ( !$userblock ) {
4489  return false;
4490  }
4491 
4492  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4493  }
4494 
4499  public function isBlockedFromCreateAccount() {
4500  $this->getBlockedStatus();
4501  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4502  return $this->mBlock;
4503  }
4504 
4505  # T15611: if the IP address the user is trying to create an account from is
4506  # blocked with createaccount disabled, prevent new account creation there even
4507  # when the user is logged in
4508  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4509  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4510  }
4511  return $this->mBlockedFromCreateAccount instanceof Block
4512  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4513  ? $this->mBlockedFromCreateAccount
4514  : false;
4515  }
4516 
4521  public function isBlockedFromEmailuser() {
4522  $this->getBlockedStatus();
4523  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4524  }
4525 
4532  public function isBlockedFromUpload() {
4533  $this->getBlockedStatus();
4534  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4535  }
4536 
4541  public function isAllowedToCreateAccount() {
4542  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4543  }
4544 
4550  public function getUserPage() {
4551  return Title::makeTitle( NS_USER, $this->getName() );
4552  }
4553 
4559  public function getTalkPage() {
4560  $title = $this->getUserPage();
4561  return $title->getTalkPage();
4562  }
4563 
4569  public function isNewbie() {
4570  return !$this->isAllowed( 'autoconfirmed' );
4571  }
4572 
4579  public function checkPassword( $password ) {
4580  wfDeprecated( __METHOD__, '1.27' );
4581 
4582  $manager = AuthManager::singleton();
4583  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4584  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4585  [
4586  'username' => $this->getName(),
4587  'password' => $password,
4588  ]
4589  );
4590  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4591  switch ( $res->status ) {
4592  case AuthenticationResponse::PASS:
4593  return true;
4594  case AuthenticationResponse::FAIL:
4595  // Hope it's not a PreAuthenticationProvider that failed...
4597  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4598  return false;
4599  default:
4600  throw new BadMethodCallException(
4601  'AuthManager returned a response unsupported by ' . __METHOD__
4602  );
4603  }
4604  }
4605 
4614  public function checkTemporaryPassword( $plaintext ) {
4615  wfDeprecated( __METHOD__, '1.27' );
4616  // Can't check the temporary password individually.
4617  return $this->checkPassword( $plaintext );
4618  }
4619 
4631  public function getEditTokenObject( $salt = '', $request = null ) {
4632  if ( $this->isAnon() ) {
4633  return new LoggedOutEditToken();
4634  }
4635 
4636  if ( !$request ) {
4637  $request = $this->getRequest();
4638  }
4639  return $request->getSession()->getToken( $salt );
4640  }
4641 
4655  public function getEditToken( $salt = '', $request = null ) {
4656  return $this->getEditTokenObject( $salt, $request )->toString();
4657  }
4658 
4671  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4672  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4673  }
4674 
4685  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4686  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4687  return $this->matchEditToken( $val, $salt, $request, $maxage );
4688  }
4689 
4697  public function sendConfirmationMail( $type = 'created' ) {
4698  global $wgLang;
4699  $expiration = null; // gets passed-by-ref and defined in next line.
4700  $token = $this->confirmationToken( $expiration );
4701  $url = $this->confirmationTokenUrl( $token );
4702  $invalidateURL = $this->invalidationTokenUrl( $token );
4703  $this->saveSettings();
4704 
4705  if ( $type == 'created' || $type === false ) {
4706  $message = 'confirmemail_body';
4707  $type = 'created';
4708  } elseif ( $type === true ) {
4709  $message = 'confirmemail_body_changed';
4710  $type = 'changed';
4711  } else {
4712  // Messages: confirmemail_body_changed, confirmemail_body_set
4713  $message = 'confirmemail_body_' . $type;
4714  }
4715 
4716  $mail = [
4717  'subject' => wfMessage( 'confirmemail_subject' )->text(),
4718  'body' => wfMessage( $message,
4719  $this->getRequest()->getIP(),
4720  $this->getName(),
4721  $url,
4722  $wgLang->userTimeAndDate( $expiration, $this ),
4723  $invalidateURL,
4724  $wgLang->userDate( $expiration, $this ),
4725  $wgLang->userTime( $expiration, $this ) )->text(),
4726  'from' => null,
4727  'replyTo' => null,
4728  ];
4729  $info = [
4730  'type' => $type,
4731  'ip' => $this->getRequest()->getIP(),
4732  'confirmURL' => $url,
4733  'invalidateURL' => $invalidateURL,
4734  'expiration' => $expiration
4735  ];
4736 
4737  Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4738  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4739  }
4740 
4752  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4753  global $wgPasswordSender;
4754 
4755  if ( $from instanceof User ) {
4756  $sender = MailAddress::newFromUser( $from );
4757  } else {
4758  $sender = new MailAddress( $wgPasswordSender,
4759  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4760  }
4761  $to = MailAddress::newFromUser( $this );
4762 
4763  return UserMailer::send( $to, $sender, $subject, $body, [
4764  'replyTo' => $replyto,
4765  ] );
4766  }
4767 
4778  protected function confirmationToken( &$expiration ) {
4780  $now = time();
4781  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4782  $expiration = wfTimestamp( TS_MW, $expires );
4783  $this->load();
4784  $token = MWCryptRand::generateHex( 32 );
4785  $hash = md5( $token );
4786  $this->mEmailToken = $hash;
4787  $this->mEmailTokenExpires = $expiration;
4788  return $token;
4789  }
4790 
4796  protected function confirmationTokenUrl( $token ) {
4797  return $this->getTokenUrl( 'ConfirmEmail', $token );
4798  }
4799 
4805  protected function invalidationTokenUrl( $token ) {
4806  return $this->getTokenUrl( 'InvalidateEmail', $token );
4807  }
4808 
4823  protected function getTokenUrl( $page, $token ) {
4824  // Hack to bypass localization of 'Special:'
4825  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4826  return $title->getCanonicalURL();
4827  }
4828 
4836  public function confirmEmail() {
4837  // Check if it's already confirmed, so we don't touch the database
4838  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4839  if ( !$this->isEmailConfirmed() ) {
4841  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4842  }
4843  return true;
4844  }
4845 
4853  public function invalidateEmail() {
4854  $this->load();
4855  $this->mEmailToken = null;
4856  $this->mEmailTokenExpires = null;
4857  $this->setEmailAuthenticationTimestamp( null );
4858  $this->mEmail = '';
4859  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4860  return true;
4861  }
4862 
4867  public function setEmailAuthenticationTimestamp( $timestamp ) {
4868  $this->load();
4869  $this->mEmailAuthenticated = $timestamp;
4870  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4871  }
4872 
4878  public function canSendEmail() {
4880  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4881  return false;
4882  }
4883  $canSend = $this->isEmailConfirmed();
4884  // Avoid PHP 7.1 warning of passing $this by reference
4885  $user = $this;
4886  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4887  return $canSend;
4888  }
4889 
4895  public function canReceiveEmail() {
4896  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4897  }
4898 
4909  public function isEmailConfirmed() {
4910  global $wgEmailAuthentication;
4911  $this->load();
4912  // Avoid PHP 7.1 warning of passing $this by reference
4913  $user = $this;
4914  $confirmed = true;
4915  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4916  if ( $this->isAnon() ) {
4917  return false;
4918  }
4919  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4920  return false;
4921  }
4923  return false;
4924  }
4925  return true;
4926  }
4927 
4928  return $confirmed;
4929  }
4930 
4935  public function isEmailConfirmationPending() {
4936  global $wgEmailAuthentication;
4937  return $wgEmailAuthentication &&
4938  !$this->isEmailConfirmed() &&
4939  $this->mEmailToken &&
4940  $this->mEmailTokenExpires > wfTimestamp();
4941  }
4942 
4950  public function getRegistration() {
4951  if ( $this->isAnon() ) {
4952  return false;
4953  }
4954  $this->load();
4955  return $this->mRegistration;
4956  }
4957 
4964  public function getFirstEditTimestamp() {
4965  return $this->getEditTimestamp( true );
4966  }
4967 
4975  public function getLatestEditTimestamp() {
4976  return $this->getEditTimestamp( false );
4977  }
4978 
4986  private function getEditTimestamp( $first ) {
4987  if ( $this->getId() == 0 ) {
4988  return false; // anons
4989  }
4990  $dbr = wfGetDB( DB_REPLICA );
4991  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4992  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4993  ? 'revactor_timestamp' : 'rev_timestamp';
4994  $sortOrder = $first ? 'ASC' : 'DESC';
4995  $time = $dbr->selectField(
4996  [ 'revision' ] + $actorWhere['tables'],
4997  $tsField,
4998  [ $actorWhere['conds'] ],
4999  __METHOD__,
5000  [ 'ORDER BY' => "$tsField $sortOrder" ],
5001  $actorWhere['joins']
5002  );
5003  if ( !$time ) {
5004  return false; // no edits
5005  }
5006  return wfTimestamp( TS_MW, $time );
5007  }
5008 
5015  public static function getGroupPermissions( $groups ) {
5017  $rights = [];
5018  // grant every granted permission first
5019  foreach ( $groups as $group ) {
5020  if ( isset( $wgGroupPermissions[$group] ) ) {
5021  $rights = array_merge( $rights,
5022  // array_filter removes empty items
5023  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
5024  }
5025  }
5026  // now revoke the revoked permissions
5027  foreach ( $groups as $group ) {
5028  if ( isset( $wgRevokePermissions[$group] ) ) {
5029  $rights = array_diff( $rights,
5030  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
5031  }
5032  }
5033  return array_unique( $rights );
5034  }
5035 
5042  public static function getGroupsWithPermission( $role ) {
5043  global $wgGroupPermissions;
5044  $allowedGroups = [];
5045  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
5046  if ( self::groupHasPermission( $group, $role ) ) {
5047  $allowedGroups[] = $group;
5048  }
5049  }
5050  return $allowedGroups;
5051  }
5052 
5065  public static function groupHasPermission( $group, $role ) {
5067  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
5068  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
5069  }
5070 
5085  public static function isEveryoneAllowed( $right ) {
5087  static $cache = [];
5088 
5089  // Use the cached results, except in unit tests which rely on
5090  // being able change the permission mid-request
5091  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
5092  return $cache[$right];
5093  }
5094 
5095  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
5096  $cache[$right] = false;
5097  return false;
5098  }
5099 
5100  // If it's revoked anywhere, then everyone doesn't have it
5101  foreach ( $wgRevokePermissions as $rights ) {
5102  if ( isset( $rights[$right] ) && $rights[$right] ) {
5103  $cache[$right] = false;
5104  return false;
5105  }
5106  }
5107 
5108  // Remove any rights that aren't allowed to the global-session user,
5109  // unless there are no sessions for this endpoint.
5110  if ( !defined( 'MW_NO_SESSION' ) ) {
5111  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
5112  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
5113  $cache[$right] = false;
5114  return false;
5115  }
5116  }
5117 
5118  // Allow extensions to say false
5119  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
5120  $cache[$right] = false;
5121  return false;
5122  }
5123 
5124  $cache[$right] = true;
5125  return true;
5126  }
5127 
5134  public static function getAllGroups() {
5136  return array_values( array_diff(
5137  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
5138  self::getImplicitGroups()
5139  ) );
5140  }
5141 
5146  public static function getAllRights() {
5147  if ( self::$mAllRights === false ) {
5148  global $wgAvailableRights;
5149  if ( count( $wgAvailableRights ) ) {
5150  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
5151  } else {
5152  self::$mAllRights = self::$mCoreRights;
5153  }
5154  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
5155  }
5156  return self::$mAllRights;
5157  }
5158 
5165  public static function getImplicitGroups() {
5166  global $wgImplicitGroups;
5167  return $wgImplicitGroups;
5168  }
5169 
5177  public static function getGroupPage( $group ) {
5178  wfDeprecated( __METHOD__, '1.29' );
5179  return UserGroupMembership::getGroupPage( $group );
5180  }
5181 
5192  public static function makeGroupLinkHTML( $group, $text = '' ) {
5193  wfDeprecated( __METHOD__, '1.29' );
5194 
5195  if ( $text == '' ) {
5196  $text = UserGroupMembership::getGroupName( $group );
5197  }
5199  if ( $title ) {
5200  return MediaWikiServices::getInstance()
5201  ->getLinkRenderer()->makeLink( $title, $text );
5202  }
5203 
5204  return htmlspecialchars( $text );
5205  }
5206 
5217  public static function makeGroupLinkWiki( $group, $text = '' ) {
5218  wfDeprecated( __METHOD__, '1.29' );
5219 
5220  if ( $text == '' ) {
5221  $text = UserGroupMembership::getGroupName( $group );
5222  }
5224  if ( $title ) {
5225  $page = $title->getFullText();
5226  return "[[$page|$text]]";
5227  }
5228 
5229  return $text;
5230  }
5231 
5241  public static function changeableByGroup( $group ) {
5243 
5244  $groups = [
5245  'add' => [],
5246  'remove' => [],
5247  'add-self' => [],
5248  'remove-self' => []
5249  ];
5250 
5251  if ( empty( $wgAddGroups[$group] ) ) {
5252  // Don't add anything to $groups
5253  } elseif ( $wgAddGroups[$group] === true ) {
5254  // You get everything
5255  $groups['add'] = self::getAllGroups();
5256  } elseif ( is_array( $wgAddGroups[$group] ) ) {
5257  $groups['add'] = $wgAddGroups[$group];
5258  }
5259 
5260  // Same thing for remove
5261  if ( empty( $wgRemoveGroups[$group] ) ) {
5262  // Do nothing
5263  } elseif ( $wgRemoveGroups[$group] === true ) {
5264  $groups['remove'] = self::getAllGroups();
5265  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5266  $groups['remove'] = $wgRemoveGroups[$group];
5267  }
5268 
5269  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5270  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5271  foreach ( $wgGroupsAddToSelf as $key => $value ) {
5272  if ( is_int( $key ) ) {
5273  $wgGroupsAddToSelf['user'][] = $value;
5274  }
5275  }
5276  }
5277 
5278  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5279  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5280  if ( is_int( $key ) ) {
5281  $wgGroupsRemoveFromSelf['user'][] = $value;
5282  }
5283  }
5284  }
5285 
5286  // Now figure out what groups the user can add to him/herself
5287  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5288  // Do nothing
5289  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5290  // No idea WHY this would be used, but it's there
5291  $groups['add-self'] = self::getAllGroups();
5292  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5293  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5294  }
5295 
5296  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5297  // Do nothing
5298  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5299  $groups['remove-self'] = self::getAllGroups();
5300  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5301  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5302  }
5303 
5304  return $groups;
5305  }
5306 
5314  public function changeableGroups() {
5315  if ( $this->isAllowed( 'userrights' ) ) {
5316  // This group gives the right to modify everything (reverse-
5317  // compatibility with old "userrights lets you change
5318  // everything")
5319  // Using array_merge to make the groups reindexed
5320  $all = array_merge( self::getAllGroups() );
5321  return [
5322  'add' => $all,
5323  'remove' => $all,
5324  'add-self' => [],
5325  'remove-self' => []
5326  ];
5327  }
5328 
5329  // Okay, it's not so simple, we will have to go through the arrays
5330  $groups = [
5331  'add' => [],
5332  'remove' => [],
5333  'add-self' => [],
5334  'remove-self' => []
5335  ];
5336  $addergroups = $this->getEffectiveGroups();
5337 
5338  foreach ( $addergroups as $addergroup ) {
5339  $groups = array_merge_recursive(
5340  $groups, $this->changeableByGroup( $addergroup )
5341  );
5342  $groups['add'] = array_unique( $groups['add'] );
5343  $groups['remove'] = array_unique( $groups['remove'] );
5344  $groups['add-self'] = array_unique( $groups['add-self'] );
5345  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5346  }
5347  return $groups;
5348  }
5349 
5353  public function incEditCount() {
5354  if ( $this->isAnon() ) {
5355  return; // sanity
5356  }
5357 
5359  new UserEditCountUpdate( $this, 1 ),
5361  );
5362  }
5363 
5369  public function setEditCountInternal( $count ) {
5370  $this->mEditCount = $count;
5371  }
5372 
5380  public function initEditCountInternal() {
5381  // Pull from a replica DB to be less cruel to servers
5382  // Accuracy isn't the point anyway here
5383  $dbr = wfGetDB( DB_REPLICA );
5384  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5385  $count = (int)$dbr->selectField(
5386  [ 'revision' ] + $actorWhere['tables'],
5387  'COUNT(*)',
5388  [ $actorWhere['conds'] ],
5389  __METHOD__,
5390  [],
5391  $actorWhere['joins']
5392  );
5393 
5394  $dbw = wfGetDB( DB_MASTER );
5395  $dbw->update(
5396  'user',
5397  [ 'user_editcount' => $count ],
5398  [
5399  'user_id' => $this->getId(),
5400  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5401  ],
5402  __METHOD__
5403  );
5404 
5405  return $count;
5406  }
5407 
5415  public static function getRightDescription( $right ) {
5416  $key = "right-$right";
5417  $msg = wfMessage( $key );
5418  return $msg->isDisabled() ? $right : $msg->text();
5419  }
5420 
5428  public static function getGrantName( $grant ) {
5429  $key = "grant-$grant";
5430  $msg = wfMessage( $key );
5431  return $msg->isDisabled() ? $grant : $msg->text();
5432  }
5433 
5454  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5455  return true; // disabled
5456  }
5457 
5466  public function addNewUserLogEntryAutoCreate() {
5467  $this->addNewUserLogEntry( 'autocreate' );
5468 
5469  return true;
5470  }
5471 
5477  protected function loadOptions( $data = null ) {
5478  $this->load();
5479 
5480  if ( $this->mOptionsLoaded ) {
5481  return;
5482  }
5483 
5484  $this->mOptions = self::getDefaultOptions();
5485 
5486  if ( !$this->getId() ) {
5487  // For unlogged-in users, load language/variant options from request.
5488  // There's no need to do it for logged-in users: they can set preferences,
5489  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5490  // so don't override user's choice (especially when the user chooses site default).
5491  $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5492  $this->mOptions['variant'] = $variant;
5493  $this->mOptions['language'] = $variant;
5494  $this->mOptionsLoaded = true;
5495  return;
5496  }
5497 
5498  // Maybe load from the object
5499  if ( !is_null( $this->mOptionOverrides ) ) {
5500  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5501  foreach ( $this->mOptionOverrides as $key => $value ) {
5502  $this->mOptions[$key] = $value;
5503  }
5504  } else {
5505  if ( !is_array( $data ) ) {
5506  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5507  // Load from database
5508  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5509  ? wfGetDB( DB_MASTER )
5510  : wfGetDB( DB_REPLICA );
5511 
5512  $res = $dbr->select(
5513  'user_properties',
5514  [ 'up_property', 'up_value' ],
5515  [ 'up_user' => $this->getId() ],
5516  __METHOD__
5517  );
5518 
5519  $this->mOptionOverrides = [];
5520  $data = [];
5521  foreach ( $res as $row ) {
5522  // Convert '0' to 0. PHP's boolean conversion considers them both
5523  // false, but e.g. JavaScript considers the former as true.
5524  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5525  // and convert all values here.
5526  if ( $row->up_value === '0' ) {
5527  $row->up_value = 0;
5528  }
5529  $data[$row->up_property] = $row->up_value;
5530  }
5531  }
5532 
5533  foreach ( $data as $property => $value ) {
5534  $this->mOptionOverrides[$property] = $value;
5535  $this->mOptions[$property] = $value;
5536  }
5537  }
5538 
5539  // Replace deprecated language codes
5540  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5541  $this->mOptions['language']
5542  );
5543 
5544  $this->mOptionsLoaded = true;
5545 
5546  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5547  }
5548 
5554  protected function saveOptions() {
5555  $this->loadOptions();
5556 
5557  // Not using getOptions(), to keep hidden preferences in database
5558  $saveOptions = $this->mOptions;
5559 
5560  // Allow hooks to abort, for instance to save to a global profile.
5561  // Reset options to default state before saving.
5562  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5563  return;
5564  }
5565 
5566  $userId = $this->getId();
5567 
5568  $insert_rows = []; // all the new preference rows
5569  foreach ( $saveOptions as $key => $value ) {
5570  // Don't bother storing default values
5571  $defaultOption = self::getDefaultOption( $key );
5572  if ( ( $defaultOption === null && $value !== false && $value !== null )
5573  || $value != $defaultOption
5574  ) {
5575  $insert_rows[] = [
5576  'up_user' => $userId,
5577  'up_property' => $key,
5578  'up_value' => $value,
5579  ];
5580  }
5581  }
5582 
5583  $dbw = wfGetDB( DB_MASTER );
5584 
5585  $res = $dbw->select( 'user_properties',
5586  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5587 
5588  // Find prior rows that need to be removed or updated. These rows will
5589  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5590  $keysDelete = [];
5591  foreach ( $res as $row ) {
5592  if ( !isset( $saveOptions[$row->up_property] )
5593  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5594  ) {
5595  $keysDelete[] = $row->up_property;
5596  }
5597  }
5598 
5599  if ( count( $keysDelete ) ) {
5600  // Do the DELETE by PRIMARY KEY for prior rows.
5601  // In the past a very large portion of calls to this function are for setting
5602  // 'rememberpassword' for new accounts (a preference that has since been removed).
5603  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5604  // caused gap locks on [max user ID,+infinity) which caused high contention since
5605  // updates would pile up on each other as they are for higher (newer) user IDs.
5606  // It might not be necessary these days, but it shouldn't hurt either.
5607  $dbw->delete( 'user_properties',
5608  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5609  }
5610  // Insert the new preference rows
5611  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5612  }
5613 
5620  public static function selectFields() {
5621  wfDeprecated( __METHOD__, '1.31' );
5622  return [
5623  'user_id',
5624  'user_name',
5625  'user_real_name',
5626  'user_email',
5627  'user_touched',
5628  'user_token',
5629  'user_email_authenticated',
5630  'user_email_token',
5631  'user_email_token_expires',
5632  'user_registration',
5633  'user_editcount',
5634  ];
5635  }
5636 
5646  public static function getQueryInfo() {
5648 
5649  $ret = [
5650  'tables' => [ 'user' ],
5651  'fields' => [
5652  'user_id',
5653  'user_name',
5654  'user_real_name',
5655  'user_email',
5656  'user_touched',
5657  'user_token',
5658  'user_email_authenticated',
5659  'user_email_token',
5660  'user_email_token_expires',
5661  'user_registration',
5662  'user_editcount',
5663  ],
5664  'joins' => [],
5665  ];
5666 
5667  // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
5668  // but it does little harm and might be needed for write callers loading a User.
5670  $ret['tables']['user_actor'] = 'actor';
5671  $ret['fields'][] = 'user_actor.actor_id';
5672  $ret['joins']['user_actor'] = [
5673  ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
5674  [ 'user_actor.actor_user = user_id' ]
5675  ];
5676  }
5677 
5678  return $ret;
5679  }
5680 
5688  static function newFatalPermissionDeniedStatus( $permission ) {
5689  global $wgLang;
5690 
5691  $groups = [];
5692  foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5693  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5694  }
5695 
5696  if ( $groups ) {
5697  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5698  }
5699 
5700  return Status::newFatal( 'badaccess-group0' );
5701  }
5702 
5712  public function getInstanceForUpdate() {
5713  if ( !$this->getId() ) {
5714  return null; // anon
5715  }
5716 
5717  $user = self::newFromId( $this->getId() );
5718  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5719  return null;
5720  }
5721 
5722  return $user;
5723  }
5724 
5732  public function equals( UserIdentity $user ) {
5733  // XXX it's not clear whether central ID providers are supposed to obey this
5734  return $this->getName() === $user->getName();
5735  }
5736 
5742  public function isAllowUsertalk() {
5743  return $this->mAllowUsertalk;
5744  }
5745 
5746 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1818
User\saveOptions
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5554
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:4893
LanguageCode\replaceDeprecatedCodes
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date,...
Definition: LanguageCode.php:165
User\updateNewtalk
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2679
$status
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1266
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:458
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:358
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2566
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:48
User\$mOptionsLoaded
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:239
$wgProxyWhitelist
$wgProxyWhitelist
Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other methods mi...
Definition: DefaultSettings.php:5608
User\inDnsBlacklist
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:2015
$user
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1476
if
if($IP===false)
Definition: cleanupArchiveUserText.php:4
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:609
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:306
User\confirmationTokenUrl
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4796
file
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing we can concentrate it all in an extension file
Definition: hooks.txt:91
$wgProxyList
$wgProxyList
Big list of banned IP addresses.
Definition: DefaultSettings.php:5971
User\clearSharedCache
clearSharedCache( $mode='changed')
Clear user data from memcached.
Definition: User.php:2776
SCHEMA_COMPAT_READ_NEW
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:287
WikiMap\getCurrentWikiDbDomain
static getCurrentWikiDbDomain()
Definition: WikiMap.php:292
wfCanIPUseHTTPS
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
Definition: GlobalFunctions.php:3106
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
User\$mToken
string $mToken
Definition: User.php:218
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:356
User\resetGetDefaultOptionsForTestsOnly
static resetGetDefaultOptionsForTestsOnly()
Reset the process cache of default user options.
Definition: User.php:1764
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1147
User\getId
getId()
Get the user's ID.
Definition: User.php:2425
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:118
User\makeGroupLinkWiki
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name.
Definition: User.php:5217
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3893
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2286
MWCryptHash\hmac
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
Definition: MWCryptHash.php:106
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:3804
$context
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2636
User\$mBlock
Block $mBlock
Definition: User.php:292
Block\clearCookie
static clearCookie(WebResponse $response)
Unset the 'BlockID' cookie.
Definition: Block.php:1815
User\$mBlockreason
string $mBlockreason
Definition: User.php:272
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4823
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2491
User\$mImplicitGroups
array $mImplicitGroups
Definition: User.php:276
User\isLocallyBlockedProxy
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:2061
$wgRevokePermissions
$wgRevokePermissions
Permission keys revoked from users in each group.
Definition: DefaultSettings.php:5245
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3308
User\isBlockedFrom
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2306
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1593
WikiMap\isCurrentWikiId
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:312
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5688
User\getEditTokenObject
getEditTokenObject( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4631
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:7018
IP\isInRanges
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:668
$opt
$opt
Definition: postprocess-phan.php:115
User\isBot
isBot()
Definition: User.php:3812
Block\newFromID
static newFromID( $id)
Load a block from the block id.
Definition: Block.php:192
User\newTouchedTimestamp
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2757
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8915
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3695
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:4480
Block\TYPE_IP
const TYPE_IP
Definition: Block.php:97
User\incEditCount
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition: User.php:5353
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:750
captcha-old.count
count
Definition: captcha-old.py:249
Block\TYPE_RANGE
const TYPE_RANGE
Definition: Block.php:98
UserMailer\send
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Definition: UserMailer.php:115
User\getOptionKinds
getOptionKinds(IContextSource $context, $options=null)
Return an associative array mapping preferences keys to the kind of a preference they're used for.
Definition: User.php:3365
Block\chooseBlock
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1523
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4935
MediaWiki\Logger\LoggerFactory\getInstance
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
Definition: LoggerFactory.php:92
User\getBlockId
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2333
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:3243
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:3200
UserGroupMembership\getGroupName
static getGroupName( $group)
Gets the localized friendly name for a group, if it exists.
Definition: UserGroupMembership.php:432
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:214
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:316
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. '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
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1912
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2984
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:4467
$wgSoftBlockRanges
string[] $wgSoftBlockRanges
IP ranges that should be considered soft-blocked (anon-only, account creation allowed).
Definition: DefaultSettings.php:5617
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2637
User\loadDefaults
loadDefaults( $name=false)
Set cached properties to default.
Definition: User.php:1310
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:295
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1780
User\$mOptions
array $mOptions
Definition: User.php:286
User\$mNewtalk
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:262
User\loadOptions
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5477
User\makeGroupLinkHTML
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition: User.php:5192
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:4825
$req
this hook is for auditing only $req
Definition: hooks.txt:979
User\setNewpassword
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:3047
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:79
Autopromote\getAutopromoteGroups
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:35
StatusValue\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
User\setEmailWithConfirmation
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:3092
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1678
User\$mHideName
bool $mHideName
Definition: User.php:284
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3535
$params
$params
Definition: styleTest.css.php:44
Block\newFromTarget
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing Block object if possible.
Definition: Block.php:1403
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1197
User\$mLocked
bool $mLocked
Definition: User.php:282
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:585
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1475
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4867
IP\isIPv6
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
User\$mEmail
string $mEmail
Definition: User.php:212
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:4550
User\$mOptionOverrides
array $mOptionOverrides
Definition: User.php:232
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3593
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:676
$s
$s
Definition: mergeMessageFileList.php:186
page
target page
Definition: All_system_messages.txt:1267
User\getBlockFromCookieValue
getBlockFromCookieValue( $blockCookieVal)
Try to load a Block from an ID given in a cookie value.
Definition: User.php:1945
PasswordFactory\generateRandomPasswordString
static generateRandomPasswordString( $minLength=10)
Generate a random string suitable for a password.
Definition: PasswordFactory.php:225
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:652
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3881
$res
$res
Definition: database.txt:21
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
User\getDatePreference
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3495
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:341
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:3075
User\idForName
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:4268
$wgAvailableRights
$wgAvailableRights[]
Definition: ReplaceText.php:55
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:993
User
User
Definition: All_system_messages.txt:425
User\sendConfirmationMail
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4697
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:5065
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:3065
User\pingLimiter
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:2132
User\$mFrom
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:257
UserEditCountUpdate
Handles increment the edit count for a given set of users.
Definition: UserEditCountUpdate.php:29
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3872
$base
$base
Definition: generateLocalAutoload.php:11
User\invalidateEmail
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4853
$messages
$messages
Definition: LogTests.i18n.php:8
UserGroupMembership\getGroupPage
static getGroupPage( $group)
Gets the title of a page describing a particular user group.
Definition: UserGroupMembership.php:457
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:772
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6911
User\loadGroups
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1603
User\$mHash
string $mHash
Definition: User.php:268
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:6927
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1043
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
User\isIPRange
isIPRange()
Is the user an IP range?
Definition: User.php:978
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
User\deleteNewtalk
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2704
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:111
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
MediaWiki\User\UserIdentity\getActorId
getActorId()
User\$mCacheVars
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:86
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
pages
The ContentHandler facility adds support for arbitrary content types on wiki pages
Definition: contenthandler.txt:1
User\getRights
getRights()
Get the permissions this user has.
Definition: User.php:3550
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4303
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3906
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
User\getAutomaticGroups
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3642
User\INVALID_TOKEN
const INVALID_TOKEN
@const string An invalid value for user_token
Definition: User.php:57
User\setPassword
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2893
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1776
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5712
$dbr
$dbr
Definition: testCompression.php:50
$wgMaxNameChars
$wgMaxNameChars
Maximum number of bytes in username.
Definition: DefaultSettings.php:4798
IDBAccessObject\READ_LOCKING
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:64
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Name of the external diff engine to use.
Definition: DefaultSettings.php:8914
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:813
UserGroupMembership\getMembershipsForUser
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
Definition: UserGroupMembership.php:309
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3730
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:32
LoggedOutEditToken
Value object representing a logged-out user's edit token.
Definition: LoggedOutEditToken.php:35
User\matchEditToken
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4671
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:3055
User\$mRights
array $mRights
Definition: User.php:270
$data
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
Definition: generatePhpCharToUpperMappings.php:13
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:4559
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4379
IP\isValidRange
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:138
Wikimedia\Rdbms\IDatabase\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
User\invalidateCache
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2805
User\setInternalPassword
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2906
MWException
MediaWiki exception.
Definition: MWException.php:26
User\invalidationTokenUrl
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4805
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2393
User\$mDatePreference
string $mDatePreference
Definition: User.php:264
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:4931
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1078
$property
$property
Definition: styleTest.css.php:48
User\checkPasswordValidity
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1202
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:101
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4836
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1359
UserGroupMembership\getLink
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
Definition: UserGroupMembership.php:374
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2324
WikiMap\getWikiIdFromDbDomain
static getWikiIdFromDbDomain( $domain)
Get the wiki ID of a database domain.
Definition: WikiMap.php:259
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2724
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2636
User\$mBlockedFromCreateAccount
Block $mBlockedFromCreateAccount
Definition: User.php:298
in
null for the wiki Added in
Definition: hooks.txt:1588
User\$mGlobalBlock
Block $mGlobalBlock
Definition: User.php:280
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4541
MediaWiki\Auth\AuthenticationResponse
This is a value object to hold authentication response data.
Definition: AuthenticationResponse.php:37
User\logout
logout()
Log this user out.
Definition: User.php:4142
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4778
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:499
$wgLang
$wgLang
Definition: Setup.php:875
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1928
User\getImplicitGroups
static getImplicitGroups()
Get a list of implicit groups TODO: Should we deprecate this? It's trivial, but we don't want to enco...
Definition: User.php:5165
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2408
User\randomPassword
static randomPassword()
Return a random password.
Definition: User.php:1297
User\$mBlockedby
string $mBlockedby
Definition: User.php:266
UserGroupMembership\newFromRow
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
Definition: UserGroupMembership.php:93
IP\getSubnet
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition: IP.php:742
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4521
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:967
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2837
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3619
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:2107
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:64
$code
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that 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
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3766
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4895
User\initEditCountInternal
initEditCountInternal()
Initialize user_editcount from data out of the revision table.
Definition: User.php:5380
$wgImplicitGroups
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
Definition: DefaultSettings.php:5250
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4569
User\isAllowedAll
isAllowedAll()
Definition: User.php:3844
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2822
MediaWiki\User\UserIdentity\getName
getName()
User\clearInstanceCache
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1734
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1672
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:73
$wgEnableDnsBlacklist
$wgEnableDnsBlacklist
Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies.
Definition: DefaultSettings.php:5577
User\TOKEN_LENGTH
const TOKEN_LENGTH
@const int Number of characters in user_token field.
Definition: User.php:52
$wgDefaultSkin
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
Definition: DefaultSettings.php:3301
$wgReservedUsernames
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
Definition: DefaultSettings.php:4804
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:576
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1941
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3937
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:216
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2289
User\setName
setName( $str)
Set the user name.
Definition: User.php:2480
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:209
User\resetOptions
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3450
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:45
User\$mGroupMemberships
UserGroupMembership[] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:230
UserPasswordPolicy
Check if a user's password complies with any password policies that apply to that user,...
Definition: UserPasswordPolicy.php:28
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:6938
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:949
string
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5499
User\$mLoadedItems
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:244
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3971
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:4190
User\addNewUserLogEntry
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5454
User\$defOpt
static array null $defOpt
Is the user an IP range?
Definition: User.php:1754
Block\getBlocksForIPList
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition: Block.php:1442
User\$defOptLang
static string null $defOptLang
Is the user an IP range?
Definition: User.php:1756
User\$mCoreRights
static $mCoreRights
Array of Strings Core rights.
Definition: User.php:113
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:929
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4964
null
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
$fname
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition: Setup.php:123
$wgAutopromoteOnceLogInRC
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
Definition: DefaultSettings.php:5470
$request
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2636
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:68
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:3157
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2604
any
they could even be mouse clicks or menu items whatever suits your program You should also get your if any
Definition: COPYING.txt:326
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:604
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3671
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:885
Block\getIdFromCookieValue
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the 'BlockID' cookie.
Definition: Block.php:1851
$value
$value
Definition: styleTest.css.php:49
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1370
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:5661
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2346
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:3172
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
User\isDnsBlacklisted
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1996
User\getTouched
getTouched()
Get the user touched timestamp.
Definition: User.php:2849
User\$mId
int $mId
Cache variables.
Definition: User.php:203
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2360
User\__toString
__toString()
Definition: User.php:323
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
Title\GAID_FOR_UPDATE
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:55
CannotCreateActorException
Exception thrown when an actor can't be created.
Definition: CannotCreateActorException.php:28
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1696
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:116
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:3145
User\$idCacheByName
static $idCacheByName
Definition: User.php:303
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4449
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:4106
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:5015
User\getGroupPage
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition: User.php:5177
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5314
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:4039
User\VERSION
const VERSION
@const int Serialized record version.
Definition: User.php:62
SCHEMA_COMPAT_WRITE_NEW
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:286
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1985
User\matchEditTokenNoSuffix
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4685
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5494
User\checkPassword
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4579
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:5134
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3954
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:5146
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:74
User\isBlockedFromUpload
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition: User.php:4532
$wgInvalidUsernameCharacters
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
Definition: DefaultSettings.php:4900
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:430
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:5646
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2315
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:8912
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2871
plain
either a plain
Definition: hooks.txt:2046
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:84
IP\isIPv4
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:41
User\getEditTimestamp
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition: User.php:4986
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:5241
Block\TYPE_USER
const TYPE_USER
Definition: Block.php:96
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:437
Autopromote\getAutopromoteOnceGroups
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
text
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Definition: All_system_messages.txt:1267
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2443
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:4068
article
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of or an object and a method hook function The function part of a third party developers and administrators to define code that will be run at certain points in the mainline and to modify the data run by that mainline code Hooks can keep mainline code and make it easier to write extensions Hooks are a principled alternative to patches for two options in MediaWiki One reverses the order of a title before displaying the article
Definition: hooks.txt:23
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1712
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4950
Block\appliesToRight
appliesToRight( $right)
Determine whether the Block prevents a given right.
Definition: Block.php:1231
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:241
IP\sanitizeIP
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:152
User\isEveryoneAllowed
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:5085
and
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
MediaWiki\User\UserIdentity\getId
getId()
User\isAllowedAny
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3829
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5415
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2957
$cache
$cache
Definition: mcc.php:33
$rows
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2636
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:5738
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1985
$wgApplyIpBlocksToXff
$wgApplyIpBlocksToXff
Whether to look at the X-Forwarded-For header's list of (potentially spoofed) IPs and apply IP blocks...
Definition: DefaultSettings.php:5624
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3796
User\checkTemporaryPassword
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4614
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:32
User\$mActorId
int null $mActorId
Definition: User.php:207
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2558
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:3027
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:905
SCHEMA_COMPAT_NEW
const SCHEMA_COMPAT_NEW
Definition: Defines.php:291
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:947
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:224
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1077
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1244
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4909
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5742
$rev
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1769
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
User\getBlockedStatus
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1829
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5428
Block
Definition: Block.php:31
UserCache\singleton
static singleton()
Definition: UserCache.php:34
User\isBlocked
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:2278
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:295
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:624
$wgDnsBlacklistUrls
$wgDnsBlacklistUrls
List of DNS blacklists to use, if $wgEnableDnsBlacklist is true.
Definition: DefaultSettings.php:5602
$keys
$keys
Definition: testCompression.php:67
NS_USER
const NS_USER
Definition: Defines.php:66
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:522
$wgLearnerMemberSince
$wgLearnerMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8913
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1627
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4752
LoggerFactory
MediaWiki Logger LoggerFactory implements a PSR[0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method. MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances. The "Spi" in MediaWiki\Logger\Spi stands for "service provider interface". An SPI is an API intended to be implemented or extended by a third party. This software design pattern is intended to enable framework extension and replaceable components. It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki. The service provider interface allows the backend logging library to be implemented in multiple ways. The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime. This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance. Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
User\getTokenFromOption
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:3280
true
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1985
User\$mEmailToken
string $mEmailToken
Definition: User.php:222
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: LogEntry.php:441
$wgGroupsRemoveFromSelf
$wgGroupsRemoveFromSelf
Definition: DefaultSettings.php:5278
User\equals
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5732
User\checkNewtalk
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2664
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1422
HTMLFormField\flattenOptions
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
Definition: HTMLFormField.php:1091
User\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5620
User\$mName
string $mName
Definition: User.php:205
User\$mRegistration
string $mRegistration
Definition: User.php:226
$wgGroupPermissions
$wgGroupPermissions['sysop']['replacetext']
Definition: ReplaceText.php:56
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1802
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for the wiki.
Definition: DefaultSettings.php:4460
$t
$t
Definition: testCompression.php:69
User\addNewUserLogEntryAutoCreate
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5466
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:726
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:78
User\$mRequest
WebRequest $mRequest
Definition: User.php:289
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:728
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3923
MediaWikiServices
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
UserGroupMembership\getMembership
static getMembership( $userId, $group, IDatabase $db=null)
Returns a UserGroupMembership object that pertains to the given user and group, or false if the user ...
Definition: UserGroupMembership.php:341
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:3231
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
$wgMinimalPasswordLength
$wgMinimalPasswordLength
Specifies the minimal length of a user password.
Definition: DefaultSettings.php:4679
$wgGroupsAddToSelf
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
Definition: DefaultSettings.php:5273
User\isUsableName
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:1042
User\$mFormerGroups
array $mFormerGroups
Definition: User.php:278
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
User\makeUpdateConditions
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1678
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1658
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4499
User\getEditToken
getEditToken( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:4655
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3515
User\$mAllRights
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:198
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1349
User\purge
static purge( $wikiId, $userId)
Definition: User.php:488
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:48
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:118
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3259
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:301
User\getPasswordValidity
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition: User.php:1159
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:895
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2452
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:4919
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:4154
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:3606
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4878
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1117
$wgNamespacesToBeSearchedDefault
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
Definition: DefaultSettings.php:6586
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:510
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
User\getLatestEditTimestamp
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4975
User\$mEffectiveGroups
array $mEffectiveGroups
Definition: User.php:274
User\setEditCountInternal
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:5369
User\setPasswordInternal
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2919
$wgDisableAnonTalk
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
Definition: DefaultSettings.php:7024
UserGroupMembership
Represents a "user group membership" – a specific instance of a user belonging to a group.
Definition: UserGroupMembership.php:37
IP\isIPAddress
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
User\$mEditCount
int $mEditCount
Definition: User.php:228
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:220
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:5042
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3859
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:667
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3342
$type
$type
Definition: testCompression.php:48
$wgActorTableSchemaMigrationStage
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
Definition: DefaultSettings.php:8979
User\trackBlockWithCookie
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition: User.php:1405