MediaWiki  1.30.1
User.php
Go to the documentation of this file.
1 <?php
23 use IPSet\IPSet;
30 use Wikimedia\ScopedCallback;
33 
39 define( 'EDIT_TOKEN_SUFFIX', Token::SUFFIX );
40 
51 class User implements IDBAccessObject {
55  const TOKEN_LENGTH = 32;
56 
60  const INVALID_TOKEN = '*** INVALID ***';
61 
68 
72  const VERSION = 11;
73 
79 
83  const CHECK_USER_RIGHTS = true;
84 
88  const IGNORE_USER_RIGHTS = false;
89 
96  protected static $mCacheVars = [
97  // user table
98  'mId',
99  'mName',
100  'mRealName',
101  'mEmail',
102  'mTouched',
103  'mToken',
104  'mEmailAuthenticated',
105  'mEmailToken',
106  'mEmailTokenExpires',
107  'mRegistration',
108  'mEditCount',
109  // user_groups table
110  'mGroupMemberships',
111  // user_properties table
112  'mOptionOverrides',
113  ];
114 
121  protected static $mCoreRights = [
122  'apihighlimits',
123  'applychangetags',
124  'autoconfirmed',
125  'autocreateaccount',
126  'autopatrol',
127  'bigdelete',
128  'block',
129  'blockemail',
130  'bot',
131  'browsearchive',
132  'changetags',
133  'createaccount',
134  'createpage',
135  'createtalk',
136  'delete',
137  'deletechangetags',
138  'deletedhistory',
139  'deletedtext',
140  'deletelogentry',
141  'deleterevision',
142  'edit',
143  'editcontentmodel',
144  'editinterface',
145  'editprotected',
146  'editmyoptions',
147  'editmyprivateinfo',
148  'editmyusercss',
149  'editmyuserjs',
150  'editmywatchlist',
151  'editsemiprotected',
152  'editusercss',
153  'edituserjs',
154  'hideuser',
155  'import',
156  'importupload',
157  'ipblock-exempt',
158  'managechangetags',
159  'markbotedits',
160  'mergehistory',
161  'minoredit',
162  'move',
163  'movefile',
164  'move-categorypages',
165  'move-rootuserpages',
166  'move-subpages',
167  'nominornewtalk',
168  'noratelimit',
169  'override-export-depth',
170  'pagelang',
171  'patrol',
172  'patrolmarks',
173  'protect',
174  'purge',
175  'read',
176  'reupload',
177  'reupload-own',
178  'reupload-shared',
179  'rollback',
180  'sendemail',
181  'siteadmin',
182  'suppressionlog',
183  'suppressredirect',
184  'suppressrevision',
185  'unblockself',
186  'undelete',
187  'unwatchedpages',
188  'upload',
189  'upload_by_url',
190  'userrights',
191  'userrights-interwiki',
192  'viewmyprivateinfo',
193  'viewmywatchlist',
194  'viewsuppressed',
195  'writeapi',
196  ];
197 
201  protected static $mAllRights = false;
202 
204  // @{
206  public $mId;
208  public $mName;
210  public $mRealName;
211 
213  public $mEmail;
215  public $mTouched;
217  protected $mQuickTouched;
219  protected $mToken;
223  protected $mEmailToken;
227  protected $mRegistration;
229  protected $mEditCount;
234  private $mGroups;
238  protected $mOptionOverrides;
239  // @}
240 
244  // @{
246 
250  protected $mLoadedItems = [];
251  // @}
252 
262  public $mFrom;
263 
267  protected $mNewtalk;
269  protected $mDatePreference;
271  public $mBlockedby;
273  protected $mHash;
275  public $mRights;
277  protected $mBlockreason;
279  protected $mEffectiveGroups;
281  protected $mImplicitGroups;
283  protected $mFormerGroups;
285  protected $mGlobalBlock;
287  protected $mLocked;
289  public $mHideName;
291  public $mOptions;
292 
294  private $mRequest;
295 
297  public $mBlock;
298 
300  protected $mAllowUsertalk;
301 
303  private $mBlockedFromCreateAccount = false;
304 
306  protected $queryFlagsUsed = self::READ_NORMAL;
307 
308  public static $idCacheByName = [];
309 
320  public function __construct() {
321  $this->clearInstanceCache( 'defaults' );
322  }
323 
327  public function __toString() {
328  return (string)$this->getName();
329  }
330 
345  public function isSafeToLoad() {
347 
348  // The user is safe to load if:
349  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
350  // * mLoadedItems === true (already loaded)
351  // * mFrom !== 'session' (sessions not involved at all)
352 
353  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
354  $this->mLoadedItems === true || $this->mFrom !== 'session';
355  }
356 
362  public function load( $flags = self::READ_NORMAL ) {
364 
365  if ( $this->mLoadedItems === true ) {
366  return;
367  }
368 
369  // Set it now to avoid infinite recursion in accessors
370  $oldLoadedItems = $this->mLoadedItems;
371  $this->mLoadedItems = true;
372  $this->queryFlagsUsed = $flags;
373 
374  // If this is called too early, things are likely to break.
375  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
377  ->warning( 'User::loadFromSession called before the end of Setup.php', [
378  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
379  ] );
380  $this->loadDefaults();
381  $this->mLoadedItems = $oldLoadedItems;
382  return;
383  }
384 
385  switch ( $this->mFrom ) {
386  case 'defaults':
387  $this->loadDefaults();
388  break;
389  case 'name':
390  // Make sure this thread sees its own changes
391  if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
392  $flags |= self::READ_LATEST;
393  $this->queryFlagsUsed = $flags;
394  }
395 
396  $this->mId = self::idFromName( $this->mName, $flags );
397  if ( !$this->mId ) {
398  // Nonexistent user placeholder object
399  $this->loadDefaults( $this->mName );
400  } else {
401  $this->loadFromId( $flags );
402  }
403  break;
404  case 'id':
405  $this->loadFromId( $flags );
406  break;
407  case 'session':
408  if ( !$this->loadFromSession() ) {
409  // Loading from session failed. Load defaults.
410  $this->loadDefaults();
411  }
412  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
413  break;
414  default:
415  throw new UnexpectedValueException(
416  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
417  }
418  }
419 
425  public function loadFromId( $flags = self::READ_NORMAL ) {
426  if ( $this->mId == 0 ) {
427  // Anonymous users are not in the database (don't need cache)
428  $this->loadDefaults();
429  return false;
430  }
431 
432  // Try cache (unless this needs data from the master DB).
433  // NOTE: if this thread called saveSettings(), the cache was cleared.
434  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
435  if ( $latest ) {
436  if ( !$this->loadFromDatabase( $flags ) ) {
437  // Can't load from ID
438  return false;
439  }
440  } else {
441  $this->loadFromCache();
442  }
443 
444  $this->mLoadedItems = true;
445  $this->queryFlagsUsed = $flags;
446 
447  return true;
448  }
449 
455  public static function purge( $wikiId, $userId ) {
457  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
458  $cache->delete( $key );
459  }
460 
466  protected function getCacheKey( WANObjectCache $cache ) {
467  return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId );
468  }
469 
476  $id = $this->getId();
477 
478  return $id ? [ $this->getCacheKey( $cache ) ] : [];
479  }
480 
487  protected function loadFromCache() {
489  $data = $cache->getWithSetCallback(
490  $this->getCacheKey( $cache ),
491  $cache::TTL_HOUR,
492  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
493  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
494  wfDebug( "User: cache miss for user {$this->mId}\n" );
495 
496  $this->loadFromDatabase( self::READ_NORMAL );
497  $this->loadGroups();
498  $this->loadOptions();
499 
500  $data = [];
501  foreach ( self::$mCacheVars as $name ) {
502  $data[$name] = $this->$name;
503  }
504 
505  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
506 
507  // if a user group membership is about to expire, the cache needs to
508  // expire at that time (T163691)
509  foreach ( $this->mGroupMemberships as $ugm ) {
510  if ( $ugm->getExpiry() ) {
511  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
512  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
513  $ttl = $secondsUntilExpiry;
514  }
515  }
516  }
517 
518  return $data;
519  },
520  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
521  );
522 
523  // Restore from cache
524  foreach ( self::$mCacheVars as $name ) {
525  $this->$name = $data[$name];
526  }
527 
528  return true;
529  }
530 
532  // @{
533 
550  public static function newFromName( $name, $validate = 'valid' ) {
551  if ( $validate === true ) {
552  $validate = 'valid';
553  }
554  $name = self::getCanonicalName( $name, $validate );
555  if ( $name === false ) {
556  return false;
557  } else {
558  // Create unloaded user object
559  $u = new User;
560  $u->mName = $name;
561  $u->mFrom = 'name';
562  $u->setItemLoaded( 'name' );
563  return $u;
564  }
565  }
566 
573  public static function newFromId( $id ) {
574  $u = new User;
575  $u->mId = $id;
576  $u->mFrom = 'id';
577  $u->setItemLoaded( 'id' );
578  return $u;
579  }
580 
592  public static function newFromConfirmationCode( $code, $flags = 0 ) {
593  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
594  ? wfGetDB( DB_MASTER )
595  : wfGetDB( DB_REPLICA );
596 
597  $id = $db->selectField(
598  'user',
599  'user_id',
600  [
601  'user_email_token' => md5( $code ),
602  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
603  ]
604  );
605 
606  return $id ? self::newFromId( $id ) : null;
607  }
608 
616  public static function newFromSession( WebRequest $request = null ) {
617  $user = new User;
618  $user->mFrom = 'session';
619  $user->mRequest = $request;
620  return $user;
621  }
622 
637  public static function newFromRow( $row, $data = null ) {
638  $user = new User;
639  $user->loadFromRow( $row, $data );
640  return $user;
641  }
642 
678  public static function newSystemUser( $name, $options = [] ) {
679  $options += [
680  'validate' => 'valid',
681  'create' => true,
682  'steal' => false,
683  ];
684 
685  $name = self::getCanonicalName( $name, $options['validate'] );
686  if ( $name === false ) {
687  return null;
688  }
689 
690  $dbr = wfGetDB( DB_REPLICA );
691  $row = $dbr->selectRow(
692  'user',
693  self::selectFields(),
694  [ 'user_name' => $name ],
695  __METHOD__
696  );
697  if ( !$row ) {
698  // Try the master database...
699  $dbw = wfGetDB( DB_MASTER );
700  $row = $dbw->selectRow(
701  'user',
702  self::selectFields(),
703  [ 'user_name' => $name ],
704  __METHOD__
705  );
706  }
707 
708  if ( !$row ) {
709  // No user. Create it?
710  return $options['create']
711  ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
712  : null;
713  }
714 
715  $user = self::newFromRow( $row );
716 
717  // A user is considered to exist as a non-system user if it can
718  // authenticate, or has an email set, or has a non-invalid token.
719  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
720  AuthManager::singleton()->userCanAuthenticate( $name )
721  ) {
722  // User exists. Steal it?
723  if ( !$options['steal'] ) {
724  return null;
725  }
726 
727  AuthManager::singleton()->revokeAccessForUser( $name );
728 
729  $user->invalidateEmail();
730  $user->mToken = self::INVALID_TOKEN;
731  $user->saveSettings();
732  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
733  }
734 
735  return $user;
736  }
737 
738  // @}
739 
745  public static function whoIs( $id ) {
746  return UserCache::singleton()->getProp( $id, 'name' );
747  }
748 
755  public static function whoIsReal( $id ) {
756  return UserCache::singleton()->getProp( $id, 'real_name' );
757  }
758 
765  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
767  if ( is_null( $nt ) ) {
768  // Illegal name
769  return null;
770  }
771 
772  if ( !( $flags & self::READ_LATEST ) && isset( self::$idCacheByName[$name] ) ) {
773  return self::$idCacheByName[$name];
774  }
775 
777  $db = wfGetDB( $index );
778 
779  $s = $db->selectRow(
780  'user',
781  [ 'user_id' ],
782  [ 'user_name' => $nt->getText() ],
783  __METHOD__,
784  $options
785  );
786 
787  if ( $s === false ) {
788  $result = null;
789  } else {
790  $result = $s->user_id;
791  }
792 
793  self::$idCacheByName[$name] = $result;
794 
795  if ( count( self::$idCacheByName ) > 1000 ) {
796  self::$idCacheByName = [];
797  }
798 
799  return $result;
800  }
801 
805  public static function resetIdByNameCache() {
806  self::$idCacheByName = [];
807  }
808 
825  public static function isIP( $name ) {
826  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
827  || IP::isIPv6( $name );
828  }
829 
836  public function isIPRange() {
837  return IP::isValidRange( $this->mName );
838  }
839 
851  public static function isValidUserName( $name ) {
853 
854  if ( $name == ''
855  || self::isIP( $name )
856  || strpos( $name, '/' ) !== false
857  || strlen( $name ) > $wgMaxNameChars
858  || $name != $wgContLang->ucfirst( $name )
859  ) {
860  return false;
861  }
862 
863  // Ensure that the name can't be misresolved as a different title,
864  // such as with extra namespace keys at the start.
865  $parsed = Title::newFromText( $name );
866  if ( is_null( $parsed )
867  || $parsed->getNamespace()
868  || strcmp( $name, $parsed->getPrefixedText() ) ) {
869  return false;
870  }
871 
872  // Check an additional blacklist of troublemaker characters.
873  // Should these be merged into the title char list?
874  $unicodeBlacklist = '/[' .
875  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
876  '\x{00a0}' . # non-breaking space
877  '\x{2000}-\x{200f}' . # various whitespace
878  '\x{2028}-\x{202f}' . # breaks and control chars
879  '\x{3000}' . # ideographic space
880  '\x{e000}-\x{f8ff}' . # private use
881  ']/u';
882  if ( preg_match( $unicodeBlacklist, $name ) ) {
883  return false;
884  }
885 
886  return true;
887  }
888 
900  public static function isUsableName( $name ) {
902  // Must be a valid username, obviously ;)
903  if ( !self::isValidUserName( $name ) ) {
904  return false;
905  }
906 
907  static $reservedUsernames = false;
908  if ( !$reservedUsernames ) {
909  $reservedUsernames = $wgReservedUsernames;
910  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
911  }
912 
913  // Certain names may be reserved for batch processes.
914  foreach ( $reservedUsernames as $reserved ) {
915  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
916  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
917  }
918  if ( $reserved == $name ) {
919  return false;
920  }
921  }
922  return true;
923  }
924 
935  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
936  if ( $groups === [] ) {
937  return UserArrayFromResult::newFromIDs( [] );
938  }
939 
940  $groups = array_unique( (array)$groups );
941  $limit = min( 5000, $limit );
942 
943  $conds = [ 'ug_group' => $groups ];
944  if ( $after !== null ) {
945  $conds[] = 'ug_user > ' . (int)$after;
946  }
947 
948  $dbr = wfGetDB( DB_REPLICA );
949  $ids = $dbr->selectFieldValues(
950  'user_groups',
951  'ug_user',
952  $conds,
953  __METHOD__,
954  [
955  'DISTINCT' => true,
956  'ORDER BY' => 'ug_user',
957  'LIMIT' => $limit,
958  ]
959  ) ?: [];
960  return UserArray::newFromIDs( $ids );
961  }
962 
975  public static function isCreatableName( $name ) {
977 
978  // Ensure that the username isn't longer than 235 bytes, so that
979  // (at least for the builtin skins) user javascript and css files
980  // will work. (T25080)
981  if ( strlen( $name ) > 235 ) {
982  wfDebugLog( 'username', __METHOD__ .
983  ": '$name' invalid due to length" );
984  return false;
985  }
986 
987  // Preg yells if you try to give it an empty string
988  if ( $wgInvalidUsernameCharacters !== '' ) {
989  if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
990  wfDebugLog( 'username', __METHOD__ .
991  ": '$name' invalid due to wgInvalidUsernameCharacters" );
992  return false;
993  }
994  }
995 
996  return self::isUsableName( $name );
997  }
998 
1005  public function isValidPassword( $password ) {
1006  // simple boolean wrapper for getPasswordValidity
1007  return $this->getPasswordValidity( $password ) === true;
1008  }
1009 
1016  public function getPasswordValidity( $password ) {
1017  $result = $this->checkPasswordValidity( $password );
1018  if ( $result->isGood() ) {
1019  return true;
1020  } else {
1021  $messages = [];
1022  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1023  $messages[] = $error['message'];
1024  }
1025  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1026  $messages[] = $warning['message'];
1027  }
1028  if ( count( $messages ) === 1 ) {
1029  return $messages[0];
1030  }
1031  return $messages;
1032  }
1033  }
1034 
1052  public function checkPasswordValidity( $password ) {
1054 
1055  $upp = new UserPasswordPolicy(
1056  $wgPasswordPolicy['policies'],
1057  $wgPasswordPolicy['checks']
1058  );
1059 
1061  $result = false; // init $result to false for the internal checks
1062 
1063  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1064  $status->error( $result );
1065  return $status;
1066  }
1067 
1068  if ( $result === false ) {
1069  $status->merge( $upp->checkUserPassword( $this, $password ) );
1070  return $status;
1071  } elseif ( $result === true ) {
1072  return $status;
1073  } else {
1074  $status->error( $result );
1075  return $status; // the isValidPassword hook set a string $result and returned true
1076  }
1077  }
1078 
1092  public static function getCanonicalName( $name, $validate = 'valid' ) {
1093  // Force usernames to capital
1095  $name = $wgContLang->ucfirst( $name );
1096 
1097  # Reject names containing '#'; these will be cleaned up
1098  # with title normalisation, but then it's too late to
1099  # check elsewhere
1100  if ( strpos( $name, '#' ) !== false ) {
1101  return false;
1102  }
1103 
1104  // Clean up name according to title rules,
1105  // but only when validation is requested (T14654)
1106  $t = ( $validate !== false ) ?
1108  // Check for invalid titles
1109  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1110  return false;
1111  }
1112 
1113  // Reject various classes of invalid names
1114  $name = AuthManager::callLegacyAuthPlugin(
1115  'getCanonicalName', [ $t->getText() ], $t->getText()
1116  );
1117 
1118  switch ( $validate ) {
1119  case false:
1120  break;
1121  case 'valid':
1122  if ( !self::isValidUserName( $name ) ) {
1123  $name = false;
1124  }
1125  break;
1126  case 'usable':
1127  if ( !self::isUsableName( $name ) ) {
1128  $name = false;
1129  }
1130  break;
1131  case 'creatable':
1132  if ( !self::isCreatableName( $name ) ) {
1133  $name = false;
1134  }
1135  break;
1136  default:
1137  throw new InvalidArgumentException(
1138  'Invalid parameter value for $validate in ' . __METHOD__ );
1139  }
1140  return $name;
1141  }
1142 
1149  public static function randomPassword() {
1152  }
1153 
1162  public function loadDefaults( $name = false ) {
1163  $this->mId = 0;
1164  $this->mName = $name;
1165  $this->mRealName = '';
1166  $this->mEmail = '';
1167  $this->mOptionOverrides = null;
1168  $this->mOptionsLoaded = false;
1169 
1170  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1171  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1172  if ( $loggedOut !== 0 ) {
1173  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1174  } else {
1175  $this->mTouched = '1'; # Allow any pages to be cached
1176  }
1177 
1178  $this->mToken = null; // Don't run cryptographic functions till we need a token
1179  $this->mEmailAuthenticated = null;
1180  $this->mEmailToken = '';
1181  $this->mEmailTokenExpires = null;
1182  $this->mRegistration = wfTimestamp( TS_MW );
1183  $this->mGroupMemberships = [];
1184 
1185  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1186  }
1187 
1200  public function isItemLoaded( $item, $all = 'all' ) {
1201  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1202  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1203  }
1204 
1210  protected function setItemLoaded( $item ) {
1211  if ( is_array( $this->mLoadedItems ) ) {
1212  $this->mLoadedItems[$item] = true;
1213  }
1214  }
1215 
1221  private function loadFromSession() {
1222  // Deprecated hook
1223  $result = null;
1224  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1225  if ( $result !== null ) {
1226  return $result;
1227  }
1228 
1229  // MediaWiki\Session\Session already did the necessary authentication of the user
1230  // returned here, so just use it if applicable.
1231  $session = $this->getRequest()->getSession();
1232  $user = $session->getUser();
1233  if ( $user->isLoggedIn() ) {
1234  $this->loadFromUserObject( $user );
1235 
1236  // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1237  // every session load, because an autoblocked editor might not edit again from the same
1238  // IP address after being blocked.
1239  $config = RequestContext::getMain()->getConfig();
1240  if ( $config->get( 'CookieSetOnAutoblock' ) === true ) {
1241  $block = $this->getBlock();
1242  $shouldSetCookie = $this->getRequest()->getCookie( 'BlockID' ) === null
1243  && $block
1244  && $block->getType() === Block::TYPE_USER
1245  && $block->isAutoblocking();
1246  if ( $shouldSetCookie ) {
1247  wfDebug( __METHOD__ . ': User is autoblocked, setting cookie to track' );
1248  $block->setCookie( $this->getRequest()->response() );
1249  }
1250  }
1251 
1252  // Other code expects these to be set in the session, so set them.
1253  $session->set( 'wsUserID', $this->getId() );
1254  $session->set( 'wsUserName', $this->getName() );
1255  $session->set( 'wsToken', $this->getToken() );
1256  return true;
1257  }
1258  return false;
1259  }
1260 
1268  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1269  // Paranoia
1270  $this->mId = intval( $this->mId );
1271 
1272  if ( !$this->mId ) {
1273  // Anonymous users are not in the database
1274  $this->loadDefaults();
1275  return false;
1276  }
1277 
1279  $db = wfGetDB( $index );
1280 
1281  $s = $db->selectRow(
1282  'user',
1283  self::selectFields(),
1284  [ 'user_id' => $this->mId ],
1285  __METHOD__,
1286  $options
1287  );
1288 
1289  $this->queryFlagsUsed = $flags;
1290  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1291 
1292  if ( $s !== false ) {
1293  // Initialise user table data
1294  $this->loadFromRow( $s );
1295  $this->mGroupMemberships = null; // deferred
1296  $this->getEditCount(); // revalidation for nulls
1297  return true;
1298  } else {
1299  // Invalid user_id
1300  $this->mId = 0;
1301  $this->loadDefaults();
1302  return false;
1303  }
1304  }
1305 
1318  protected function loadFromRow( $row, $data = null ) {
1319  $all = true;
1320 
1321  $this->mGroupMemberships = null; // deferred
1322 
1323  if ( isset( $row->user_name ) ) {
1324  $this->mName = $row->user_name;
1325  $this->mFrom = 'name';
1326  $this->setItemLoaded( 'name' );
1327  } else {
1328  $all = false;
1329  }
1330 
1331  if ( isset( $row->user_real_name ) ) {
1332  $this->mRealName = $row->user_real_name;
1333  $this->setItemLoaded( 'realname' );
1334  } else {
1335  $all = false;
1336  }
1337 
1338  if ( isset( $row->user_id ) ) {
1339  $this->mId = intval( $row->user_id );
1340  $this->mFrom = 'id';
1341  $this->setItemLoaded( 'id' );
1342  } else {
1343  $all = false;
1344  }
1345 
1346  if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
1347  self::$idCacheByName[$row->user_name] = $row->user_id;
1348  }
1349 
1350  if ( isset( $row->user_editcount ) ) {
1351  $this->mEditCount = $row->user_editcount;
1352  } else {
1353  $all = false;
1354  }
1355 
1356  if ( isset( $row->user_touched ) ) {
1357  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1358  } else {
1359  $all = false;
1360  }
1361 
1362  if ( isset( $row->user_token ) ) {
1363  // The definition for the column is binary(32), so trim the NULs
1364  // that appends. The previous definition was char(32), so trim
1365  // spaces too.
1366  $this->mToken = rtrim( $row->user_token, " \0" );
1367  if ( $this->mToken === '' ) {
1368  $this->mToken = null;
1369  }
1370  } else {
1371  $all = false;
1372  }
1373 
1374  if ( isset( $row->user_email ) ) {
1375  $this->mEmail = $row->user_email;
1376  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1377  $this->mEmailToken = $row->user_email_token;
1378  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1379  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1380  } else {
1381  $all = false;
1382  }
1383 
1384  if ( $all ) {
1385  $this->mLoadedItems = true;
1386  }
1387 
1388  if ( is_array( $data ) ) {
1389  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1390  if ( !count( $data['user_groups'] ) ) {
1391  $this->mGroupMemberships = [];
1392  } else {
1393  $firstGroup = reset( $data['user_groups'] );
1394  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1395  $this->mGroupMemberships = [];
1396  foreach ( $data['user_groups'] as $row ) {
1397  $ugm = UserGroupMembership::newFromRow( (object)$row );
1398  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1399  }
1400  }
1401  }
1402  }
1403  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1404  $this->loadOptions( $data['user_properties'] );
1405  }
1406  }
1407  }
1408 
1414  protected function loadFromUserObject( $user ) {
1415  $user->load();
1416  foreach ( self::$mCacheVars as $var ) {
1417  $this->$var = $user->$var;
1418  }
1419  }
1420 
1424  private function loadGroups() {
1425  if ( is_null( $this->mGroupMemberships ) ) {
1426  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1427  ? wfGetDB( DB_MASTER )
1428  : wfGetDB( DB_REPLICA );
1429  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1430  $this->mId, $db );
1431  }
1432  }
1433 
1448  public function addAutopromoteOnceGroups( $event ) {
1450 
1451  if ( wfReadOnly() || !$this->getId() ) {
1452  return [];
1453  }
1454 
1455  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1456  if ( !count( $toPromote ) ) {
1457  return [];
1458  }
1459 
1460  if ( !$this->checkAndSetTouched() ) {
1461  return []; // raced out (bug T48834)
1462  }
1463 
1464  $oldGroups = $this->getGroups(); // previous groups
1465  foreach ( $toPromote as $group ) {
1466  $this->addGroup( $group );
1467  }
1468  // update groups in external authentication database
1469  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false ] );
1470  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1471 
1472  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1473 
1474  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1475  $logEntry->setPerformer( $this );
1476  $logEntry->setTarget( $this->getUserPage() );
1477  $logEntry->setParameters( [
1478  '4::oldgroups' => $oldGroups,
1479  '5::newgroups' => $newGroups,
1480  ] );
1481  $logid = $logEntry->insert();
1482  if ( $wgAutopromoteOnceLogInRC ) {
1483  $logEntry->publish( $logid );
1484  }
1485 
1486  return $toPromote;
1487  }
1488 
1498  protected function makeUpdateConditions( Database $db, array $conditions ) {
1499  if ( $this->mTouched ) {
1500  // CAS check: only update if the row wasn't changed sicne it was loaded.
1501  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1502  }
1503 
1504  return $conditions;
1505  }
1506 
1516  protected function checkAndSetTouched() {
1517  $this->load();
1518 
1519  if ( !$this->mId ) {
1520  return false; // anon
1521  }
1522 
1523  // Get a new user_touched that is higher than the old one
1524  $newTouched = $this->newTouchedTimestamp();
1525 
1526  $dbw = wfGetDB( DB_MASTER );
1527  $dbw->update( 'user',
1528  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1529  $this->makeUpdateConditions( $dbw, [
1530  'user_id' => $this->mId,
1531  ] ),
1532  __METHOD__
1533  );
1534  $success = ( $dbw->affectedRows() > 0 );
1535 
1536  if ( $success ) {
1537  $this->mTouched = $newTouched;
1538  $this->clearSharedCache();
1539  } else {
1540  // Clears on failure too since that is desired if the cache is stale
1541  $this->clearSharedCache( 'refresh' );
1542  }
1543 
1544  return $success;
1545  }
1546 
1554  public function clearInstanceCache( $reloadFrom = false ) {
1555  $this->mNewtalk = -1;
1556  $this->mDatePreference = null;
1557  $this->mBlockedby = -1; # Unset
1558  $this->mHash = false;
1559  $this->mRights = null;
1560  $this->mEffectiveGroups = null;
1561  $this->mImplicitGroups = null;
1562  $this->mGroupMemberships = null;
1563  $this->mOptions = null;
1564  $this->mOptionsLoaded = false;
1565  $this->mEditCount = null;
1566 
1567  if ( $reloadFrom ) {
1568  $this->mLoadedItems = [];
1569  $this->mFrom = $reloadFrom;
1570  }
1571  }
1572 
1579  public static function getDefaultOptions() {
1581 
1582  static $defOpt = null;
1583  static $defOptLang = null;
1584 
1585  if ( $defOpt !== null && $defOptLang === $wgContLang->getCode() ) {
1586  // $wgContLang does not change (and should not change) mid-request,
1587  // but the unit tests change it anyway, and expect this method to
1588  // return values relevant to the current $wgContLang.
1589  return $defOpt;
1590  }
1591 
1592  $defOpt = $wgDefaultUserOptions;
1593  // Default language setting
1594  $defOptLang = $wgContLang->getCode();
1595  $defOpt['language'] = $defOptLang;
1596  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1597  $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
1598  }
1599 
1600  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1601  // since extensions may change the set of searchable namespaces depending
1602  // on user groups/permissions.
1603  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1604  $defOpt['searchNs' . $nsnum] = (bool)$val;
1605  }
1606  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1607 
1608  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1609 
1610  return $defOpt;
1611  }
1612 
1619  public static function getDefaultOption( $opt ) {
1620  $defOpts = self::getDefaultOptions();
1621  if ( isset( $defOpts[$opt] ) ) {
1622  return $defOpts[$opt];
1623  } else {
1624  return null;
1625  }
1626  }
1627 
1634  private function getBlockedStatus( $bFromSlave = true ) {
1636 
1637  if ( -1 != $this->mBlockedby ) {
1638  return;
1639  }
1640 
1641  wfDebug( __METHOD__ . ": checking...\n" );
1642 
1643  // Initialize data...
1644  // Otherwise something ends up stomping on $this->mBlockedby when
1645  // things get lazy-loaded later, causing false positive block hits
1646  // due to -1 !== 0. Probably session-related... Nothing should be
1647  // overwriting mBlockedby, surely?
1648  $this->load();
1649 
1650  # We only need to worry about passing the IP address to the Block generator if the
1651  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1652  # know which IP address they're actually coming from
1653  $ip = null;
1654  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1655  // $wgUser->getName() only works after the end of Setup.php. Until
1656  // then, assume it's a logged-out user.
1657  $globalUserName = $wgUser->isSafeToLoad()
1658  ? $wgUser->getName()
1659  : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1660  if ( $this->getName() === $globalUserName ) {
1661  $ip = $this->getRequest()->getIP();
1662  }
1663  }
1664 
1665  // User/IP blocking
1666  $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1667 
1668  // Cookie blocking
1669  if ( !$block instanceof Block ) {
1670  $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
1671  }
1672 
1673  // Proxy blocking
1674  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1675  // Local list
1676  if ( self::isLocallyBlockedProxy( $ip ) ) {
1677  $block = new Block( [
1678  'byText' => wfMessage( 'proxyblocker' )->text(),
1679  'reason' => wfMessage( 'proxyblockreason' )->text(),
1680  'address' => $ip,
1681  'systemBlock' => 'proxy',
1682  ] );
1683  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1684  $block = new Block( [
1685  'byText' => wfMessage( 'sorbs' )->text(),
1686  'reason' => wfMessage( 'sorbsreason' )->text(),
1687  'address' => $ip,
1688  'systemBlock' => 'dnsbl',
1689  ] );
1690  }
1691  }
1692 
1693  // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
1694  if ( !$block instanceof Block
1696  && $ip !== null
1697  && !in_array( $ip, $wgProxyWhitelist )
1698  ) {
1699  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1700  $xff = array_map( 'trim', explode( ',', $xff ) );
1701  $xff = array_diff( $xff, [ $ip ] );
1702  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1703  $block = Block::chooseBlock( $xffblocks, $xff );
1704  if ( $block instanceof Block ) {
1705  # Mangle the reason to alert the user that the block
1706  # originated from matching the X-Forwarded-For header.
1707  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
1708  }
1709  }
1710 
1711  if ( !$block instanceof Block
1712  && $ip !== null
1713  && $this->isAnon()
1715  ) {
1716  $block = new Block( [
1717  'address' => $ip,
1718  'byText' => 'MediaWiki default',
1719  'reason' => wfMessage( 'softblockrangesreason', $ip )->text(),
1720  'anonOnly' => true,
1721  'systemBlock' => 'wgSoftBlockRanges',
1722  ] );
1723  }
1724 
1725  if ( $block instanceof Block ) {
1726  wfDebug( __METHOD__ . ": Found block.\n" );
1727  $this->mBlock = $block;
1728  $this->mBlockedby = $block->getByName();
1729  $this->mBlockreason = $block->mReason;
1730  $this->mHideName = $block->mHideName;
1731  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1732  } else {
1733  $this->mBlockedby = '';
1734  $this->mHideName = 0;
1735  $this->mAllowUsertalk = false;
1736  }
1737 
1738  // Avoid PHP 7.1 warning of passing $this by reference
1739  $user = $this;
1740  // Extensions
1741  Hooks::run( 'GetBlockedStatus', [ &$user ] );
1742  }
1743 
1749  protected function getBlockFromCookieValue( $blockCookieVal ) {
1750  // Make sure there's something to check. The cookie value must start with a number.
1751  if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
1752  return false;
1753  }
1754  // Load the Block from the ID in the cookie.
1755  $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
1756  if ( $blockCookieId !== null ) {
1757  // An ID was found in the cookie.
1758  $tmpBlock = Block::newFromID( $blockCookieId );
1759  if ( $tmpBlock instanceof Block ) {
1760  // Check the validity of the block.
1761  $blockIsValid = $tmpBlock->getType() == Block::TYPE_USER
1762  && !$tmpBlock->isExpired()
1763  && $tmpBlock->isAutoblocking();
1764  $config = RequestContext::getMain()->getConfig();
1765  $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
1766  if ( $blockIsValid && $useBlockCookie ) {
1767  // Use the block.
1768  return $tmpBlock;
1769  } else {
1770  // If the block is not valid, remove the cookie.
1771  Block::clearCookie( $this->getRequest()->response() );
1772  }
1773  } else {
1774  // If the block doesn't exist, remove the cookie.
1775  Block::clearCookie( $this->getRequest()->response() );
1776  }
1777  }
1778  return false;
1779  }
1780 
1788  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1790 
1791  if ( !$wgEnableDnsBlacklist ) {
1792  return false;
1793  }
1794 
1795  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
1796  return false;
1797  }
1798 
1799  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
1800  }
1801 
1809  public function inDnsBlacklist( $ip, $bases ) {
1810  $found = false;
1811  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1812  if ( IP::isIPv4( $ip ) ) {
1813  // Reverse IP, T23255
1814  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1815 
1816  foreach ( (array)$bases as $base ) {
1817  // Make hostname
1818  // If we have an access key, use that too (ProjectHoneypot, etc.)
1819  $basename = $base;
1820  if ( is_array( $base ) ) {
1821  if ( count( $base ) >= 2 ) {
1822  // Access key is 1, base URL is 0
1823  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1824  } else {
1825  $host = "$ipReversed.{$base[0]}";
1826  }
1827  $basename = $base[0];
1828  } else {
1829  $host = "$ipReversed.$base";
1830  }
1831 
1832  // Send query
1833  $ipList = gethostbynamel( $host );
1834 
1835  if ( $ipList ) {
1836  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1837  $found = true;
1838  break;
1839  } else {
1840  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1841  }
1842  }
1843  }
1844 
1845  return $found;
1846  }
1847 
1855  public static function isLocallyBlockedProxy( $ip ) {
1857 
1858  if ( !$wgProxyList ) {
1859  return false;
1860  }
1861 
1862  if ( !is_array( $wgProxyList ) ) {
1863  // Load values from the specified file
1864  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1865  }
1866 
1867  $resultProxyList = [];
1868  $deprecatedIPEntries = [];
1869 
1870  // backward compatibility: move all ip addresses in keys to values
1871  foreach ( $wgProxyList as $key => $value ) {
1872  $keyIsIP = IP::isIPAddress( $key );
1873  $valueIsIP = IP::isIPAddress( $value );
1874  if ( $keyIsIP && !$valueIsIP ) {
1875  $deprecatedIPEntries[] = $key;
1876  $resultProxyList[] = $key;
1877  } elseif ( $keyIsIP && $valueIsIP ) {
1878  $deprecatedIPEntries[] = $key;
1879  $resultProxyList[] = $key;
1880  $resultProxyList[] = $value;
1881  } else {
1882  $resultProxyList[] = $value;
1883  }
1884  }
1885 
1886  if ( $deprecatedIPEntries ) {
1887  wfDeprecated(
1888  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1889  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
1890  }
1891 
1892  $proxyListIPSet = new IPSet( $resultProxyList );
1893  return $proxyListIPSet->match( $ip );
1894  }
1895 
1901  public function isPingLimitable() {
1903  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1904  // No other good way currently to disable rate limits
1905  // for specific IPs. :P
1906  // But this is a crappy hack and should die.
1907  return false;
1908  }
1909  return !$this->isAllowed( 'noratelimit' );
1910  }
1911 
1926  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1927  // Avoid PHP 7.1 warning of passing $this by reference
1928  $user = $this;
1929  // Call the 'PingLimiter' hook
1930  $result = false;
1931  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1932  return $result;
1933  }
1934 
1936  if ( !isset( $wgRateLimits[$action] ) ) {
1937  return false;
1938  }
1939 
1940  $limits = array_merge(
1941  [ '&can-bypass' => true ],
1942  $wgRateLimits[$action]
1943  );
1944 
1945  // Some groups shouldn't trigger the ping limiter, ever
1946  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1947  return false;
1948  }
1949 
1950  $keys = [];
1951  $id = $this->getId();
1952  $userLimit = false;
1953  $isNewbie = $this->isNewbie();
1955 
1956  if ( $id == 0 ) {
1957  // limits for anons
1958  if ( isset( $limits['anon'] ) ) {
1959  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1960  }
1961  } else {
1962  // limits for logged-in users
1963  if ( isset( $limits['user'] ) ) {
1964  $userLimit = $limits['user'];
1965  }
1966  }
1967 
1968  // limits for anons and for newbie logged-in users
1969  if ( $isNewbie ) {
1970  // ip-based limits
1971  if ( isset( $limits['ip'] ) ) {
1972  $ip = $this->getRequest()->getIP();
1973  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1974  }
1975  // subnet-based limits
1976  if ( isset( $limits['subnet'] ) ) {
1977  $ip = $this->getRequest()->getIP();
1978  $subnet = IP::getSubnet( $ip );
1979  if ( $subnet !== false ) {
1980  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1981  }
1982  }
1983  }
1984 
1985  // Check for group-specific permissions
1986  // If more than one group applies, use the group with the highest limit ratio (max/period)
1987  foreach ( $this->getGroups() as $group ) {
1988  if ( isset( $limits[$group] ) ) {
1989  if ( $userLimit === false
1990  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1991  ) {
1992  $userLimit = $limits[$group];
1993  }
1994  }
1995  }
1996 
1997  // limits for newbie logged-in users (override all the normal user limits)
1998  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1999  $userLimit = $limits['newbie'];
2000  }
2001 
2002  // Set the user limit key
2003  if ( $userLimit !== false ) {
2004  list( $max, $period ) = $userLimit;
2005  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2006  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2007  }
2008 
2009  // ip-based limits for all ping-limitable users
2010  if ( isset( $limits['ip-all'] ) ) {
2011  $ip = $this->getRequest()->getIP();
2012  // ignore if user limit is more permissive
2013  if ( $isNewbie || $userLimit === false
2014  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2015  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2016  }
2017  }
2018 
2019  // subnet-based limits for all ping-limitable users
2020  if ( isset( $limits['subnet-all'] ) ) {
2021  $ip = $this->getRequest()->getIP();
2022  $subnet = IP::getSubnet( $ip );
2023  if ( $subnet !== false ) {
2024  // ignore if user limit is more permissive
2025  if ( $isNewbie || $userLimit === false
2026  || $limits['ip-all'][0] / $limits['ip-all'][1]
2027  > $userLimit[0] / $userLimit[1] ) {
2028  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2029  }
2030  }
2031  }
2032 
2033  $triggered = false;
2034  foreach ( $keys as $key => $limit ) {
2035  list( $max, $period ) = $limit;
2036  $summary = "(limit $max in {$period}s)";
2037  $count = $cache->get( $key );
2038  // Already pinged?
2039  if ( $count ) {
2040  if ( $count >= $max ) {
2041  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2042  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2043  $triggered = true;
2044  } else {
2045  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2046  }
2047  } else {
2048  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2049  if ( $incrBy > 0 ) {
2050  $cache->add( $key, 0, intval( $period ) ); // first ping
2051  }
2052  }
2053  if ( $incrBy > 0 ) {
2054  $cache->incr( $key, $incrBy );
2055  }
2056  }
2057 
2058  return $triggered;
2059  }
2060 
2068  public function isBlocked( $bFromSlave = true ) {
2069  return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
2070  }
2071 
2078  public function getBlock( $bFromSlave = true ) {
2079  $this->getBlockedStatus( $bFromSlave );
2080  return $this->mBlock instanceof Block ? $this->mBlock : null;
2081  }
2082 
2090  public function isBlockedFrom( $title, $bFromSlave = false ) {
2092 
2093  $blocked = $this->isBlocked( $bFromSlave );
2094  $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
2095  // If a user's name is suppressed, they cannot make edits anywhere
2096  if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
2097  && $title->getNamespace() == NS_USER_TALK ) {
2098  $blocked = false;
2099  wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
2100  }
2101 
2102  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
2103 
2104  return $blocked;
2105  }
2106 
2111  public function blockedBy() {
2112  $this->getBlockedStatus();
2113  return $this->mBlockedby;
2114  }
2115 
2120  public function blockedFor() {
2121  $this->getBlockedStatus();
2122  return $this->mBlockreason;
2123  }
2124 
2129  public function getBlockId() {
2130  $this->getBlockedStatus();
2131  return ( $this->mBlock ? $this->mBlock->getId() : false );
2132  }
2133 
2142  public function isBlockedGlobally( $ip = '' ) {
2143  return $this->getGlobalBlock( $ip ) instanceof Block;
2144  }
2145 
2156  public function getGlobalBlock( $ip = '' ) {
2157  if ( $this->mGlobalBlock !== null ) {
2158  return $this->mGlobalBlock ?: null;
2159  }
2160  // User is already an IP?
2161  if ( IP::isIPAddress( $this->getName() ) ) {
2162  $ip = $this->getName();
2163  } elseif ( !$ip ) {
2164  $ip = $this->getRequest()->getIP();
2165  }
2166  // Avoid PHP 7.1 warning of passing $this by reference
2167  $user = $this;
2168  $blocked = false;
2169  $block = null;
2170  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2171 
2172  if ( $blocked && $block === null ) {
2173  // back-compat: UserIsBlockedGlobally didn't have $block param first
2174  $block = new Block( [
2175  'address' => $ip,
2176  'systemBlock' => 'global-block'
2177  ] );
2178  }
2179 
2180  $this->mGlobalBlock = $blocked ? $block : false;
2181  return $this->mGlobalBlock ?: null;
2182  }
2183 
2189  public function isLocked() {
2190  if ( $this->mLocked !== null ) {
2191  return $this->mLocked;
2192  }
2193  // Avoid PHP 7.1 warning of passing $this by reference
2194  $user = $this;
2195  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2196  $this->mLocked = $authUser && $authUser->isLocked();
2197  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2198  return $this->mLocked;
2199  }
2200 
2206  public function isHidden() {
2207  if ( $this->mHideName !== null ) {
2208  return $this->mHideName;
2209  }
2210  $this->getBlockedStatus();
2211  if ( !$this->mHideName ) {
2212  // Avoid PHP 7.1 warning of passing $this by reference
2213  $user = $this;
2214  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2215  $this->mHideName = $authUser && $authUser->isHidden();
2216  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2217  }
2218  return $this->mHideName;
2219  }
2220 
2225  public function getId() {
2226  if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2227  // Special case, we know the user is anonymous
2228  return 0;
2229  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2230  // Don't load if this was initialized from an ID
2231  $this->load();
2232  }
2233 
2234  return (int)$this->mId;
2235  }
2236 
2241  public function setId( $v ) {
2242  $this->mId = $v;
2243  $this->clearInstanceCache( 'id' );
2244  }
2245 
2250  public function getName() {
2251  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2252  // Special case optimisation
2253  return $this->mName;
2254  } else {
2255  $this->load();
2256  if ( $this->mName === false ) {
2257  // Clean up IPs
2258  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2259  }
2260  return $this->mName;
2261  }
2262  }
2263 
2277  public function setName( $str ) {
2278  $this->load();
2279  $this->mName = $str;
2280  }
2281 
2286  public function getTitleKey() {
2287  return str_replace( ' ', '_', $this->getName() );
2288  }
2289 
2294  public function getNewtalk() {
2295  $this->load();
2296 
2297  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2298  if ( $this->mNewtalk === -1 ) {
2299  $this->mNewtalk = false; # reset talk page status
2300 
2301  // Check memcached separately for anons, who have no
2302  // entire User object stored in there.
2303  if ( !$this->mId ) {
2305  if ( $wgDisableAnonTalk ) {
2306  // Anon newtalk disabled by configuration.
2307  $this->mNewtalk = false;
2308  } else {
2309  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2310  }
2311  } else {
2312  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2313  }
2314  }
2315 
2316  return (bool)$this->mNewtalk;
2317  }
2318 
2332  public function getNewMessageLinks() {
2333  // Avoid PHP 7.1 warning of passing $this by reference
2334  $user = $this;
2335  $talks = [];
2336  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2337  return $talks;
2338  } elseif ( !$this->getNewtalk() ) {
2339  return [];
2340  }
2341  $utp = $this->getTalkPage();
2342  $dbr = wfGetDB( DB_REPLICA );
2343  // Get the "last viewed rev" timestamp from the oldest message notification
2344  $timestamp = $dbr->selectField( 'user_newtalk',
2345  'MIN(user_last_timestamp)',
2346  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2347  __METHOD__ );
2348  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2349  return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2350  }
2351 
2357  public function getNewMessageRevisionId() {
2358  $newMessageRevisionId = null;
2359  $newMessageLinks = $this->getNewMessageLinks();
2360  if ( $newMessageLinks ) {
2361  // Note: getNewMessageLinks() never returns more than a single link
2362  // and it is always for the same wiki, but we double-check here in
2363  // case that changes some time in the future.
2364  if ( count( $newMessageLinks ) === 1
2365  && $newMessageLinks[0]['wiki'] === wfWikiID()
2366  && $newMessageLinks[0]['rev']
2367  ) {
2369  $newMessageRevision = $newMessageLinks[0]['rev'];
2370  $newMessageRevisionId = $newMessageRevision->getId();
2371  }
2372  }
2373  return $newMessageRevisionId;
2374  }
2375 
2384  protected function checkNewtalk( $field, $id ) {
2385  $dbr = wfGetDB( DB_REPLICA );
2386 
2387  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2388 
2389  return $ok !== false;
2390  }
2391 
2399  protected function updateNewtalk( $field, $id, $curRev = null ) {
2400  // Get timestamp of the talk page revision prior to the current one
2401  $prevRev = $curRev ? $curRev->getPrevious() : false;
2402  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2403  // Mark the user as having new messages since this revision
2404  $dbw = wfGetDB( DB_MASTER );
2405  $dbw->insert( 'user_newtalk',
2406  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2407  __METHOD__,
2408  'IGNORE' );
2409  if ( $dbw->affectedRows() ) {
2410  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2411  return true;
2412  } else {
2413  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2414  return false;
2415  }
2416  }
2417 
2424  protected function deleteNewtalk( $field, $id ) {
2425  $dbw = wfGetDB( DB_MASTER );
2426  $dbw->delete( 'user_newtalk',
2427  [ $field => $id ],
2428  __METHOD__ );
2429  if ( $dbw->affectedRows() ) {
2430  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2431  return true;
2432  } else {
2433  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2434  return false;
2435  }
2436  }
2437 
2444  public function setNewtalk( $val, $curRev = null ) {
2445  if ( wfReadOnly() ) {
2446  return;
2447  }
2448 
2449  $this->load();
2450  $this->mNewtalk = $val;
2451 
2452  if ( $this->isAnon() ) {
2453  $field = 'user_ip';
2454  $id = $this->getName();
2455  } else {
2456  $field = 'user_id';
2457  $id = $this->getId();
2458  }
2459 
2460  if ( $val ) {
2461  $changed = $this->updateNewtalk( $field, $id, $curRev );
2462  } else {
2463  $changed = $this->deleteNewtalk( $field, $id );
2464  }
2465 
2466  if ( $changed ) {
2467  $this->invalidateCache();
2468  }
2469  }
2470 
2476  private function newTouchedTimestamp() {
2478 
2479  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2480  if ( $this->mTouched && $time <= $this->mTouched ) {
2481  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2482  }
2483 
2484  return $time;
2485  }
2486 
2497  public function clearSharedCache( $mode = 'changed' ) {
2498  if ( !$this->getId() ) {
2499  return;
2500  }
2501 
2503  $key = $this->getCacheKey( $cache );
2504  if ( $mode === 'refresh' ) {
2505  $cache->delete( $key, 1 );
2506  } else {
2507  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
2508  function () use ( $cache, $key ) {
2509  $cache->delete( $key );
2510  },
2511  __METHOD__
2512  );
2513  }
2514  }
2515 
2521  public function invalidateCache() {
2522  $this->touch();
2523  $this->clearSharedCache();
2524  }
2525 
2538  public function touch() {
2539  $id = $this->getId();
2540  if ( $id ) {
2541  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2542  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2543  $cache->touchCheckKey( $key );
2544  $this->mQuickTouched = null;
2545  }
2546  }
2547 
2553  public function validateCache( $timestamp ) {
2554  return ( $timestamp >= $this->getTouched() );
2555  }
2556 
2565  public function getTouched() {
2566  $this->load();
2567 
2568  if ( $this->mId ) {
2569  if ( $this->mQuickTouched === null ) {
2570  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2571  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2572 
2573  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2574  }
2575 
2576  return max( $this->mTouched, $this->mQuickTouched );
2577  }
2578 
2579  return $this->mTouched;
2580  }
2581 
2587  public function getDBTouched() {
2588  $this->load();
2589 
2590  return $this->mTouched;
2591  }
2592 
2609  public function setPassword( $str ) {
2610  return $this->setPasswordInternal( $str );
2611  }
2612 
2621  public function setInternalPassword( $str ) {
2622  $this->setPasswordInternal( $str );
2623  }
2624 
2633  private function setPasswordInternal( $str ) {
2634  $manager = AuthManager::singleton();
2635 
2636  // If the user doesn't exist yet, fail
2637  if ( !$manager->userExists( $this->getName() ) ) {
2638  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2639  }
2640 
2641  $status = $this->changeAuthenticationData( [
2642  'username' => $this->getName(),
2643  'password' => $str,
2644  'retype' => $str,
2645  ] );
2646  if ( !$status->isGood() ) {
2648  ->info( __METHOD__ . ': Password change rejected: '
2649  . $status->getWikiText( null, null, 'en' ) );
2650  return false;
2651  }
2652 
2653  $this->setOption( 'watchlisttoken', false );
2654  SessionManager::singleton()->invalidateSessionsForUser( $this );
2655 
2656  return true;
2657  }
2658 
2671  public function changeAuthenticationData( array $data ) {
2672  $manager = AuthManager::singleton();
2673  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2674  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2675 
2676  $status = Status::newGood( 'ignored' );
2677  foreach ( $reqs as $req ) {
2678  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2679  }
2680  if ( $status->getValue() === 'ignored' ) {
2681  $status->warning( 'authenticationdatachange-ignored' );
2682  }
2683 
2684  if ( $status->isGood() ) {
2685  foreach ( $reqs as $req ) {
2686  $manager->changeAuthenticationData( $req );
2687  }
2688  }
2689  return $status;
2690  }
2691 
2698  public function getToken( $forceCreation = true ) {
2700 
2701  $this->load();
2702  if ( !$this->mToken && $forceCreation ) {
2703  $this->setToken();
2704  }
2705 
2706  if ( !$this->mToken ) {
2707  // The user doesn't have a token, return null to indicate that.
2708  return null;
2709  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2710  // We return a random value here so existing token checks are very
2711  // likely to fail.
2712  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2713  } elseif ( $wgAuthenticationTokenVersion === null ) {
2714  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2715  return $this->mToken;
2716  } else {
2717  // $wgAuthenticationTokenVersion in use, so hmac it.
2718  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2719 
2720  // The raw hash can be overly long. Shorten it up.
2721  $len = max( 32, self::TOKEN_LENGTH );
2722  if ( strlen( $ret ) < $len ) {
2723  // Should never happen, even md5 is 128 bits
2724  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2725  }
2726  return substr( $ret, -$len );
2727  }
2728  }
2729 
2736  public function setToken( $token = false ) {
2737  $this->load();
2738  if ( $this->mToken === self::INVALID_TOKEN ) {
2740  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2741  } elseif ( !$token ) {
2742  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2743  } else {
2744  $this->mToken = $token;
2745  }
2746  }
2747 
2756  public function setNewpassword( $str, $throttle = true ) {
2757  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2758  }
2759 
2764  public function getEmail() {
2765  $this->load();
2766  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2767  return $this->mEmail;
2768  }
2769 
2775  $this->load();
2776  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2778  }
2779 
2784  public function setEmail( $str ) {
2785  $this->load();
2786  if ( $str == $this->mEmail ) {
2787  return;
2788  }
2789  $this->invalidateEmail();
2790  $this->mEmail = $str;
2791  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2792  }
2793 
2801  public function setEmailWithConfirmation( $str ) {
2803 
2804  if ( !$wgEnableEmail ) {
2805  return Status::newFatal( 'emaildisabled' );
2806  }
2807 
2808  $oldaddr = $this->getEmail();
2809  if ( $str === $oldaddr ) {
2810  return Status::newGood( true );
2811  }
2812 
2813  $type = $oldaddr != '' ? 'changed' : 'set';
2814  $notificationResult = null;
2815 
2816  if ( $wgEmailAuthentication ) {
2817  // Send the user an email notifying the user of the change in registered
2818  // email address on their previous email address
2819  if ( $type == 'changed' ) {
2820  $change = $str != '' ? 'changed' : 'removed';
2821  $notificationResult = $this->sendMail(
2822  wfMessage( 'notificationemail_subject_' . $change )->text(),
2823  wfMessage( 'notificationemail_body_' . $change,
2824  $this->getRequest()->getIP(),
2825  $this->getName(),
2826  $str )->text()
2827  );
2828  }
2829  }
2830 
2831  $this->setEmail( $str );
2832 
2833  if ( $str !== '' && $wgEmailAuthentication ) {
2834  // Send a confirmation request to the new address if needed
2835  $result = $this->sendConfirmationMail( $type );
2836 
2837  if ( $notificationResult !== null ) {
2838  $result->merge( $notificationResult );
2839  }
2840 
2841  if ( $result->isGood() ) {
2842  // Say to the caller that a confirmation and notification mail has been sent
2843  $result->value = 'eauth';
2844  }
2845  } else {
2846  $result = Status::newGood( true );
2847  }
2848 
2849  return $result;
2850  }
2851 
2856  public function getRealName() {
2857  if ( !$this->isItemLoaded( 'realname' ) ) {
2858  $this->load();
2859  }
2860 
2861  return $this->mRealName;
2862  }
2863 
2868  public function setRealName( $str ) {
2869  $this->load();
2870  $this->mRealName = $str;
2871  }
2872 
2883  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2885  $this->loadOptions();
2886 
2887  # We want 'disabled' preferences to always behave as the default value for
2888  # users, even if they have set the option explicitly in their settings (ie they
2889  # set it, and then it was disabled removing their ability to change it). But
2890  # we don't want to erase the preferences in the database in case the preference
2891  # is re-enabled again. So don't touch $mOptions, just override the returned value
2892  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2893  return self::getDefaultOption( $oname );
2894  }
2895 
2896  if ( array_key_exists( $oname, $this->mOptions ) ) {
2897  return $this->mOptions[$oname];
2898  } else {
2899  return $defaultOverride;
2900  }
2901  }
2902 
2911  public function getOptions( $flags = 0 ) {
2913  $this->loadOptions();
2915 
2916  # We want 'disabled' preferences to always behave as the default value for
2917  # users, even if they have set the option explicitly in their settings (ie they
2918  # set it, and then it was disabled removing their ability to change it). But
2919  # we don't want to erase the preferences in the database in case the preference
2920  # is re-enabled again. So don't touch $mOptions, just override the returned value
2921  foreach ( $wgHiddenPrefs as $pref ) {
2922  $default = self::getDefaultOption( $pref );
2923  if ( $default !== null ) {
2924  $options[$pref] = $default;
2925  }
2926  }
2927 
2928  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2929  $options = array_diff_assoc( $options, self::getDefaultOptions() );
2930  }
2931 
2932  return $options;
2933  }
2934 
2942  public function getBoolOption( $oname ) {
2943  return (bool)$this->getOption( $oname );
2944  }
2945 
2954  public function getIntOption( $oname, $defaultOverride = 0 ) {
2955  $val = $this->getOption( $oname );
2956  if ( $val == '' ) {
2957  $val = $defaultOverride;
2958  }
2959  return intval( $val );
2960  }
2961 
2970  public function setOption( $oname, $val ) {
2971  $this->loadOptions();
2972 
2973  // Explicitly NULL values should refer to defaults
2974  if ( is_null( $val ) ) {
2975  $val = self::getDefaultOption( $oname );
2976  }
2977 
2978  $this->mOptions[$oname] = $val;
2979  }
2980 
2991  public function getTokenFromOption( $oname ) {
2993 
2994  $id = $this->getId();
2995  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2996  return false;
2997  }
2998 
2999  $token = $this->getOption( $oname );
3000  if ( !$token ) {
3001  // Default to a value based on the user token to avoid space
3002  // wasted on storing tokens for all users. When this option
3003  // is set manually by the user, only then is it stored.
3004  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3005  }
3006 
3007  return $token;
3008  }
3009 
3019  public function resetTokenFromOption( $oname ) {
3021  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3022  return false;
3023  }
3024 
3025  $token = MWCryptRand::generateHex( 40 );
3026  $this->setOption( $oname, $token );
3027  return $token;
3028  }
3029 
3053  public static function listOptionKinds() {
3054  return [
3055  'registered',
3056  'registered-multiselect',
3057  'registered-checkmatrix',
3058  'userjs',
3059  'special',
3060  'unused'
3061  ];
3062  }
3063 
3076  public function getOptionKinds( IContextSource $context, $options = null ) {
3077  $this->loadOptions();
3078  if ( $options === null ) {
3080  }
3081 
3082  $prefs = Preferences::getPreferences( $this, $context );
3083  $mapping = [];
3084 
3085  // Pull out the "special" options, so they don't get converted as
3086  // multiselect or checkmatrix.
3087  $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
3088  foreach ( $specialOptions as $name => $value ) {
3089  unset( $prefs[$name] );
3090  }
3091 
3092  // Multiselect and checkmatrix options are stored in the database with
3093  // one key per option, each having a boolean value. Extract those keys.
3094  $multiselectOptions = [];
3095  foreach ( $prefs as $name => $info ) {
3096  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3097  ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
3098  $opts = HTMLFormField::flattenOptions( $info['options'] );
3099  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
3100 
3101  foreach ( $opts as $value ) {
3102  $multiselectOptions["$prefix$value"] = true;
3103  }
3104 
3105  unset( $prefs[$name] );
3106  }
3107  }
3108  $checkmatrixOptions = [];
3109  foreach ( $prefs as $name => $info ) {
3110  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3111  ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
3112  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3113  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3114  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
3115 
3116  foreach ( $columns as $column ) {
3117  foreach ( $rows as $row ) {
3118  $checkmatrixOptions["$prefix$column-$row"] = true;
3119  }
3120  }
3121 
3122  unset( $prefs[$name] );
3123  }
3124  }
3125 
3126  // $value is ignored
3127  foreach ( $options as $key => $value ) {
3128  if ( isset( $prefs[$key] ) ) {
3129  $mapping[$key] = 'registered';
3130  } elseif ( isset( $multiselectOptions[$key] ) ) {
3131  $mapping[$key] = 'registered-multiselect';
3132  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3133  $mapping[$key] = 'registered-checkmatrix';
3134  } elseif ( isset( $specialOptions[$key] ) ) {
3135  $mapping[$key] = 'special';
3136  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3137  $mapping[$key] = 'userjs';
3138  } else {
3139  $mapping[$key] = 'unused';
3140  }
3141  }
3142 
3143  return $mapping;
3144  }
3145 
3160  public function resetOptions(
3161  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3162  IContextSource $context = null
3163  ) {
3164  $this->load();
3165  $defaultOptions = self::getDefaultOptions();
3166 
3167  if ( !is_array( $resetKinds ) ) {
3168  $resetKinds = [ $resetKinds ];
3169  }
3170 
3171  if ( in_array( 'all', $resetKinds ) ) {
3172  $newOptions = $defaultOptions;
3173  } else {
3174  if ( $context === null ) {
3176  }
3177 
3178  $optionKinds = $this->getOptionKinds( $context );
3179  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3180  $newOptions = [];
3181 
3182  // Use default values for the options that should be deleted, and
3183  // copy old values for the ones that shouldn't.
3184  foreach ( $this->mOptions as $key => $value ) {
3185  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3186  if ( array_key_exists( $key, $defaultOptions ) ) {
3187  $newOptions[$key] = $defaultOptions[$key];
3188  }
3189  } else {
3190  $newOptions[$key] = $value;
3191  }
3192  }
3193  }
3194 
3195  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3196 
3197  $this->mOptions = $newOptions;
3198  $this->mOptionsLoaded = true;
3199  }
3200 
3205  public function getDatePreference() {
3206  // Important migration for old data rows
3207  if ( is_null( $this->mDatePreference ) ) {
3208  global $wgLang;
3209  $value = $this->getOption( 'date' );
3210  $map = $wgLang->getDatePreferenceMigrationMap();
3211  if ( isset( $map[$value] ) ) {
3212  $value = $map[$value];
3213  }
3214  $this->mDatePreference = $value;
3215  }
3216  return $this->mDatePreference;
3217  }
3218 
3225  public function requiresHTTPS() {
3227  if ( !$wgSecureLogin ) {
3228  return false;
3229  } else {
3230  $https = $this->getBoolOption( 'prefershttps' );
3231  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3232  if ( $https ) {
3233  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3234  }
3235  return $https;
3236  }
3237  }
3238 
3244  public function getStubThreshold() {
3245  global $wgMaxArticleSize; # Maximum article size, in Kb
3246  $threshold = $this->getIntOption( 'stubthreshold' );
3247  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3248  // If they have set an impossible value, disable the preference
3249  // so we can use the parser cache again.
3250  $threshold = 0;
3251  }
3252  return $threshold;
3253  }
3254 
3259  public function getRights() {
3260  if ( is_null( $this->mRights ) ) {
3261  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3262  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3263 
3264  // Deny any rights denied by the user's session, unless this
3265  // endpoint has no sessions.
3266  if ( !defined( 'MW_NO_SESSION' ) ) {
3267  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3268  if ( $allowedRights !== null ) {
3269  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3270  }
3271  }
3272 
3273  // Force reindexation of rights when a hook has unset one of them
3274  $this->mRights = array_values( array_unique( $this->mRights ) );
3275 
3276  // If block disables login, we should also remove any
3277  // extra rights blocked users might have, in case the
3278  // blocked user has a pre-existing session (T129738).
3279  // This is checked here for cases where people only call
3280  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3281  // to give a better error message in the common case.
3282  $config = RequestContext::getMain()->getConfig();
3283  if (
3284  $this->isLoggedIn() &&
3285  $config->get( 'BlockDisablesLogin' ) &&
3286  $this->isBlocked()
3287  ) {
3288  $anon = new User;
3289  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3290  }
3291  }
3292  return $this->mRights;
3293  }
3294 
3300  public function getGroups() {
3301  $this->load();
3302  $this->loadGroups();
3303  return array_keys( $this->mGroupMemberships );
3304  }
3305 
3313  public function getGroupMemberships() {
3314  $this->load();
3315  $this->loadGroups();
3316  return $this->mGroupMemberships;
3317  }
3318 
3326  public function getEffectiveGroups( $recache = false ) {
3327  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3328  $this->mEffectiveGroups = array_unique( array_merge(
3329  $this->getGroups(), // explicit groups
3330  $this->getAutomaticGroups( $recache ) // implicit groups
3331  ) );
3332  // Avoid PHP 7.1 warning of passing $this by reference
3333  $user = $this;
3334  // Hook for additional groups
3335  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3336  // Force reindexation of groups when a hook has unset one of them
3337  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3338  }
3339  return $this->mEffectiveGroups;
3340  }
3341 
3349  public function getAutomaticGroups( $recache = false ) {
3350  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3351  $this->mImplicitGroups = [ '*' ];
3352  if ( $this->getId() ) {
3353  $this->mImplicitGroups[] = 'user';
3354 
3355  $this->mImplicitGroups = array_unique( array_merge(
3356  $this->mImplicitGroups,
3358  ) );
3359  }
3360  if ( $recache ) {
3361  // Assure data consistency with rights/groups,
3362  // as getEffectiveGroups() depends on this function
3363  $this->mEffectiveGroups = null;
3364  }
3365  }
3366  return $this->mImplicitGroups;
3367  }
3368 
3378  public function getFormerGroups() {
3379  $this->load();
3380 
3381  if ( is_null( $this->mFormerGroups ) ) {
3382  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3383  ? wfGetDB( DB_MASTER )
3384  : wfGetDB( DB_REPLICA );
3385  $res = $db->select( 'user_former_groups',
3386  [ 'ufg_group' ],
3387  [ 'ufg_user' => $this->mId ],
3388  __METHOD__ );
3389  $this->mFormerGroups = [];
3390  foreach ( $res as $row ) {
3391  $this->mFormerGroups[] = $row->ufg_group;
3392  }
3393  }
3394 
3395  return $this->mFormerGroups;
3396  }
3397 
3402  public function getEditCount() {
3403  if ( !$this->getId() ) {
3404  return null;
3405  }
3406 
3407  if ( $this->mEditCount === null ) {
3408  /* Populate the count, if it has not been populated yet */
3409  $dbr = wfGetDB( DB_REPLICA );
3410  // check if the user_editcount field has been initialized
3411  $count = $dbr->selectField(
3412  'user', 'user_editcount',
3413  [ 'user_id' => $this->mId ],
3414  __METHOD__
3415  );
3416 
3417  if ( $count === null ) {
3418  // it has not been initialized. do so.
3419  $count = $this->initEditCount();
3420  }
3421  $this->mEditCount = $count;
3422  }
3423  return (int)$this->mEditCount;
3424  }
3425 
3437  public function addGroup( $group, $expiry = null ) {
3438  $this->load();
3439  $this->loadGroups();
3440 
3441  if ( $expiry ) {
3442  $expiry = wfTimestamp( TS_MW, $expiry );
3443  }
3444 
3445  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3446  return false;
3447  }
3448 
3449  // create the new UserGroupMembership and put it in the DB
3450  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3451  if ( !$ugm->insert( true ) ) {
3452  return false;
3453  }
3454 
3455  $this->mGroupMemberships[$group] = $ugm;
3456 
3457  // Refresh the groups caches, and clear the rights cache so it will be
3458  // refreshed on the next call to $this->getRights().
3459  $this->getEffectiveGroups( true );
3460  $this->mRights = null;
3461 
3462  $this->invalidateCache();
3463 
3464  return true;
3465  }
3466 
3473  public function removeGroup( $group ) {
3474  $this->load();
3475 
3476  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3477  return false;
3478  }
3479 
3480  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3481  // delete the membership entry
3482  if ( !$ugm || !$ugm->delete() ) {
3483  return false;
3484  }
3485 
3486  $this->loadGroups();
3487  unset( $this->mGroupMemberships[$group] );
3488 
3489  // Refresh the groups caches, and clear the rights cache so it will be
3490  // refreshed on the next call to $this->getRights().
3491  $this->getEffectiveGroups( true );
3492  $this->mRights = null;
3493 
3494  $this->invalidateCache();
3495 
3496  return true;
3497  }
3498 
3503  public function isLoggedIn() {
3504  return $this->getId() != 0;
3505  }
3506 
3511  public function isAnon() {
3512  return !$this->isLoggedIn();
3513  }
3514 
3519  public function isBot() {
3520  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3521  return true;
3522  }
3523 
3524  $isBot = false;
3525  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3526 
3527  return $isBot;
3528  }
3529 
3536  public function isAllowedAny() {
3537  $permissions = func_get_args();
3538  foreach ( $permissions as $permission ) {
3539  if ( $this->isAllowed( $permission ) ) {
3540  return true;
3541  }
3542  }
3543  return false;
3544  }
3545 
3551  public function isAllowedAll() {
3552  $permissions = func_get_args();
3553  foreach ( $permissions as $permission ) {
3554  if ( !$this->isAllowed( $permission ) ) {
3555  return false;
3556  }
3557  }
3558  return true;
3559  }
3560 
3566  public function isAllowed( $action = '' ) {
3567  if ( $action === '' ) {
3568  return true; // In the spirit of DWIM
3569  }
3570  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3571  // by misconfiguration: 0 == 'foo'
3572  return in_array( $action, $this->getRights(), true );
3573  }
3574 
3579  public function useRCPatrol() {
3581  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3582  }
3583 
3588  public function useNPPatrol() {
3590  return (
3591  ( $wgUseRCPatrol || $wgUseNPPatrol )
3592  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3593  );
3594  }
3595 
3600  public function useFilePatrol() {
3602  return (
3603  ( $wgUseRCPatrol || $wgUseFilePatrol )
3604  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3605  );
3606  }
3607 
3613  public function getRequest() {
3614  if ( $this->mRequest ) {
3615  return $this->mRequest;
3616  } else {
3618  return $wgRequest;
3619  }
3620  }
3621 
3630  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3631  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3632  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3633  }
3634  return false;
3635  }
3636 
3644  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3645  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3646  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3647  $this,
3648  [ $title->getSubjectPage(), $title->getTalkPage() ]
3649  );
3650  }
3651  $this->invalidateCache();
3652  }
3653 
3661  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3662  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3663  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3664  $store->removeWatch( $this, $title->getSubjectPage() );
3665  $store->removeWatch( $this, $title->getTalkPage() );
3666  }
3667  $this->invalidateCache();
3668  }
3669 
3678  public function clearNotification( &$title, $oldid = 0 ) {
3680 
3681  // Do nothing if the database is locked to writes
3682  if ( wfReadOnly() ) {
3683  return;
3684  }
3685 
3686  // Do nothing if not allowed to edit the watchlist
3687  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3688  return;
3689  }
3690 
3691  // If we're working on user's talk page, we should update the talk page message indicator
3692  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3693  // Avoid PHP 7.1 warning of passing $this by reference
3694  $user = $this;
3695  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3696  return;
3697  }
3698 
3699  // Try to update the DB post-send and only if needed...
3700  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3701  if ( !$this->getNewtalk() ) {
3702  return; // no notifications to clear
3703  }
3704 
3705  // Delete the last notifications (they stack up)
3706  $this->setNewtalk( false );
3707 
3708  // If there is a new, unseen, revision, use its timestamp
3709  $nextid = $oldid
3710  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3711  : null;
3712  if ( $nextid ) {
3713  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3714  }
3715  } );
3716  }
3717 
3718  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3719  return;
3720  }
3721 
3722  if ( $this->isAnon() ) {
3723  // Nothing else to do...
3724  return;
3725  }
3726 
3727  // Only update the timestamp if the page is being watched.
3728  // The query to find out if it is watched is cached both in memcached and per-invocation,
3729  // and when it does have to be executed, it can be on a replica DB
3730  // If this is the user's newtalk page, we always update the timestamp
3731  $force = '';
3732  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3733  $force = 'force';
3734  }
3735 
3736  MediaWikiServices::getInstance()->getWatchedItemStore()
3737  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3738  }
3739 
3746  public function clearAllNotifications() {
3748  // Do nothing if not allowed to edit the watchlist
3749  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3750  return;
3751  }
3752 
3753  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3754  $this->setNewtalk( false );
3755  return;
3756  }
3757 
3758  $id = $this->getId();
3759  if ( !$id ) {
3760  return;
3761  }
3762 
3763  $dbw = wfGetDB( DB_MASTER );
3764  $asOfTimes = array_unique( $dbw->selectFieldValues(
3765  'watchlist',
3766  'wl_notificationtimestamp',
3767  [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3768  __METHOD__,
3769  [ 'ORDER BY' => 'wl_notificationtimestamp DESC', 'LIMIT' => 500 ]
3770  ) );
3771  if ( !$asOfTimes ) {
3772  return;
3773  }
3774  // Immediately update the most recent touched rows, which hopefully covers what
3775  // the user sees on the watchlist page before pressing "mark all pages visited"....
3776  $dbw->update(
3777  'watchlist',
3778  [ 'wl_notificationtimestamp' => null ],
3779  [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimes ],
3780  __METHOD__
3781  );
3782  // ...and finish the older ones in a post-send update with lag checks...
3784  $dbw,
3785  __METHOD__,
3786  function () use ( $dbw, $id ) {
3788 
3789  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
3790  $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
3791  $asOfTimes = array_unique( $dbw->selectFieldValues(
3792  'watchlist',
3793  'wl_notificationtimestamp',
3794  [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3795  __METHOD__
3796  ) );
3797  foreach ( array_chunk( $asOfTimes, $wgUpdateRowsPerQuery ) as $asOfTimeBatch ) {
3798  $dbw->update(
3799  'watchlist',
3800  [ 'wl_notificationtimestamp' => null ],
3801  [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimeBatch ],
3802  __METHOD__
3803  );
3804  $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
3805  }
3806  }
3807  ) );
3808  // We also need to clear here the "you have new message" notification for the own
3809  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3810  }
3811 
3817  public function getExperienceLevel() {
3822 
3823  if ( $this->isAnon() ) {
3824  return false;
3825  }
3826 
3827  $editCount = $this->getEditCount();
3828  $registration = $this->getRegistration();
3829  $now = time();
3830  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3831  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3832 
3833  if (
3834  $editCount < $wgLearnerEdits ||
3835  $registration > $learnerRegistration
3836  ) {
3837  return 'newcomer';
3838  } elseif (
3839  $editCount > $wgExperiencedUserEdits &&
3840  $registration <= $experiencedRegistration
3841  ) {
3842  return 'experienced';
3843  } else {
3844  return 'learner';
3845  }
3846  }
3847 
3864  protected function setCookie(
3865  $name, $value, $exp = 0, $secure = null, $params = [], $request = null
3866  ) {
3867  wfDeprecated( __METHOD__, '1.27' );
3868  if ( $request === null ) {
3869  $request = $this->getRequest();
3870  }
3871  $params['secure'] = $secure;
3872  $request->response()->setCookie( $name, $value, $exp, $params );
3873  }
3874 
3885  protected function clearCookie( $name, $secure = null, $params = [] ) {
3886  wfDeprecated( __METHOD__, '1.27' );
3887  $this->setCookie( $name, '', time() - 86400, $secure, $params );
3888  }
3889 
3905  protected function setExtendedLoginCookie( $name, $value, $secure ) {
3907 
3908  wfDeprecated( __METHOD__, '1.27' );
3909 
3910  $exp = time();
3911  $exp += $wgExtendedLoginCookieExpiration !== null
3914 
3915  $this->setCookie( $name, $value, $exp, $secure );
3916  }
3917 
3926  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3927  $this->load();
3928  if ( 0 == $this->mId ) {
3929  return;
3930  }
3931 
3932  $session = $this->getRequest()->getSession();
3933  if ( $request && $session->getRequest() !== $request ) {
3934  $session = $session->sessionWithRequest( $request );
3935  }
3936  $delay = $session->delaySave();
3937 
3938  if ( !$session->getUser()->equals( $this ) ) {
3939  if ( !$session->canSetUser() ) {
3941  ->warning( __METHOD__ .
3942  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3943  );
3944  return;
3945  }
3946  $session->setUser( $this );
3947  }
3948 
3949  $session->setRememberUser( $rememberMe );
3950  if ( $secure !== null ) {
3951  $session->setForceHTTPS( $secure );
3952  }
3953 
3954  $session->persist();
3955 
3956  ScopedCallback::consume( $delay );
3957  }
3958 
3962  public function logout() {
3963  // Avoid PHP 7.1 warning of passing $this by reference
3964  $user = $this;
3965  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
3966  $this->doLogout();
3967  }
3968  }
3969 
3974  public function doLogout() {
3975  $session = $this->getRequest()->getSession();
3976  if ( !$session->canSetUser() ) {
3978  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3979  $error = 'immutable';
3980  } elseif ( !$session->getUser()->equals( $this ) ) {
3982  ->warning( __METHOD__ .
3983  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3984  );
3985  // But we still may as well make this user object anon
3986  $this->clearInstanceCache( 'defaults' );
3987  $error = 'wronguser';
3988  } else {
3989  $this->clearInstanceCache( 'defaults' );
3990  $delay = $session->delaySave();
3991  $session->unpersist(); // Clear cookies (T127436)
3992  $session->setLoggedOutTimestamp( time() );
3993  $session->setUser( new User );
3994  $session->set( 'wsUserID', 0 ); // Other code expects this
3995  $session->resetAllTokens();
3996  ScopedCallback::consume( $delay );
3997  $error = false;
3998  }
3999  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4000  'event' => 'logout',
4001  'successful' => $error === false,
4002  'status' => $error ?: 'success',
4003  ] );
4004  }
4005 
4010  public function saveSettings() {
4011  if ( wfReadOnly() ) {
4012  // @TODO: caller should deal with this instead!
4013  // This should really just be an exception.
4015  null,
4016  "Could not update user with ID '{$this->mId}'; DB is read-only."
4017  ) );
4018  return;
4019  }
4020 
4021  $this->load();
4022  if ( 0 == $this->mId ) {
4023  return; // anon
4024  }
4025 
4026  // Get a new user_touched that is higher than the old one.
4027  // This will be used for a CAS check as a last-resort safety
4028  // check against race conditions and replica DB lag.
4029  $newTouched = $this->newTouchedTimestamp();
4030 
4031  $dbw = wfGetDB( DB_MASTER );
4032  $dbw->update( 'user',
4033  [ /* SET */
4034  'user_name' => $this->mName,
4035  'user_real_name' => $this->mRealName,
4036  'user_email' => $this->mEmail,
4037  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4038  'user_touched' => $dbw->timestamp( $newTouched ),
4039  'user_token' => strval( $this->mToken ),
4040  'user_email_token' => $this->mEmailToken,
4041  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4042  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4043  'user_id' => $this->mId,
4044  ] ), __METHOD__
4045  );
4046 
4047  if ( !$dbw->affectedRows() ) {
4048  // Maybe the problem was a missed cache update; clear it to be safe
4049  $this->clearSharedCache( 'refresh' );
4050  // User was changed in the meantime or loaded with stale data
4051  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4052  throw new MWException(
4053  "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
4054  " the version of the user to be saved is older than the current version."
4055  );
4056  }
4057 
4058  $this->mTouched = $newTouched;
4059  $this->saveOptions();
4060 
4061  Hooks::run( 'UserSaveSettings', [ $this ] );
4062  $this->clearSharedCache();
4063  $this->getUserPage()->invalidateCache();
4064  }
4065 
4072  public function idForName( $flags = 0 ) {
4073  $s = trim( $this->getName() );
4074  if ( $s === '' ) {
4075  return 0;
4076  }
4077 
4078  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4079  ? wfGetDB( DB_MASTER )
4080  : wfGetDB( DB_REPLICA );
4081 
4082  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4083  ? [ 'LOCK IN SHARE MODE' ]
4084  : [];
4085 
4086  $id = $db->selectField( 'user',
4087  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4088 
4089  return (int)$id;
4090  }
4091 
4107  public static function createNew( $name, $params = [] ) {
4108  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4109  if ( isset( $params[$field] ) ) {
4110  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4111  unset( $params[$field] );
4112  }
4113  }
4114 
4115  $user = new User;
4116  $user->load();
4117  $user->setToken(); // init token
4118  if ( isset( $params['options'] ) ) {
4119  $user->mOptions = $params['options'] + (array)$user->mOptions;
4120  unset( $params['options'] );
4121  }
4122  $dbw = wfGetDB( DB_MASTER );
4123 
4124  $noPass = PasswordFactory::newInvalidPassword()->toString();
4125 
4126  $fields = [
4127  'user_name' => $name,
4128  'user_password' => $noPass,
4129  'user_newpassword' => $noPass,
4130  'user_email' => $user->mEmail,
4131  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4132  'user_real_name' => $user->mRealName,
4133  'user_token' => strval( $user->mToken ),
4134  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4135  'user_editcount' => 0,
4136  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4137  ];
4138  foreach ( $params as $name => $value ) {
4139  $fields["user_$name"] = $value;
4140  }
4141  $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
4142  if ( $dbw->affectedRows() ) {
4143  $newUser = self::newFromId( $dbw->insertId() );
4144  } else {
4145  $newUser = null;
4146  }
4147  return $newUser;
4148  }
4149 
4176  public function addToDatabase() {
4177  $this->load();
4178  if ( !$this->mToken ) {
4179  $this->setToken(); // init token
4180  }
4181 
4182  if ( !is_string( $this->mName ) ) {
4183  throw new RuntimeException( "User name field is not set." );
4184  }
4185 
4186  $this->mTouched = $this->newTouchedTimestamp();
4187 
4188  $noPass = PasswordFactory::newInvalidPassword()->toString();
4189 
4190  $dbw = wfGetDB( DB_MASTER );
4191  $dbw->insert( 'user',
4192  [
4193  'user_name' => $this->mName,
4194  'user_password' => $noPass,
4195  'user_newpassword' => $noPass,
4196  'user_email' => $this->mEmail,
4197  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4198  'user_real_name' => $this->mRealName,
4199  'user_token' => strval( $this->mToken ),
4200  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4201  'user_editcount' => 0,
4202  'user_touched' => $dbw->timestamp( $this->mTouched ),
4203  ], __METHOD__,
4204  [ 'IGNORE' ]
4205  );
4206  if ( !$dbw->affectedRows() ) {
4207  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4208  $this->mId = $dbw->selectField(
4209  'user',
4210  'user_id',
4211  [ 'user_name' => $this->mName ],
4212  __METHOD__,
4213  [ 'LOCK IN SHARE MODE' ]
4214  );
4215  $loaded = false;
4216  if ( $this->mId ) {
4217  if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4218  $loaded = true;
4219  }
4220  }
4221  if ( !$loaded ) {
4222  throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
4223  "to insert user '{$this->mName}' row, but it was not present in select!" );
4224  }
4225  return Status::newFatal( 'userexists' );
4226  }
4227  $this->mId = $dbw->insertId();
4228  self::$idCacheByName[$this->mName] = $this->mId;
4229 
4230  // Clear instance cache other than user table data, which is already accurate
4231  $this->clearInstanceCache();
4232 
4233  $this->saveOptions();
4234  return Status::newGood();
4235  }
4236 
4242  public function spreadAnyEditBlock() {
4243  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4244  return $this->spreadBlock();
4245  }
4246 
4247  return false;
4248  }
4249 
4255  protected function spreadBlock() {
4256  wfDebug( __METHOD__ . "()\n" );
4257  $this->load();
4258  if ( $this->mId == 0 ) {
4259  return false;
4260  }
4261 
4262  $userblock = Block::newFromTarget( $this->getName() );
4263  if ( !$userblock ) {
4264  return false;
4265  }
4266 
4267  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4268  }
4269 
4274  public function isBlockedFromCreateAccount() {
4275  $this->getBlockedStatus();
4276  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4277  return $this->mBlock;
4278  }
4279 
4280  # T15611: if the IP address the user is trying to create an account from is
4281  # blocked with createaccount disabled, prevent new account creation there even
4282  # when the user is logged in
4283  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4284  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4285  }
4286  return $this->mBlockedFromCreateAccount instanceof Block
4287  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4288  ? $this->mBlockedFromCreateAccount
4289  : false;
4290  }
4291 
4296  public function isBlockedFromEmailuser() {
4297  $this->getBlockedStatus();
4298  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4299  }
4300 
4305  public function isAllowedToCreateAccount() {
4306  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4307  }
4308 
4314  public function getUserPage() {
4315  return Title::makeTitle( NS_USER, $this->getName() );
4316  }
4317 
4323  public function getTalkPage() {
4324  $title = $this->getUserPage();
4325  return $title->getTalkPage();
4326  }
4327 
4333  public function isNewbie() {
4334  return !$this->isAllowed( 'autoconfirmed' );
4335  }
4336 
4343  public function checkPassword( $password ) {
4344  $manager = AuthManager::singleton();
4345  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4346  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4347  [
4348  'username' => $this->getName(),
4349  'password' => $password,
4350  ]
4351  );
4352  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4353  switch ( $res->status ) {
4354  case AuthenticationResponse::PASS:
4355  return true;
4356  case AuthenticationResponse::FAIL:
4357  // Hope it's not a PreAuthenticationProvider that failed...
4359  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4360  return false;
4361  default:
4362  throw new BadMethodCallException(
4363  'AuthManager returned a response unsupported by ' . __METHOD__
4364  );
4365  }
4366  }
4367 
4376  public function checkTemporaryPassword( $plaintext ) {
4377  // Can't check the temporary password individually.
4378  return $this->checkPassword( $plaintext );
4379  }
4380 
4392  public function getEditTokenObject( $salt = '', $request = null ) {
4393  if ( $this->isAnon() ) {
4394  return new LoggedOutEditToken();
4395  }
4396 
4397  if ( !$request ) {
4398  $request = $this->getRequest();
4399  }
4400  return $request->getSession()->getToken( $salt );
4401  }
4402 
4416  public function getEditToken( $salt = '', $request = null ) {
4417  return $this->getEditTokenObject( $salt, $request )->toString();
4418  }
4419 
4426  public static function getEditTokenTimestamp( $val ) {
4427  wfDeprecated( __METHOD__, '1.27' );
4429  }
4430 
4443  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4444  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4445  }
4446 
4457  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4458  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4459  return $this->matchEditToken( $val, $salt, $request, $maxage );
4460  }
4461 
4469  public function sendConfirmationMail( $type = 'created' ) {
4470  global $wgLang;
4471  $expiration = null; // gets passed-by-ref and defined in next line.
4472  $token = $this->confirmationToken( $expiration );
4473  $url = $this->confirmationTokenUrl( $token );
4474  $invalidateURL = $this->invalidationTokenUrl( $token );
4475  $this->saveSettings();
4476 
4477  if ( $type == 'created' || $type === false ) {
4478  $message = 'confirmemail_body';
4479  } elseif ( $type === true ) {
4480  $message = 'confirmemail_body_changed';
4481  } else {
4482  // Messages: confirmemail_body_changed, confirmemail_body_set
4483  $message = 'confirmemail_body_' . $type;
4484  }
4485 
4486  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4487  wfMessage( $message,
4488  $this->getRequest()->getIP(),
4489  $this->getName(),
4490  $url,
4491  $wgLang->userTimeAndDate( $expiration, $this ),
4492  $invalidateURL,
4493  $wgLang->userDate( $expiration, $this ),
4494  $wgLang->userTime( $expiration, $this ) )->text() );
4495  }
4496 
4508  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4510 
4511  if ( $from instanceof User ) {
4512  $sender = MailAddress::newFromUser( $from );
4513  } else {
4514  $sender = new MailAddress( $wgPasswordSender,
4515  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4516  }
4517  $to = MailAddress::newFromUser( $this );
4518 
4519  return UserMailer::send( $to, $sender, $subject, $body, [
4520  'replyTo' => $replyto,
4521  ] );
4522  }
4523 
4534  protected function confirmationToken( &$expiration ) {
4536  $now = time();
4537  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4538  $expiration = wfTimestamp( TS_MW, $expires );
4539  $this->load();
4540  $token = MWCryptRand::generateHex( 32 );
4541  $hash = md5( $token );
4542  $this->mEmailToken = $hash;
4543  $this->mEmailTokenExpires = $expiration;
4544  return $token;
4545  }
4546 
4552  protected function confirmationTokenUrl( $token ) {
4553  return $this->getTokenUrl( 'ConfirmEmail', $token );
4554  }
4555 
4561  protected function invalidationTokenUrl( $token ) {
4562  return $this->getTokenUrl( 'InvalidateEmail', $token );
4563  }
4564 
4579  protected function getTokenUrl( $page, $token ) {
4580  // Hack to bypass localization of 'Special:'
4581  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4582  return $title->getCanonicalURL();
4583  }
4584 
4592  public function confirmEmail() {
4593  // Check if it's already confirmed, so we don't touch the database
4594  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4595  if ( !$this->isEmailConfirmed() ) {
4597  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4598  }
4599  return true;
4600  }
4601 
4609  public function invalidateEmail() {
4610  $this->load();
4611  $this->mEmailToken = null;
4612  $this->mEmailTokenExpires = null;
4613  $this->setEmailAuthenticationTimestamp( null );
4614  $this->mEmail = '';
4615  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4616  return true;
4617  }
4618 
4623  public function setEmailAuthenticationTimestamp( $timestamp ) {
4624  $this->load();
4625  $this->mEmailAuthenticated = $timestamp;
4626  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4627  }
4628 
4634  public function canSendEmail() {
4636  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4637  return false;
4638  }
4639  $canSend = $this->isEmailConfirmed();
4640  // Avoid PHP 7.1 warning of passing $this by reference
4641  $user = $this;
4642  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4643  return $canSend;
4644  }
4645 
4651  public function canReceiveEmail() {
4652  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4653  }
4654 
4665  public function isEmailConfirmed() {
4667  $this->load();
4668  // Avoid PHP 7.1 warning of passing $this by reference
4669  $user = $this;
4670  $confirmed = true;
4671  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4672  if ( $this->isAnon() ) {
4673  return false;
4674  }
4675  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4676  return false;
4677  }
4679  return false;
4680  }
4681  return true;
4682  } else {
4683  return $confirmed;
4684  }
4685  }
4686 
4691  public function isEmailConfirmationPending() {
4693  return $wgEmailAuthentication &&
4694  !$this->isEmailConfirmed() &&
4695  $this->mEmailToken &&
4696  $this->mEmailTokenExpires > wfTimestamp();
4697  }
4698 
4706  public function getRegistration() {
4707  if ( $this->isAnon() ) {
4708  return false;
4709  }
4710  $this->load();
4711  return $this->mRegistration;
4712  }
4713 
4720  public function getFirstEditTimestamp() {
4721  if ( $this->getId() == 0 ) {
4722  return false; // anons
4723  }
4724  $dbr = wfGetDB( DB_REPLICA );
4725  $time = $dbr->selectField( 'revision', 'rev_timestamp',
4726  [ 'rev_user' => $this->getId() ],
4727  __METHOD__,
4728  [ 'ORDER BY' => 'rev_timestamp ASC' ]
4729  );
4730  if ( !$time ) {
4731  return false; // no edits
4732  }
4733  return wfTimestamp( TS_MW, $time );
4734  }
4735 
4742  public static function getGroupPermissions( $groups ) {
4744  $rights = [];
4745  // grant every granted permission first
4746  foreach ( $groups as $group ) {
4747  if ( isset( $wgGroupPermissions[$group] ) ) {
4748  $rights = array_merge( $rights,
4749  // array_filter removes empty items
4750  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4751  }
4752  }
4753  // now revoke the revoked permissions
4754  foreach ( $groups as $group ) {
4755  if ( isset( $wgRevokePermissions[$group] ) ) {
4756  $rights = array_diff( $rights,
4757  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4758  }
4759  }
4760  return array_unique( $rights );
4761  }
4762 
4769  public static function getGroupsWithPermission( $role ) {
4771  $allowedGroups = [];
4772  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4773  if ( self::groupHasPermission( $group, $role ) ) {
4774  $allowedGroups[] = $group;
4775  }
4776  }
4777  return $allowedGroups;
4778  }
4779 
4792  public static function groupHasPermission( $group, $role ) {
4794  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
4795  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
4796  }
4797 
4812  public static function isEveryoneAllowed( $right ) {
4814  static $cache = [];
4815 
4816  // Use the cached results, except in unit tests which rely on
4817  // being able change the permission mid-request
4818  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
4819  return $cache[$right];
4820  }
4821 
4822  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
4823  $cache[$right] = false;
4824  return false;
4825  }
4826 
4827  // If it's revoked anywhere, then everyone doesn't have it
4828  foreach ( $wgRevokePermissions as $rights ) {
4829  if ( isset( $rights[$right] ) && $rights[$right] ) {
4830  $cache[$right] = false;
4831  return false;
4832  }
4833  }
4834 
4835  // Remove any rights that aren't allowed to the global-session user,
4836  // unless there are no sessions for this endpoint.
4837  if ( !defined( 'MW_NO_SESSION' ) ) {
4838  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
4839  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
4840  $cache[$right] = false;
4841  return false;
4842  }
4843  }
4844 
4845  // Allow extensions to say false
4846  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
4847  $cache[$right] = false;
4848  return false;
4849  }
4850 
4851  $cache[$right] = true;
4852  return true;
4853  }
4854 
4862  public static function getGroupName( $group ) {
4863  wfDeprecated( __METHOD__, '1.29' );
4864  return UserGroupMembership::getGroupName( $group );
4865  }
4866 
4875  public static function getGroupMember( $group, $username = '#' ) {
4876  wfDeprecated( __METHOD__, '1.29' );
4878  }
4879 
4886  public static function getAllGroups() {
4888  return array_diff(
4889  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4890  self::getImplicitGroups()
4891  );
4892  }
4893 
4898  public static function getAllRights() {
4899  if ( self::$mAllRights === false ) {
4901  if ( count( $wgAvailableRights ) ) {
4902  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
4903  } else {
4904  self::$mAllRights = self::$mCoreRights;
4905  }
4906  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
4907  }
4908  return self::$mAllRights;
4909  }
4910 
4915  public static function getImplicitGroups() {
4917 
4918  $groups = $wgImplicitGroups;
4919  # Deprecated, use $wgImplicitGroups instead
4920  Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
4921 
4922  return $groups;
4923  }
4924 
4932  public static function getGroupPage( $group ) {
4933  wfDeprecated( __METHOD__, '1.29' );
4934  return UserGroupMembership::getGroupPage( $group );
4935  }
4936 
4947  public static function makeGroupLinkHTML( $group, $text = '' ) {
4948  wfDeprecated( __METHOD__, '1.29' );
4949 
4950  if ( $text == '' ) {
4951  $text = UserGroupMembership::getGroupName( $group );
4952  }
4954  if ( $title ) {
4955  return MediaWikiServices::getInstance()
4956  ->getLinkRenderer()->makeLink( $title, $text );
4957  } else {
4958  return htmlspecialchars( $text );
4959  }
4960  }
4961 
4972  public static function makeGroupLinkWiki( $group, $text = '' ) {
4973  wfDeprecated( __METHOD__, '1.29' );
4974 
4975  if ( $text == '' ) {
4976  $text = UserGroupMembership::getGroupName( $group );
4977  }
4979  if ( $title ) {
4980  $page = $title->getFullText();
4981  return "[[$page|$text]]";
4982  } else {
4983  return $text;
4984  }
4985  }
4986 
4996  public static function changeableByGroup( $group ) {
4998 
4999  $groups = [
5000  'add' => [],
5001  'remove' => [],
5002  'add-self' => [],
5003  'remove-self' => []
5004  ];
5005 
5006  if ( empty( $wgAddGroups[$group] ) ) {
5007  // Don't add anything to $groups
5008  } elseif ( $wgAddGroups[$group] === true ) {
5009  // You get everything
5010  $groups['add'] = self::getAllGroups();
5011  } elseif ( is_array( $wgAddGroups[$group] ) ) {
5012  $groups['add'] = $wgAddGroups[$group];
5013  }
5014 
5015  // Same thing for remove
5016  if ( empty( $wgRemoveGroups[$group] ) ) {
5017  // Do nothing
5018  } elseif ( $wgRemoveGroups[$group] === true ) {
5019  $groups['remove'] = self::getAllGroups();
5020  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5021  $groups['remove'] = $wgRemoveGroups[$group];
5022  }
5023 
5024  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5025  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5026  foreach ( $wgGroupsAddToSelf as $key => $value ) {
5027  if ( is_int( $key ) ) {
5028  $wgGroupsAddToSelf['user'][] = $value;
5029  }
5030  }
5031  }
5032 
5033  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5034  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5035  if ( is_int( $key ) ) {
5036  $wgGroupsRemoveFromSelf['user'][] = $value;
5037  }
5038  }
5039  }
5040 
5041  // Now figure out what groups the user can add to him/herself
5042  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5043  // Do nothing
5044  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5045  // No idea WHY this would be used, but it's there
5046  $groups['add-self'] = self::getAllGroups();
5047  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5048  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5049  }
5050 
5051  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5052  // Do nothing
5053  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5054  $groups['remove-self'] = self::getAllGroups();
5055  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5056  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5057  }
5058 
5059  return $groups;
5060  }
5061 
5069  public function changeableGroups() {
5070  if ( $this->isAllowed( 'userrights' ) ) {
5071  // This group gives the right to modify everything (reverse-
5072  // compatibility with old "userrights lets you change
5073  // everything")
5074  // Using array_merge to make the groups reindexed
5075  $all = array_merge( self::getAllGroups() );
5076  return [
5077  'add' => $all,
5078  'remove' => $all,
5079  'add-self' => [],
5080  'remove-self' => []
5081  ];
5082  }
5083 
5084  // Okay, it's not so simple, we will have to go through the arrays
5085  $groups = [
5086  'add' => [],
5087  'remove' => [],
5088  'add-self' => [],
5089  'remove-self' => []
5090  ];
5091  $addergroups = $this->getEffectiveGroups();
5092 
5093  foreach ( $addergroups as $addergroup ) {
5094  $groups = array_merge_recursive(
5095  $groups, $this->changeableByGroup( $addergroup )
5096  );
5097  $groups['add'] = array_unique( $groups['add'] );
5098  $groups['remove'] = array_unique( $groups['remove'] );
5099  $groups['add-self'] = array_unique( $groups['add-self'] );
5100  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5101  }
5102  return $groups;
5103  }
5104 
5111  public function incEditCount() {
5112  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
5113  function () {
5114  $this->incEditCountImmediate();
5115  },
5116  __METHOD__
5117  );
5118  }
5119 
5125  public function incEditCountImmediate() {
5126  if ( $this->isAnon() ) {
5127  return;
5128  }
5129 
5130  $dbw = wfGetDB( DB_MASTER );
5131  // No rows will be "affected" if user_editcount is NULL
5132  $dbw->update(
5133  'user',
5134  [ 'user_editcount=user_editcount+1' ],
5135  [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
5136  __METHOD__
5137  );
5138  // Lazy initialization check...
5139  if ( $dbw->affectedRows() == 0 ) {
5140  // Now here's a goddamn hack...
5141  $dbr = wfGetDB( DB_REPLICA );
5142  if ( $dbr !== $dbw ) {
5143  // If we actually have a replica DB server, the count is
5144  // at least one behind because the current transaction
5145  // has not been committed and replicated.
5146  $this->mEditCount = $this->initEditCount( 1 );
5147  } else {
5148  // But if DB_REPLICA is selecting the master, then the
5149  // count we just read includes the revision that was
5150  // just added in the working transaction.
5151  $this->mEditCount = $this->initEditCount();
5152  }
5153  } else {
5154  if ( $this->mEditCount === null ) {
5155  $this->getEditCount();
5156  $dbr = wfGetDB( DB_REPLICA );
5157  $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
5158  } else {
5159  $this->mEditCount++;
5160  }
5161  }
5162  // Edit count in user cache too
5163  $this->invalidateCache();
5164  }
5165 
5172  protected function initEditCount( $add = 0 ) {
5173  // Pull from a replica DB to be less cruel to servers
5174  // Accuracy isn't the point anyway here
5175  $dbr = wfGetDB( DB_REPLICA );
5176  $count = (int)$dbr->selectField(
5177  'revision',
5178  'COUNT(rev_user)',
5179  [ 'rev_user' => $this->getId() ],
5180  __METHOD__
5181  );
5182  $count = $count + $add;
5183 
5184  $dbw = wfGetDB( DB_MASTER );
5185  $dbw->update(
5186  'user',
5187  [ 'user_editcount' => $count ],
5188  [ 'user_id' => $this->getId() ],
5189  __METHOD__
5190  );
5191 
5192  return $count;
5193  }
5194 
5202  public static function getRightDescription( $right ) {
5203  $key = "right-$right";
5204  $msg = wfMessage( $key );
5205  return $msg->isDisabled() ? $right : $msg->text();
5206  }
5207 
5215  public static function getGrantName( $grant ) {
5216  $key = "grant-$grant";
5217  $msg = wfMessage( $key );
5218  return $msg->isDisabled() ? $grant : $msg->text();
5219  }
5220 
5241  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5242  return true; // disabled
5243  }
5244 
5253  public function addNewUserLogEntryAutoCreate() {
5254  $this->addNewUserLogEntry( 'autocreate' );
5255 
5256  return true;
5257  }
5258 
5264  protected function loadOptions( $data = null ) {
5266 
5267  $this->load();
5268 
5269  if ( $this->mOptionsLoaded ) {
5270  return;
5271  }
5272 
5273  $this->mOptions = self::getDefaultOptions();
5274 
5275  if ( !$this->getId() ) {
5276  // For unlogged-in users, load language/variant options from request.
5277  // There's no need to do it for logged-in users: they can set preferences,
5278  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5279  // so don't override user's choice (especially when the user chooses site default).
5280  $variant = $wgContLang->getDefaultVariant();
5281  $this->mOptions['variant'] = $variant;
5282  $this->mOptions['language'] = $variant;
5283  $this->mOptionsLoaded = true;
5284  return;
5285  }
5286 
5287  // Maybe load from the object
5288  if ( !is_null( $this->mOptionOverrides ) ) {
5289  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5290  foreach ( $this->mOptionOverrides as $key => $value ) {
5291  $this->mOptions[$key] = $value;
5292  }
5293  } else {
5294  if ( !is_array( $data ) ) {
5295  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5296  // Load from database
5297  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5298  ? wfGetDB( DB_MASTER )
5299  : wfGetDB( DB_REPLICA );
5300 
5301  $res = $dbr->select(
5302  'user_properties',
5303  [ 'up_property', 'up_value' ],
5304  [ 'up_user' => $this->getId() ],
5305  __METHOD__
5306  );
5307 
5308  $this->mOptionOverrides = [];
5309  $data = [];
5310  foreach ( $res as $row ) {
5311  // Convert '0' to 0. PHP's boolean conversion considers them both
5312  // false, but e.g. JavaScript considers the former as true.
5313  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5314  // and convert all values here.
5315  if ( $row->up_value === '0' ) {
5316  $row->up_value = 0;
5317  }
5318  $data[$row->up_property] = $row->up_value;
5319  }
5320  }
5321 
5322  // Convert the email blacklist from a new line delimited string
5323  // to an array of ids.
5324  if ( isset( $data['email-blacklist'] ) && $data['email-blacklist'] ) {
5325  $data['email-blacklist'] = array_map( 'intval', explode( "\n", $data['email-blacklist'] ) );
5326  }
5327 
5328  foreach ( $data as $property => $value ) {
5329  $this->mOptionOverrides[$property] = $value;
5330  $this->mOptions[$property] = $value;
5331  }
5332  }
5333 
5334  $this->mOptionsLoaded = true;
5335 
5336  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5337  }
5338 
5344  protected function saveOptions() {
5345  $this->loadOptions();
5346 
5347  // Not using getOptions(), to keep hidden preferences in database
5348  $saveOptions = $this->mOptions;
5349 
5350  // Convert usernames to ids.
5351  if ( isset( $this->mOptions['email-blacklist'] ) ) {
5352  if ( $this->mOptions['email-blacklist'] ) {
5353  $value = $this->mOptions['email-blacklist'];
5354  // Email Blacklist may be an array of ids or a string of new line
5355  // delimnated user names.
5356  if ( is_array( $value ) ) {
5357  $ids = array_filter( $value, 'is_numeric' );
5358  } else {
5359  $lookup = CentralIdLookup::factory();
5360  $ids = $lookup->centralIdsFromNames( explode( "\n", $value ), $this );
5361  }
5362  $this->mOptions['email-blacklist'] = $ids;
5363  $saveOptions['email-blacklist'] = implode( "\n", $this->mOptions['email-blacklist'] );
5364  } else {
5365  // If the blacklist is empty, set it to null rather than an empty string.
5366  $this->mOptions['email-blacklist'] = null;
5367  }
5368  }
5369 
5370  // Allow hooks to abort, for instance to save to a global profile.
5371  // Reset options to default state before saving.
5372  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5373  return;
5374  }
5375 
5376  $userId = $this->getId();
5377 
5378  $insert_rows = []; // all the new preference rows
5379  foreach ( $saveOptions as $key => $value ) {
5380  // Don't bother storing default values
5381  $defaultOption = self::getDefaultOption( $key );
5382  if ( ( $defaultOption === null && $value !== false && $value !== null )
5383  || $value != $defaultOption
5384  ) {
5385  $insert_rows[] = [
5386  'up_user' => $userId,
5387  'up_property' => $key,
5388  'up_value' => $value,
5389  ];
5390  }
5391  }
5392 
5393  $dbw = wfGetDB( DB_MASTER );
5394 
5395  $res = $dbw->select( 'user_properties',
5396  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5397 
5398  // Find prior rows that need to be removed or updated. These rows will
5399  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5400  $keysDelete = [];
5401  foreach ( $res as $row ) {
5402  if ( !isset( $saveOptions[$row->up_property] )
5403  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5404  ) {
5405  $keysDelete[] = $row->up_property;
5406  }
5407  }
5408 
5409  if ( count( $keysDelete ) ) {
5410  // Do the DELETE by PRIMARY KEY for prior rows.
5411  // In the past a very large portion of calls to this function are for setting
5412  // 'rememberpassword' for new accounts (a preference that has since been removed).
5413  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5414  // caused gap locks on [max user ID,+infinity) which caused high contention since
5415  // updates would pile up on each other as they are for higher (newer) user IDs.
5416  // It might not be necessary these days, but it shouldn't hurt either.
5417  $dbw->delete( 'user_properties',
5418  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5419  }
5420  // Insert the new preference rows
5421  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5422  }
5423 
5430  public static function getPasswordFactory() {
5431  wfDeprecated( __METHOD__, '1.27' );
5432  $ret = new PasswordFactory();
5433  $ret->init( RequestContext::getMain()->getConfig() );
5434  return $ret;
5435  }
5436 
5461  public static function passwordChangeInputAttribs() {
5463 
5464  if ( $wgMinimalPasswordLength == 0 ) {
5465  return [];
5466  }
5467 
5468  # Note that the pattern requirement will always be satisfied if the
5469  # input is empty, so we need required in all cases.
5470 
5471  # @todo FIXME: T25769: This needs to not claim the password is required
5472  # if e-mail confirmation is being used. Since HTML5 input validation
5473  # is b0rked anyway in some browsers, just return nothing. When it's
5474  # re-enabled, fix this code to not output required for e-mail
5475  # registration.
5476  # $ret = array( 'required' );
5477  $ret = [];
5478 
5479  # We can't actually do this right now, because Opera 9.6 will print out
5480  # the entered password visibly in its error message! When other
5481  # browsers add support for this attribute, or Opera fixes its support,
5482  # we can add support with a version check to avoid doing this on Opera
5483  # versions where it will be a problem. Reported to Opera as
5484  # DSK-262266, but they don't have a public bug tracker for us to follow.
5485  /*
5486  if ( $wgMinimalPasswordLength > 1 ) {
5487  $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}';
5488  $ret['title'] = wfMessage( 'passwordtooshort' )
5489  ->numParams( $wgMinimalPasswordLength )->text();
5490  }
5491  */
5492 
5493  return $ret;
5494  }
5495 
5501  public static function selectFields() {
5502  return [
5503  'user_id',
5504  'user_name',
5505  'user_real_name',
5506  'user_email',
5507  'user_touched',
5508  'user_token',
5509  'user_email_authenticated',
5510  'user_email_token',
5511  'user_email_token_expires',
5512  'user_registration',
5513  'user_editcount',
5514  ];
5515  }
5516 
5524  static function newFatalPermissionDeniedStatus( $permission ) {
5525  global $wgLang;
5526 
5527  $groups = [];
5528  foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5529  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5530  }
5531 
5532  if ( $groups ) {
5533  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5534  } else {
5535  return Status::newFatal( 'badaccess-group0' );
5536  }
5537  }
5538 
5548  public function getInstanceForUpdate() {
5549  if ( !$this->getId() ) {
5550  return null; // anon
5551  }
5552 
5553  $user = self::newFromId( $this->getId() );
5554  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5555  return null;
5556  }
5557 
5558  return $user;
5559  }
5560 
5568  public function equals( User $user ) {
5569  return $this->getName() === $user->getName();
5570  }
5571 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1619
User\saveOptions
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5344
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:4929
Block\prevents
prevents( $action, $x=null)
Get/set whether the Block prevents a given action.
Definition: Block.php:1012
User\passwordChangeInputAttribs
static passwordChangeInputAttribs()
Provide an array of HTML5 attributes to put on an input element intended for the user to enter a new ...
Definition: User.php:5461
Preferences\getPreferences
static getPreferences( $user, IContextSource $context)
Definition: Preferences.php:80
User\updateNewtalk
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2399
User\setCookie
setCookie( $name, $value, $exp=0, $secure=null, $params=[], $request=null)
Set a cookie on the user's client.
Definition: User.php:3864
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:425
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:362
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2294
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:45
User\$mOptionsLoaded
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:245
$wgProxyWhitelist
$wgProxyWhitelist
Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other methods mi...
Definition: DefaultSettings.php:5625
User\inDnsBlacklist
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1809
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:244
$wgUser
$wgUser
Definition: Setup.php:809
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:573
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:268
User\confirmationTokenUrl
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4552
file
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:2497
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:3364
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:219
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:357
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1005
User\getId
getId()
Get the user's ID.
Definition: User.php:2225
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:116
User\makeGroupLinkWiki
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name.
Definition: User.php:4972
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3600
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2196
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:3511
$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:2581
User\$mBlock
Block $mBlock
Definition: User.php:297
Block\clearCookie
static clearCookie(WebResponse $response)
Unset the 'BlockID' cookie.
Definition: Block.php:1481
User\$mBlockreason
string $mBlockreason
Definition: User.php:277
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4579
User\$mImplicitGroups
array $mImplicitGroups
Definition: User.php:281
User\isLocallyBlockedProxy
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1855
$wgRevokePermissions
$wgRevokePermissions
Permission keys revoked from users in each group.
Definition: DefaultSettings.php:5268
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3019
$wgBlockAllowsUTEdit
$wgBlockAllowsUTEdit
Set this to true to allow blocked users to edit their own user talk page.
Definition: DefaultSettings.php:5008
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1414
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5524
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:4392
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:6926
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:3519
Block\newFromID
static newFromID( $id)
Load a blocked user from their block id.
Definition: Block.php:184
User\newTouchedTimestamp
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2476
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8740
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3402
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:4255
AutoCommitUpdate
Deferrable Update for closure/callback updates that should use auto-commit mode.
Definition: AutoCommitUpdate.php:9
User\incEditCount
incEditCount()
Deferred version of incEditCountImmediate()
Definition: User.php:5111
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:616
captcha-old.count
count
Definition: captcha-old.py:249
wfGetLB
wfGetLB( $wiki=false)
Get a load balancer object.
Definition: GlobalFunctions.php:2869
UserMailer\send
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Definition: UserMailer.php:114
User\getOptionKinds
getOptionKinds(IContextSource $context, $options=null)
Return an associative array mapping preferences keys to the kind of a preference they're used for.
Definition: User.php:3076
Block\chooseBlock
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1232
User\getBlock
getBlock( $bFromSlave=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2078
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4691
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:2129
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2954
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:2911
UserGroupMembership\getGroupName
static getGroupName( $group)
Gets the localized friendly name for a group, if it exists.
Definition: UserGroupMembership.php:405
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:215
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:320
$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 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! 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:1963
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2040
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2698
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:4242
$wgSoftBlockRanges
string[] $wgSoftBlockRanges
IP ranges that should be considered soft-blocked (anon-only, account creation allowed).
Definition: DefaultSettings.php:5634
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2357
User\loadDefaults
loadDefaults( $name=false)
Set cached properties to default.
Definition: User.php:1162
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:300
$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. '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). '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:1245
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1699
User\$mOptions
array $mOptions
Definition: User.php:291
User\$mNewtalk
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:267
User\loadOptions
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5264
User\makeGroupLinkHTML
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition: User.php:4947
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
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:4862
$req
this hook is for auditing only $req
Definition: hooks.txt:988
User\setNewpassword
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:2756
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:76
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:2801
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1604
User\$mHideName
bool $mHideName
Definition: User.php:289
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3244
$params
$params
Definition: styleTest.css.php:40
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:1112
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1324
User\$mLocked
bool $mLocked
Definition: User.php:287
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:550
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1318
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4623
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:213
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:4314
User\$mOptionOverrides
array $mOptionOverrides
Definition: User.php:238
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3300
$s
$s
Definition: mergeMessageFileList.php:188
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:1749
PasswordFactory\generateRandomPasswordString
static generateRandomPasswordString( $minLength=10)
Generate a random string suitable for a password.
Definition: PasswordFactory.php:198
MWCryptRand\generateHex
static generateHex( $chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:76
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3588
$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
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
User\getDatePreference
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3205
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:345
User\getEditTokenTimestamp
static getEditTokenTimestamp( $val)
Get the embedded timestamp from a token.
Definition: User.php:4426
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:2784
User\idForName
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:4072
$success
$success
Definition: NoLocalSettings.php:44
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:851
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:4469
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4792
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2774
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:1926
User\$mFrom
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:262
User\initEditCount
initEditCount( $add=0)
Initialize user_editcount from data out of the revision table.
Definition: User.php:5172
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:3579
$base
$base
Definition: generateLocalAutoload.php:10
User\invalidateEmail
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4609
$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:430
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:637
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6802
User\loadGroups
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1424
User\$mHash
string $mHash
Definition: User.php:273
MediaWiki\Session\Token\getTimestamp
static getTimestamp( $token)
Decode the timestamp from a token string.
Definition: Token.php:61
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:6834
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:1140
User\isIPRange
isIPRange()
Is the user an IP range?
Definition: User.php:836
User\deleteNewtalk
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2424
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:75
User\equals
equals(User $user)
Checks if two user objects point to the same user.
Definition: User.php:5568
User\$mCacheVars
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:96
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:3259
$wgClockSkewFudge
$wgClockSkewFudge
Clock skew or the one-second resolution of time() can occasionally cause cache problems when the user...
Definition: DefaultSettings.php:2606
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4107
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3613
Preferences\getSaveBlacklist
static getSaveBlacklist()
Definition: Preferences.php:70
User\getAutomaticGroups
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3349
User\INVALID_TOKEN
const INVALID_TOKEN
@const string An invalid value for user_token
Definition: User.php:60
User\setPassword
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2609
User\$mGroupMemberships
array $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:236
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1579
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5548
$wgMaxNameChars
$wgMaxNameChars
Maximum number of bytes in username.
Definition: DefaultSettings.php:4836
User\$mGroups
array $mGroups
No longer used since 1.29; use User::getGroups() instead.
Definition: User.php:234
IDBAccessObject\READ_LOCKING
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:62
User\isBlockedFrom
isBlockedFrom( $title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2090
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Name of the external diff engine to use.
Definition: DefaultSettings.php:8739
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:678
UserGroupMembership\getMembershipsForUser
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
Definition: UserGroupMembership.php:281
NS_MAIN
const NS_MAIN
Definition: Defines.php:65
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3437
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:4443
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2764
User\$mRights
array $mRights
Definition: User.php:275
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, IDatabase $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:111
article
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an article
Definition: design.txt:25
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:4323
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4176
User\makeUpdateConditions
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition: User.php:1498
IP\isValidRange
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:138
User\invalidateCache
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2521
User\setInternalPassword
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2621
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:4561
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:932
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2189
User\$mDatePreference
string $mDatePreference
Definition: User.php:269
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:4967
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1176
$property
$property
Definition: styleTest.css.php:44
User\checkPasswordValidity
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1052
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:95
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4592
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1210
User\getPasswordFactory
static getPasswordFactory()
Lazily instantiate and return a factory object for making passwords.
Definition: User.php:5430
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:346
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2120
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2444
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2856
User\$mBlockedFromCreateAccount
Block $mBlockedFromCreateAccount
Definition: User.php:303
in
null for the wiki Added in
Definition: hooks.txt:1581
User\$mGlobalBlock
Block $mGlobalBlock
Definition: User.php:285
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4305
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:3962
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4534
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:466
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:2056
User\getImplicitGroups
static getImplicitGroups()
Get a list of implicit groups.
Definition: User.php:4915
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2206
User\randomPassword
static randomPassword()
Return a random password.
Definition: User.php:1149
User\$mBlockedby
string $mBlockedby
Definition: User.php:271
UserGroupMembership\newFromRow
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
Definition: UserGroupMembership.php:92
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:4296
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:825
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2553
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3326
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1901
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3473
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4651
$wgImplicitGroups
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
Definition: DefaultSettings.php:5273
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4333
User\isAllowedAll
isAllowedAll()
Definition: User.php:3551
$wgLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition: design.txt:56
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2538
$wgExtendedLoginCookieExpiration
$wgExtendedLoginCookieExpiration
Default login cookie lifetime, in seconds.
Definition: DefaultSettings.php:5991
User\clearInstanceCache
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1554
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1598
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:83
$wgEnableDnsBlacklist
$wgEnableDnsBlacklist
Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies.
Definition: DefaultSettings.php:5594
User\TOKEN_LENGTH
const TOKEN_LENGTH
@const int Number of characters in user_token field.
Definition: User.php:55
$wgDefaultSkin
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
Definition: DefaultSettings.php:3259
$wgUpdateRowsPerQuery
$wgUpdateRowsPerQuery
Number of rows to update per query.
Definition: DefaultSettings.php:8332
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1778
$wgReservedUsernames
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
Definition: DefaultSettings.php:4842
User\clearCookie
clearCookie( $name, $secure=null, $params=[])
Clear a cookie on the user's client.
Definition: User.php:3885
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:529
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2069
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3644
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:217
User\setName
setName( $str)
Set the user name.
Definition: User.php:2277
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:210
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:3160
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:45
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:6841
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:1047
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5522
User\$mLoadedItems
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:250
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3678
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:4010
User\addNewUserLogEntry
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5241
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:1151
User\$mCoreRights
static $mCoreRights
Array of Strings Core rights.
Definition: User.php:121
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:884
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4720
$wgAutopromoteOnceLogInRC
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
Definition: DefaultSettings.php:5493
$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:2581
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:78
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:2868
User\EDIT_TOKEN_SUFFIX
const EDIT_TOKEN_SUFFIX
Global constant made accessible as class constants so that autoloader magic can be used.
Definition: User.php:67
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2332
User\setExtendedLoginCookie
setExtendedLoginCookie( $name, $value, $secure)
Set an extended login cookie on the user's client.
Definition: User.php:3905
$wgCookieExpiration
$wgCookieExpiration
Default cookie lifetime, in seconds.
Definition: DefaultSettings.php:5983
any
they could even be mouse clicks or menu items whatever suits your program You should also get your if any
Definition: COPYING.txt:326
User\getBlockedStatus
getBlockedStatus( $bFromSlave=true)
Get blocking information.
Definition: User.php:1634
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:557
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:2807
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:68
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3378
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:745
Block\getIdFromCookieValue
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the 'BlockID' cookie.
Definition: Block.php:1517
$value
$value
Definition: styleTest.css.php:45
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\incEditCountImmediate
incEditCountImmediate()
Increment the user's edit-count field.
Definition: User.php:5125
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1221
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:5678
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2142
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2883
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:1788
User\getTouched
getTouched()
Get the user touched timestamp.
Definition: User.php:2565
User\$mId
int $mId
Cache variables.
Definition: User.php:206
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2156
User\__toString
__toString()
Definition: User.php:327
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:49
Title\GAID_FOR_UPDATE
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:54
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1516
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:80
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:2856
User\$idCacheByName
static $idCacheByName
Definition: User.php:308
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:3926
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4742
User\getGroupPage
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition: User.php:4932
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5069
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3746
User\VERSION
const VERSION
@const int Serialized record version.
Definition: User.php:72
$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:1965
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:4457
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5517
User\checkPassword
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4343
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4886
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3661
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4898
$wgInvalidUsernameCharacters
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
Definition: DefaultSettings.php:4936
RequestContext\getMain
static getMain()
Static methods.
Definition: RequestContext.php:470
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2111
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:8737
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2587
Wikimedia\Rdbms\Database\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:3081
User\getGroupMember
static getGroupMember( $group, $username='#')
Get the localized descriptive name for a member of a group, if it exists.
Definition: User.php:4875
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:82
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:55
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:38
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4996
Block\TYPE_USER
const TYPE_USER
Definition: Block.php:83
User\getGroupName
static getGroupName( $group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:4862
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:368
Autopromote\getAutopromoteOnceGroups
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2241
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3817
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1638
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4706
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:214
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:4812
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
User\isAllowedAny
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3536
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5202
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2671
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
$cache
$cache
Definition: mcc.php:33
$wgAvailableRights
$wgAvailableRights
A list of available rights, in addition to the ones defined by the core.
Definition: DefaultSettings.php:5528
$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:2581
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:5751
$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:1965
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:370
$wgApplyIpBlocksToXff
$wgApplyIpBlocksToXff
Whether to look at the X-Forwarded-For header's list of (potentially spoofed) IPs and apply IP blocks...
Definition: DefaultSettings.php:5641
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3503
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:4376
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:35
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2286
$code
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:781
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2736
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:765
$wgGroupPermissions
$wgGroupPermissions
Permission keys given to users in each group.
Definition: DefaultSettings.php:5129
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:805
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:225
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:935
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:1092
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4665
$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:1750
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5215
Block
Definition: Block.php:27
UserCache\singleton
static singleton()
Definition: UserCache.php:34
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:309
$wgDnsBlacklistUrls
$wgDnsBlacklistUrls
List of DNS blacklists to use, if $wgEnableDnsBlacklist is true.
Definition: DefaultSettings.php:5619
$keys
$keys
Definition: testCompression.php:65
NS_USER
const NS_USER
Definition: Defines.php:67
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:487
$wgLearnerMemberSince
$wgLearnerMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8738
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1448
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4508
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:2991
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:1965
User\$mEmailToken
string $mEmailToken
Definition: User.php:223
ManualLogEntry
Class for creating log entries manually, to inject them into the database.
Definition: LogEntry.php:400
$wgGroupsRemoveFromSelf
$wgGroupsRemoveFromSelf
Definition: DefaultSettings.php:5301
User\checkNewtalk
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2384
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1268
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:1110
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 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\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5501
PasswordFactory
Factory class for creating and checking Password objects.
Definition: PasswordFactory.php:28
User\$mName
string $mName
Definition: User.php:208
User\$mRegistration
string $mRegistration
Definition: User.php:227
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for local wiki users.
Definition: DefaultSettings.php:4516
$t
$t
Definition: testCompression.php:67
User\addNewUserLogEntryAutoCreate
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5253
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:592
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:88
User\$mRequest
WebRequest $mRequest
Definition: User.php:294
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:662
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3630
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:312
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:2942
$wgMinimalPasswordLength
$wgMinimalPasswordLength
Specifies the minimal length of a user password.
Definition: DefaultSettings.php:4735
$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:5296
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:900
User\isBlocked
isBlocked( $bFromSlave=true)
Check if user is blocked.
Definition: User.php:2068
User\$mFormerGroups
array $mFormerGroups
Definition: User.php:283
CentralIdLookup\factory
static factory( $providerId=null)
Fetch a CentralIdLookup.
Definition: CentralIdLookup.php:45
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1577
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4274
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:4416
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3225
User\$mAllRights
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:201
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1200
User\purge
static purge( $wikiId, $userId)
Definition: User.php:455
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:2970
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:306
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:781
User\getPasswordValidity
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition: User.php:1016
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:755
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2250
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:4955
UserGroupMembership\getGroupMemberName
static getGroupMemberName( $group, $username)
Gets the localized name for a member of a group, if it exists.
Definition: UserGroupMembership.php:418
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3974
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:3313
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4634
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:975
$wgNamespacesToBeSearchedDefault
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
Definition: DefaultSettings.php:6488
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:475
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2801
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
User\$mEffectiveGroups
array $mEffectiveGroups
Definition: User.php:279
User\setPasswordInternal
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2633
$wgDisableAnonTalk
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
Definition: DefaultSettings.php:6932
UserGroupMembership
Represents a "user group membership" – a specific instance of a user belonging to a group.
Definition: UserGroupMembership.php:36
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:229
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:221
array
the array() calling protocol came about after MediaWiki 1.4rc1.
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4769
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3566
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:613
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3053
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56
$type
$type
Definition: testCompression.php:48