MediaWiki  1.27.2
User.php
Go to the documentation of this file.
1 <?php
29 
35 define( 'EDIT_TOKEN_SUFFIX', Token::SUFFIX );
36 
47 class User implements IDBAccessObject {
51  const TOKEN_LENGTH = 32;
52 
56  const INVALID_TOKEN = '*** INVALID ***';
57 
64 
68  const VERSION = 10;
69 
75 
79  const CHECK_USER_RIGHTS = true;
80 
84  const IGNORE_USER_RIGHTS = false;
85 
92  protected static $mCacheVars = [
93  // user table
94  'mId',
95  'mName',
96  'mRealName',
97  'mEmail',
98  'mTouched',
99  'mToken',
100  'mEmailAuthenticated',
101  'mEmailToken',
102  'mEmailTokenExpires',
103  'mRegistration',
104  'mEditCount',
105  // user_groups table
106  'mGroups',
107  // user_properties table
108  'mOptionOverrides',
109  ];
110 
117  protected static $mCoreRights = [
118  'apihighlimits',
119  'applychangetags',
120  'autoconfirmed',
121  'autocreateaccount',
122  'autopatrol',
123  'bigdelete',
124  'block',
125  'blockemail',
126  'bot',
127  'browsearchive',
128  'changetags',
129  'createaccount',
130  'createpage',
131  'createtalk',
132  'delete',
133  'deletedhistory',
134  'deletedtext',
135  'deletelogentry',
136  'deleterevision',
137  'edit',
138  'editcontentmodel',
139  'editinterface',
140  'editprotected',
141  'editmyoptions',
142  'editmyprivateinfo',
143  'editmyusercss',
144  'editmyuserjs',
145  'editmywatchlist',
146  'editsemiprotected',
147  'editusercssjs', # deprecated
148  'editusercss',
149  'edituserjs',
150  'hideuser',
151  'import',
152  'importupload',
153  'ipblock-exempt',
154  'managechangetags',
155  'markbotedits',
156  'mergehistory',
157  'minoredit',
158  'move',
159  'movefile',
160  'move-categorypages',
161  'move-rootuserpages',
162  'move-subpages',
163  'nominornewtalk',
164  'noratelimit',
165  'override-export-depth',
166  'pagelang',
167  'passwordreset',
168  'patrol',
169  'patrolmarks',
170  'protect',
171  'purge',
172  'read',
173  'reupload',
174  'reupload-own',
175  'reupload-shared',
176  'rollback',
177  'sendemail',
178  'siteadmin',
179  'suppressionlog',
180  'suppressredirect',
181  'suppressrevision',
182  'unblockself',
183  'undelete',
184  'unwatchedpages',
185  'upload',
186  'upload_by_url',
187  'userrights',
188  'userrights-interwiki',
189  'viewmyprivateinfo',
190  'viewmywatchlist',
191  'viewsuppressed',
192  'writeapi',
193  ];
194 
198  protected static $mAllRights = false;
199 
204  protected static $inProcessCache;
205 
207  // @{
209  public $mId;
211  public $mName;
213  public $mRealName;
214 
216  public $mEmail;
218  public $mTouched;
220  protected $mQuickTouched;
222  protected $mToken;
226  protected $mEmailToken;
230  protected $mRegistration;
232  protected $mEditCount;
234  public $mGroups;
236  protected $mOptionOverrides;
237  // @}
238 
242  // @{
244 
248  protected $mLoadedItems = [];
249  // @}
250 
260  public $mFrom;
261 
265  protected $mNewtalk;
267  protected $mDatePreference;
269  public $mBlockedby;
271  protected $mHash;
273  public $mRights;
275  protected $mBlockreason;
277  protected $mEffectiveGroups;
279  protected $mImplicitGroups;
281  protected $mFormerGroups;
283  protected $mGlobalBlock;
285  protected $mLocked;
287  public $mHideName;
289  public $mOptions;
290 
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  $this->loadDefaults();
428  return false;
429  }
430 
431  // Try cache (unless this needs data from the master DB).
432  // NOTE: if this thread called saveSettings(), the cache was cleared.
433  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
434  if ( $latest || !$this->loadFromCache() ) {
435  wfDebug( "User: cache miss for user {$this->mId}\n" );
436  // Load from DB (make sure this thread sees its own changes)
437  if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
438  $flags |= self::READ_LATEST;
439  }
440  if ( !$this->loadFromDatabase( $flags ) ) {
441  // Can't load from ID, user is anonymous
442  return false;
443  }
444  $this->saveToCache();
445  }
446 
447  $this->mLoadedItems = true;
448  $this->queryFlagsUsed = $flags;
449 
450  return true;
451  }
452 
458  public static function purge( $wikiId, $userId ) {
460  $processCache = self::getInProcessCache();
461  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
462  $cache->delete( $key );
463  $processCache->delete( $key );
464  }
465 
471  protected function getCacheKey( WANObjectCache $cache ) {
472  return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId );
473  }
474 
479  protected static function getInProcessCache() {
480  if ( !self::$inProcessCache ) {
481  self::$inProcessCache = new HashBagOStuff( [ 'maxKeys' => 10 ] );
482  }
483  return self::$inProcessCache;
484  }
485 
492  protected function loadFromCache() {
493  if ( $this->mId == 0 ) {
494  $this->loadDefaults();
495  return false;
496  }
497 
499  $processCache = self::getInProcessCache();
500  $key = $this->getCacheKey( $cache );
501  $data = $processCache->get( $key );
502  if ( !is_array( $data ) ) {
503  $data = $cache->get( $key );
504  if ( !is_array( $data ) || $data['mVersion'] < self::VERSION ) {
505  // Object is expired
506  return false;
507  }
508  $processCache->set( $key, $data );
509  }
510  wfDebug( "User: got user {$this->mId} from cache\n" );
511 
512  // Restore from cache
513  foreach ( self::$mCacheVars as $name ) {
514  $this->$name = $data[$name];
515  }
516 
517  return true;
518  }
519 
525  public function saveToCache() {
526  $this->load();
527  $this->loadGroups();
528  $this->loadOptions();
529 
530  if ( $this->isAnon() ) {
531  // Anonymous users are uncached
532  return;
533  }
534 
535  $data = [];
536  foreach ( self::$mCacheVars as $name ) {
537  $data[$name] = $this->$name;
538  }
539  $data['mVersion'] = self::VERSION;
541 
543  $processCache = self::getInProcessCache();
544  $key = $this->getCacheKey( $cache );
545  $cache->set( $key, $data, $cache::TTL_HOUR, $opts );
546  $processCache->set( $key, $data );
547  }
548 
550  // @{
551 
568  public static function newFromName( $name, $validate = 'valid' ) {
569  if ( $validate === true ) {
570  $validate = 'valid';
571  }
572  $name = self::getCanonicalName( $name, $validate );
573  if ( $name === false ) {
574  return false;
575  } else {
576  // Create unloaded user object
577  $u = new User;
578  $u->mName = $name;
579  $u->mFrom = 'name';
580  $u->setItemLoaded( 'name' );
581  return $u;
582  }
583  }
584 
591  public static function newFromId( $id ) {
592  $u = new User;
593  $u->mId = $id;
594  $u->mFrom = 'id';
595  $u->setItemLoaded( 'id' );
596  return $u;
597  }
598 
610  public static function newFromConfirmationCode( $code, $flags = 0 ) {
611  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
612  ? wfGetDB( DB_MASTER )
613  : wfGetDB( DB_SLAVE );
614 
615  $id = $db->selectField(
616  'user',
617  'user_id',
618  [
619  'user_email_token' => md5( $code ),
620  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
621  ]
622  );
623 
624  return $id ? User::newFromId( $id ) : null;
625  }
626 
634  public static function newFromSession( WebRequest $request = null ) {
635  $user = new User;
636  $user->mFrom = 'session';
637  $user->mRequest = $request;
638  return $user;
639  }
640 
655  public static function newFromRow( $row, $data = null ) {
656  $user = new User;
657  $user->loadFromRow( $row, $data );
658  return $user;
659  }
660 
695  public static function newSystemUser( $name, $options = [] ) {
696  $options += [
697  'validate' => 'valid',
698  'create' => true,
699  'steal' => false,
700  ];
701 
702  $name = self::getCanonicalName( $name, $options['validate'] );
703  if ( $name === false ) {
704  return null;
705  }
706 
707  $fields = self::selectFields();
708 
709  $dbw = wfGetDB( DB_MASTER );
710  $row = $dbw->selectRow(
711  'user',
712  $fields,
713  [ 'user_name' => $name ],
714  __METHOD__
715  );
716  if ( !$row ) {
717  // No user. Create it?
718  return $options['create'] ? self::createNew( $name ) : null;
719  }
720  $user = self::newFromRow( $row );
721 
722  // A user is considered to exist as a non-system user if it can
723  // authenticate, or has an email set, or has a non-invalid token.
724  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
725  AuthManager::singleton()->userCanAuthenticate( $name )
726  ) {
727  // User exists. Steal it?
728  if ( !$options['steal'] ) {
729  return null;
730  }
731 
732  AuthManager::singleton()->revokeAccessForUser( $name );
733 
734  $user->invalidateEmail();
735  $user->mToken = self::INVALID_TOKEN;
736  $user->saveSettings();
737  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
738  }
739 
740  return $user;
741  }
742 
743  // @}
744 
750  public static function whoIs( $id ) {
751  return UserCache::singleton()->getProp( $id, 'name' );
752  }
753 
760  public static function whoIsReal( $id ) {
761  return UserCache::singleton()->getProp( $id, 'real_name' );
762  }
763 
770  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
772  if ( is_null( $nt ) ) {
773  // Illegal name
774  return null;
775  }
776 
777  if ( !( $flags & self::READ_LATEST ) && isset( self::$idCacheByName[$name] ) ) {
778  return self::$idCacheByName[$name];
779  }
780 
781  $db = ( $flags & self::READ_LATEST )
782  ? wfGetDB( DB_MASTER )
783  : wfGetDB( DB_SLAVE );
784 
785  $s = $db->selectRow(
786  'user',
787  [ 'user_id' ],
788  [ 'user_name' => $nt->getText() ],
789  __METHOD__
790  );
791 
792  if ( $s === false ) {
793  $result = null;
794  } else {
795  $result = $s->user_id;
796  }
797 
798  self::$idCacheByName[$name] = $result;
799 
800  if ( count( self::$idCacheByName ) > 1000 ) {
801  self::$idCacheByName = [];
802  }
803 
804  return $result;
805  }
806 
810  public static function resetIdByNameCache() {
811  self::$idCacheByName = [];
812  }
813 
830  public static function isIP( $name ) {
831  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
832  || IP::isIPv6( $name );
833  }
834 
846  public static function isValidUserName( $name ) {
847  global $wgContLang, $wgMaxNameChars;
848 
849  if ( $name == ''
850  || User::isIP( $name )
851  || strpos( $name, '/' ) !== false
852  || strlen( $name ) > $wgMaxNameChars
853  || $name != $wgContLang->ucfirst( $name )
854  ) {
855  return false;
856  }
857 
858  // Ensure that the name can't be misresolved as a different title,
859  // such as with extra namespace keys at the start.
860  $parsed = Title::newFromText( $name );
861  if ( is_null( $parsed )
862  || $parsed->getNamespace()
863  || strcmp( $name, $parsed->getPrefixedText() ) ) {
864  return false;
865  }
866 
867  // Check an additional blacklist of troublemaker characters.
868  // Should these be merged into the title char list?
869  $unicodeBlacklist = '/[' .
870  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
871  '\x{00a0}' . # non-breaking space
872  '\x{2000}-\x{200f}' . # various whitespace
873  '\x{2028}-\x{202f}' . # breaks and control chars
874  '\x{3000}' . # ideographic space
875  '\x{e000}-\x{f8ff}' . # private use
876  ']/u';
877  if ( preg_match( $unicodeBlacklist, $name ) ) {
878  return false;
879  }
880 
881  return true;
882  }
883 
895  public static function isUsableName( $name ) {
896  global $wgReservedUsernames;
897  // Must be a valid username, obviously ;)
898  if ( !self::isValidUserName( $name ) ) {
899  return false;
900  }
901 
902  static $reservedUsernames = false;
903  if ( !$reservedUsernames ) {
904  $reservedUsernames = $wgReservedUsernames;
905  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
906  }
907 
908  // Certain names may be reserved for batch processes.
909  foreach ( $reservedUsernames as $reserved ) {
910  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
911  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
912  }
913  if ( $reserved == $name ) {
914  return false;
915  }
916  }
917  return true;
918  }
919 
932  public static function isCreatableName( $name ) {
933  global $wgInvalidUsernameCharacters;
934 
935  // Ensure that the username isn't longer than 235 bytes, so that
936  // (at least for the builtin skins) user javascript and css files
937  // will work. (bug 23080)
938  if ( strlen( $name ) > 235 ) {
939  wfDebugLog( 'username', __METHOD__ .
940  ": '$name' invalid due to length" );
941  return false;
942  }
943 
944  // Preg yells if you try to give it an empty string
945  if ( $wgInvalidUsernameCharacters !== '' ) {
946  if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
947  wfDebugLog( 'username', __METHOD__ .
948  ": '$name' invalid due to wgInvalidUsernameCharacters" );
949  return false;
950  }
951  }
952 
953  return self::isUsableName( $name );
954  }
955 
962  public function isValidPassword( $password ) {
963  // simple boolean wrapper for getPasswordValidity
964  return $this->getPasswordValidity( $password ) === true;
965  }
966 
973  public function getPasswordValidity( $password ) {
974  $result = $this->checkPasswordValidity( $password );
975  if ( $result->isGood() ) {
976  return true;
977  } else {
978  $messages = [];
979  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
980  $messages[] = $error['message'];
981  }
982  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
983  $messages[] = $warning['message'];
984  }
985  if ( count( $messages ) === 1 ) {
986  return $messages[0];
987  }
988  return $messages;
989  }
990  }
991 
1010  public function checkPasswordValidity( $password, $purpose = 'login' ) {
1011  global $wgPasswordPolicy;
1012 
1013  $upp = new UserPasswordPolicy(
1014  $wgPasswordPolicy['policies'],
1015  $wgPasswordPolicy['checks']
1016  );
1017 
1019  $result = false; // init $result to false for the internal checks
1020 
1021  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1022  $status->error( $result );
1023  return $status;
1024  }
1025 
1026  if ( $result === false ) {
1027  $status->merge( $upp->checkUserPassword( $this, $password, $purpose ) );
1028  return $status;
1029  } elseif ( $result === true ) {
1030  return $status;
1031  } else {
1032  $status->error( $result );
1033  return $status; // the isValidPassword hook set a string $result and returned true
1034  }
1035  }
1036 
1050  public static function getCanonicalName( $name, $validate = 'valid' ) {
1051  // Force usernames to capital
1053  $name = $wgContLang->ucfirst( $name );
1054 
1055  # Reject names containing '#'; these will be cleaned up
1056  # with title normalisation, but then it's too late to
1057  # check elsewhere
1058  if ( strpos( $name, '#' ) !== false ) {
1059  return false;
1060  }
1061 
1062  // Clean up name according to title rules,
1063  // but only when validation is requested (bug 12654)
1064  $t = ( $validate !== false ) ?
1066  // Check for invalid titles
1067  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1068  return false;
1069  }
1070 
1071  // Reject various classes of invalid names
1072  $name = AuthManager::callLegacyAuthPlugin(
1073  'getCanonicalName', [ $t->getText() ], $t->getText()
1074  );
1075 
1076  switch ( $validate ) {
1077  case false:
1078  break;
1079  case 'valid':
1080  if ( !User::isValidUserName( $name ) ) {
1081  $name = false;
1082  }
1083  break;
1084  case 'usable':
1085  if ( !User::isUsableName( $name ) ) {
1086  $name = false;
1087  }
1088  break;
1089  case 'creatable':
1090  if ( !User::isCreatableName( $name ) ) {
1091  $name = false;
1092  }
1093  break;
1094  default:
1095  throw new InvalidArgumentException(
1096  'Invalid parameter value for $validate in ' . __METHOD__ );
1097  }
1098  return $name;
1099  }
1100 
1109  public static function edits( $uid ) {
1110  wfDeprecated( __METHOD__, '1.21' );
1111  $user = self::newFromId( $uid );
1112  return $user->getEditCount();
1113  }
1114 
1121  public static function randomPassword() {
1122  global $wgMinimalPasswordLength;
1123  return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1124  }
1125 
1134  public function loadDefaults( $name = false ) {
1135  $this->mId = 0;
1136  $this->mName = $name;
1137  $this->mRealName = '';
1138  $this->mEmail = '';
1139  $this->mOptionOverrides = null;
1140  $this->mOptionsLoaded = false;
1141 
1142  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1143  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1144  if ( $loggedOut !== 0 ) {
1145  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1146  } else {
1147  $this->mTouched = '1'; # Allow any pages to be cached
1148  }
1149 
1150  $this->mToken = null; // Don't run cryptographic functions till we need a token
1151  $this->mEmailAuthenticated = null;
1152  $this->mEmailToken = '';
1153  $this->mEmailTokenExpires = null;
1154  $this->mRegistration = wfTimestamp( TS_MW );
1155  $this->mGroups = [];
1156 
1157  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1158  }
1159 
1172  public function isItemLoaded( $item, $all = 'all' ) {
1173  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1174  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1175  }
1176 
1182  protected function setItemLoaded( $item ) {
1183  if ( is_array( $this->mLoadedItems ) ) {
1184  $this->mLoadedItems[$item] = true;
1185  }
1186  }
1187 
1193  private function loadFromSession() {
1194  // Deprecated hook
1195  $result = null;
1196  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1197  if ( $result !== null ) {
1198  return $result;
1199  }
1200 
1201  // MediaWiki\Session\Session already did the necessary authentication of the user
1202  // returned here, so just use it if applicable.
1203  $session = $this->getRequest()->getSession();
1204  $user = $session->getUser();
1205  if ( $user->isLoggedIn() ) {
1206  $this->loadFromUserObject( $user );
1207  // Other code expects these to be set in the session, so set them.
1208  $session->set( 'wsUserID', $this->getId() );
1209  $session->set( 'wsUserName', $this->getName() );
1210  $session->set( 'wsToken', $this->getToken() );
1211  return true;
1212  }
1213 
1214  return false;
1215  }
1216 
1224  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1225  // Paranoia
1226  $this->mId = intval( $this->mId );
1227 
1228  // Anonymous user
1229  if ( !$this->mId ) {
1230  $this->loadDefaults();
1231  return false;
1232  }
1233 
1235  $db = wfGetDB( $index );
1236 
1237  $s = $db->selectRow(
1238  'user',
1239  self::selectFields(),
1240  [ 'user_id' => $this->mId ],
1241  __METHOD__,
1242  $options
1243  );
1244 
1245  $this->queryFlagsUsed = $flags;
1246  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1247 
1248  if ( $s !== false ) {
1249  // Initialise user table data
1250  $this->loadFromRow( $s );
1251  $this->mGroups = null; // deferred
1252  $this->getEditCount(); // revalidation for nulls
1253  return true;
1254  } else {
1255  // Invalid user_id
1256  $this->mId = 0;
1257  $this->loadDefaults();
1258  return false;
1259  }
1260  }
1261 
1271  protected function loadFromRow( $row, $data = null ) {
1272  $all = true;
1273 
1274  $this->mGroups = null; // deferred
1275 
1276  if ( isset( $row->user_name ) ) {
1277  $this->mName = $row->user_name;
1278  $this->mFrom = 'name';
1279  $this->setItemLoaded( 'name' );
1280  } else {
1281  $all = false;
1282  }
1283 
1284  if ( isset( $row->user_real_name ) ) {
1285  $this->mRealName = $row->user_real_name;
1286  $this->setItemLoaded( 'realname' );
1287  } else {
1288  $all = false;
1289  }
1290 
1291  if ( isset( $row->user_id ) ) {
1292  $this->mId = intval( $row->user_id );
1293  $this->mFrom = 'id';
1294  $this->setItemLoaded( 'id' );
1295  } else {
1296  $all = false;
1297  }
1298 
1299  if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
1300  self::$idCacheByName[$row->user_name] = $row->user_id;
1301  }
1302 
1303  if ( isset( $row->user_editcount ) ) {
1304  $this->mEditCount = $row->user_editcount;
1305  } else {
1306  $all = false;
1307  }
1308 
1309  if ( isset( $row->user_touched ) ) {
1310  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1311  } else {
1312  $all = false;
1313  }
1314 
1315  if ( isset( $row->user_token ) ) {
1316  // The definition for the column is binary(32), so trim the NULs
1317  // that appends. The previous definition was char(32), so trim
1318  // spaces too.
1319  $this->mToken = rtrim( $row->user_token, " \0" );
1320  if ( $this->mToken === '' ) {
1321  $this->mToken = null;
1322  }
1323  } else {
1324  $all = false;
1325  }
1326 
1327  if ( isset( $row->user_email ) ) {
1328  $this->mEmail = $row->user_email;
1329  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1330  $this->mEmailToken = $row->user_email_token;
1331  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1332  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1333  } else {
1334  $all = false;
1335  }
1336 
1337  if ( $all ) {
1338  $this->mLoadedItems = true;
1339  }
1340 
1341  if ( is_array( $data ) ) {
1342  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1343  $this->mGroups = $data['user_groups'];
1344  }
1345  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1346  $this->loadOptions( $data['user_properties'] );
1347  }
1348  }
1349  }
1350 
1356  protected function loadFromUserObject( $user ) {
1357  $user->load();
1358  foreach ( self::$mCacheVars as $var ) {
1359  $this->$var = $user->$var;
1360  }
1361  }
1362 
1366  private function loadGroups() {
1367  if ( is_null( $this->mGroups ) ) {
1368  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1369  ? wfGetDB( DB_MASTER )
1370  : wfGetDB( DB_SLAVE );
1371  $res = $db->select( 'user_groups',
1372  [ 'ug_group' ],
1373  [ 'ug_user' => $this->mId ],
1374  __METHOD__ );
1375  $this->mGroups = [];
1376  foreach ( $res as $row ) {
1377  $this->mGroups[] = $row->ug_group;
1378  }
1379  }
1380  }
1381 
1396  public function addAutopromoteOnceGroups( $event ) {
1397  global $wgAutopromoteOnceLogInRC;
1398 
1399  if ( wfReadOnly() || !$this->getId() ) {
1400  return [];
1401  }
1402 
1403  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1404  if ( !count( $toPromote ) ) {
1405  return [];
1406  }
1407 
1408  if ( !$this->checkAndSetTouched() ) {
1409  return []; // raced out (bug T48834)
1410  }
1411 
1412  $oldGroups = $this->getGroups(); // previous groups
1413  foreach ( $toPromote as $group ) {
1414  $this->addGroup( $group );
1415  }
1416  // update groups in external authentication database
1417  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false ] );
1418  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1419 
1420  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1421 
1422  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1423  $logEntry->setPerformer( $this );
1424  $logEntry->setTarget( $this->getUserPage() );
1425  $logEntry->setParameters( [
1426  '4::oldgroups' => $oldGroups,
1427  '5::newgroups' => $newGroups,
1428  ] );
1429  $logid = $logEntry->insert();
1430  if ( $wgAutopromoteOnceLogInRC ) {
1431  $logEntry->publish( $logid );
1432  }
1433 
1434  return $toPromote;
1435  }
1436 
1446  protected function checkAndSetTouched() {
1447  $this->load();
1448 
1449  if ( !$this->mId ) {
1450  return false; // anon
1451  }
1452 
1453  // Get a new user_touched that is higher than the old one
1454  $oldTouched = $this->mTouched;
1455  $newTouched = $this->newTouchedTimestamp();
1456 
1457  $dbw = wfGetDB( DB_MASTER );
1458  $dbw->update( 'user',
1459  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1460  [
1461  'user_id' => $this->mId,
1462  'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
1463  ],
1464  __METHOD__
1465  );
1466  $success = ( $dbw->affectedRows() > 0 );
1467 
1468  if ( $success ) {
1469  $this->mTouched = $newTouched;
1470  $this->clearSharedCache();
1471  } else {
1472  // Clears on failure too since that is desired if the cache is stale
1473  $this->clearSharedCache( 'refresh' );
1474  }
1475 
1476  return $success;
1477  }
1478 
1486  public function clearInstanceCache( $reloadFrom = false ) {
1487  $this->mNewtalk = -1;
1488  $this->mDatePreference = null;
1489  $this->mBlockedby = -1; # Unset
1490  $this->mHash = false;
1491  $this->mRights = null;
1492  $this->mEffectiveGroups = null;
1493  $this->mImplicitGroups = null;
1494  $this->mGroups = null;
1495  $this->mOptions = null;
1496  $this->mOptionsLoaded = false;
1497  $this->mEditCount = null;
1498 
1499  if ( $reloadFrom ) {
1500  $this->mLoadedItems = [];
1501  $this->mFrom = $reloadFrom;
1502  }
1503  }
1504 
1511  public static function getDefaultOptions() {
1512  global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
1513 
1514  static $defOpt = null;
1515  if ( !defined( 'MW_PHPUNIT_TEST' ) && $defOpt !== null ) {
1516  // Disabling this for the unit tests, as they rely on being able to change $wgContLang
1517  // mid-request and see that change reflected in the return value of this function.
1518  // Which is insane and would never happen during normal MW operation
1519  return $defOpt;
1520  }
1521 
1522  $defOpt = $wgDefaultUserOptions;
1523  // Default language setting
1524  $defOpt['language'] = $wgContLang->getCode();
1525  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1526  $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
1527  }
1528  $namespaces = MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
1529  foreach ( $namespaces as $nsnum => $nsname ) {
1530  $defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
1531  }
1532  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1533 
1534  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1535 
1536  return $defOpt;
1537  }
1538 
1545  public static function getDefaultOption( $opt ) {
1546  $defOpts = self::getDefaultOptions();
1547  if ( isset( $defOpts[$opt] ) ) {
1548  return $defOpts[$opt];
1549  } else {
1550  return null;
1551  }
1552  }
1553 
1560  private function getBlockedStatus( $bFromSlave = true ) {
1561  global $wgProxyWhitelist, $wgUser, $wgApplyIpBlocksToXff;
1562 
1563  if ( -1 != $this->mBlockedby ) {
1564  return;
1565  }
1566 
1567  wfDebug( __METHOD__ . ": checking...\n" );
1568 
1569  // Initialize data...
1570  // Otherwise something ends up stomping on $this->mBlockedby when
1571  // things get lazy-loaded later, causing false positive block hits
1572  // due to -1 !== 0. Probably session-related... Nothing should be
1573  // overwriting mBlockedby, surely?
1574  $this->load();
1575 
1576  # We only need to worry about passing the IP address to the Block generator if the
1577  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1578  # know which IP address they're actually coming from
1579  $ip = null;
1580  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1581  // $wgUser->getName() only works after the end of Setup.php. Until
1582  // then, assume it's a logged-out user.
1583  $globalUserName = $wgUser->isSafeToLoad()
1584  ? $wgUser->getName()
1585  : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1586  if ( $this->getName() === $globalUserName ) {
1587  $ip = $this->getRequest()->getIP();
1588  }
1589  }
1590 
1591  // User/IP blocking
1592  $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1593 
1594  // Proxy blocking
1595  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1596  // Local list
1597  if ( self::isLocallyBlockedProxy( $ip ) ) {
1598  $block = new Block;
1599  $block->setBlocker( wfMessage( 'proxyblocker' )->text() );
1600  $block->mReason = wfMessage( 'proxyblockreason' )->text();
1601  $block->setTarget( $ip );
1602  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1603  $block = new Block;
1604  $block->setBlocker( wfMessage( 'sorbs' )->text() );
1605  $block->mReason = wfMessage( 'sorbsreason' )->text();
1606  $block->setTarget( $ip );
1607  }
1608  }
1609 
1610  // (bug 23343) Apply IP blocks to the contents of XFF headers, if enabled
1611  if ( !$block instanceof Block
1612  && $wgApplyIpBlocksToXff
1613  && $ip !== null
1614  && !in_array( $ip, $wgProxyWhitelist )
1615  ) {
1616  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1617  $xff = array_map( 'trim', explode( ',', $xff ) );
1618  $xff = array_diff( $xff, [ $ip ] );
1619  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1620  $block = Block::chooseBlock( $xffblocks, $xff );
1621  if ( $block instanceof Block ) {
1622  # Mangle the reason to alert the user that the block
1623  # originated from matching the X-Forwarded-For header.
1624  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
1625  }
1626  }
1627 
1628  if ( $block instanceof Block ) {
1629  wfDebug( __METHOD__ . ": Found block.\n" );
1630  $this->mBlock = $block;
1631  $this->mBlockedby = $block->getByName();
1632  $this->mBlockreason = $block->mReason;
1633  $this->mHideName = $block->mHideName;
1634  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1635  } else {
1636  $this->mBlockedby = '';
1637  $this->mHideName = 0;
1638  $this->mAllowUsertalk = false;
1639  }
1640 
1641  // Extensions
1642  Hooks::run( 'GetBlockedStatus', [ &$this ] );
1643 
1644  }
1645 
1653  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1654  global $wgEnableDnsBlacklist, $wgDnsBlacklistUrls, $wgProxyWhitelist;
1655 
1656  if ( !$wgEnableDnsBlacklist ) {
1657  return false;
1658  }
1659 
1660  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
1661  return false;
1662  }
1663 
1664  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
1665  }
1666 
1674  public function inDnsBlacklist( $ip, $bases ) {
1675 
1676  $found = false;
1677  // @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
1678  if ( IP::isIPv4( $ip ) ) {
1679  // Reverse IP, bug 21255
1680  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1681 
1682  foreach ( (array)$bases as $base ) {
1683  // Make hostname
1684  // If we have an access key, use that too (ProjectHoneypot, etc.)
1685  $basename = $base;
1686  if ( is_array( $base ) ) {
1687  if ( count( $base ) >= 2 ) {
1688  // Access key is 1, base URL is 0
1689  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1690  } else {
1691  $host = "$ipReversed.{$base[0]}";
1692  }
1693  $basename = $base[0];
1694  } else {
1695  $host = "$ipReversed.$base";
1696  }
1697 
1698  // Send query
1699  $ipList = gethostbynamel( $host );
1700 
1701  if ( $ipList ) {
1702  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1703  $found = true;
1704  break;
1705  } else {
1706  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1707  }
1708  }
1709  }
1710 
1711  return $found;
1712  }
1713 
1721  public static function isLocallyBlockedProxy( $ip ) {
1722  global $wgProxyList;
1723 
1724  if ( !$wgProxyList ) {
1725  return false;
1726  }
1727 
1728  if ( !is_array( $wgProxyList ) ) {
1729  // Load from the specified file
1730  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1731  }
1732 
1733  if ( !is_array( $wgProxyList ) ) {
1734  $ret = false;
1735  } elseif ( array_search( $ip, $wgProxyList ) !== false ) {
1736  $ret = true;
1737  } elseif ( array_key_exists( $ip, $wgProxyList ) ) {
1738  // Old-style flipped proxy list
1739  $ret = true;
1740  } else {
1741  $ret = false;
1742  }
1743  return $ret;
1744  }
1745 
1751  public function isPingLimitable() {
1752  global $wgRateLimitsExcludedIPs;
1753  if ( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1754  // No other good way currently to disable rate limits
1755  // for specific IPs. :P
1756  // But this is a crappy hack and should die.
1757  return false;
1758  }
1759  return !$this->isAllowed( 'noratelimit' );
1760  }
1761 
1776  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1777  // Call the 'PingLimiter' hook
1778  $result = false;
1779  if ( !Hooks::run( 'PingLimiter', [ &$this, $action, &$result, $incrBy ] ) ) {
1780  return $result;
1781  }
1782 
1783  global $wgRateLimits;
1784  if ( !isset( $wgRateLimits[$action] ) ) {
1785  return false;
1786  }
1787 
1788  // Some groups shouldn't trigger the ping limiter, ever
1789  if ( !$this->isPingLimitable() ) {
1790  return false;
1791  }
1792 
1793  $limits = $wgRateLimits[$action];
1794  $keys = [];
1795  $id = $this->getId();
1796  $userLimit = false;
1797  $isNewbie = $this->isNewbie();
1798 
1799  if ( $id == 0 ) {
1800  // limits for anons
1801  if ( isset( $limits['anon'] ) ) {
1802  $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1803  }
1804  } else {
1805  // limits for logged-in users
1806  if ( isset( $limits['user'] ) ) {
1807  $userLimit = $limits['user'];
1808  }
1809  // limits for newbie logged-in users
1810  if ( $isNewbie && isset( $limits['newbie'] ) ) {
1811  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
1812  }
1813  }
1814 
1815  // limits for anons and for newbie logged-in users
1816  if ( $isNewbie ) {
1817  // ip-based limits
1818  if ( isset( $limits['ip'] ) ) {
1819  $ip = $this->getRequest()->getIP();
1820  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1821  }
1822  // subnet-based limits
1823  if ( isset( $limits['subnet'] ) ) {
1824  $ip = $this->getRequest()->getIP();
1825  $subnet = IP::getSubnet( $ip );
1826  if ( $subnet !== false ) {
1827  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1828  }
1829  }
1830  }
1831 
1832  // Check for group-specific permissions
1833  // If more than one group applies, use the group with the highest limit ratio (max/period)
1834  foreach ( $this->getGroups() as $group ) {
1835  if ( isset( $limits[$group] ) ) {
1836  if ( $userLimit === false
1837  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1838  ) {
1839  $userLimit = $limits[$group];
1840  }
1841  }
1842  }
1843 
1844  // Set the user limit key
1845  if ( $userLimit !== false ) {
1846  list( $max, $period ) = $userLimit;
1847  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
1848  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit;
1849  }
1850 
1851  // ip-based limits for all ping-limitable users
1852  if ( isset( $limits['ip-all'] ) ) {
1853  $ip = $this->getRequest()->getIP();
1854  // ignore if user limit is more permissive
1855  if ( $isNewbie || $userLimit === false
1856  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1857  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
1858  }
1859  }
1860 
1861  // subnet-based limits for all ping-limitable users
1862  if ( isset( $limits['subnet-all'] ) ) {
1863  $ip = $this->getRequest()->getIP();
1864  $subnet = IP::getSubnet( $ip );
1865  if ( $subnet !== false ) {
1866  // ignore if user limit is more permissive
1867  if ( $isNewbie || $userLimit === false
1868  || $limits['ip-all'][0] / $limits['ip-all'][1]
1869  > $userLimit[0] / $userLimit[1] ) {
1870  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
1871  }
1872  }
1873  }
1874 
1876 
1877  $triggered = false;
1878  foreach ( $keys as $key => $limit ) {
1879  list( $max, $period ) = $limit;
1880  $summary = "(limit $max in {$period}s)";
1881  $count = $cache->get( $key );
1882  // Already pinged?
1883  if ( $count ) {
1884  if ( $count >= $max ) {
1885  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
1886  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
1887  $triggered = true;
1888  } else {
1889  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
1890  }
1891  } else {
1892  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
1893  if ( $incrBy > 0 ) {
1894  $cache->add( $key, 0, intval( $period ) ); // first ping
1895  }
1896  }
1897  if ( $incrBy > 0 ) {
1898  $cache->incr( $key, $incrBy );
1899  }
1900  }
1901 
1902  return $triggered;
1903  }
1904 
1912  public function isBlocked( $bFromSlave = true ) {
1913  return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
1914  }
1915 
1922  public function getBlock( $bFromSlave = true ) {
1923  $this->getBlockedStatus( $bFromSlave );
1924  return $this->mBlock instanceof Block ? $this->mBlock : null;
1925  }
1926 
1934  public function isBlockedFrom( $title, $bFromSlave = false ) {
1935  global $wgBlockAllowsUTEdit;
1936 
1937  $blocked = $this->isBlocked( $bFromSlave );
1938  $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
1939  // If a user's name is suppressed, they cannot make edits anywhere
1940  if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
1941  && $title->getNamespace() == NS_USER_TALK ) {
1942  $blocked = false;
1943  wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
1944  }
1945 
1946  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
1947 
1948  return $blocked;
1949  }
1950 
1955  public function blockedBy() {
1956  $this->getBlockedStatus();
1957  return $this->mBlockedby;
1958  }
1959 
1964  public function blockedFor() {
1965  $this->getBlockedStatus();
1966  return $this->mBlockreason;
1967  }
1968 
1973  public function getBlockId() {
1974  $this->getBlockedStatus();
1975  return ( $this->mBlock ? $this->mBlock->getId() : false );
1976  }
1977 
1986  public function isBlockedGlobally( $ip = '' ) {
1987  return $this->getGlobalBlock( $ip ) instanceof Block;
1988  }
1989 
2000  public function getGlobalBlock( $ip = '' ) {
2001  if ( $this->mGlobalBlock !== null ) {
2002  return $this->mGlobalBlock ?: null;
2003  }
2004  // User is already an IP?
2005  if ( IP::isIPAddress( $this->getName() ) ) {
2006  $ip = $this->getName();
2007  } elseif ( !$ip ) {
2008  $ip = $this->getRequest()->getIP();
2009  }
2010  $blocked = false;
2011  $block = null;
2012  Hooks::run( 'UserIsBlockedGlobally', [ &$this, $ip, &$blocked, &$block ] );
2013 
2014  if ( $blocked && $block === null ) {
2015  // back-compat: UserIsBlockedGlobally didn't have $block param first
2016  $block = new Block;
2017  $block->setTarget( $ip );
2018  }
2019 
2020  $this->mGlobalBlock = $blocked ? $block : false;
2021  return $this->mGlobalBlock ?: null;
2022  }
2023 
2029  public function isLocked() {
2030  if ( $this->mLocked !== null ) {
2031  return $this->mLocked;
2032  }
2033  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2034  $this->mLocked = $authUser && $authUser->isLocked();
2035  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2036  return $this->mLocked;
2037  }
2038 
2044  public function isHidden() {
2045  if ( $this->mHideName !== null ) {
2046  return $this->mHideName;
2047  }
2048  $this->getBlockedStatus();
2049  if ( !$this->mHideName ) {
2050  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2051  $this->mHideName = $authUser && $authUser->isHidden();
2052  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2053  }
2054  return $this->mHideName;
2055  }
2056 
2061  public function getId() {
2062  if ( $this->mId === null && $this->mName !== null && User::isIP( $this->mName ) ) {
2063  // Special case, we know the user is anonymous
2064  return 0;
2065  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2066  // Don't load if this was initialized from an ID
2067  $this->load();
2068  }
2069 
2070  return (int)$this->mId;
2071  }
2072 
2077  public function setId( $v ) {
2078  $this->mId = $v;
2079  $this->clearInstanceCache( 'id' );
2080  }
2081 
2086  public function getName() {
2087  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2088  // Special case optimisation
2089  return $this->mName;
2090  } else {
2091  $this->load();
2092  if ( $this->mName === false ) {
2093  // Clean up IPs
2094  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2095  }
2096  return $this->mName;
2097  }
2098  }
2099 
2113  public function setName( $str ) {
2114  $this->load();
2115  $this->mName = $str;
2116  }
2117 
2122  public function getTitleKey() {
2123  return str_replace( ' ', '_', $this->getName() );
2124  }
2125 
2130  public function getNewtalk() {
2131  $this->load();
2132 
2133  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2134  if ( $this->mNewtalk === -1 ) {
2135  $this->mNewtalk = false; # reset talk page status
2136 
2137  // Check memcached separately for anons, who have no
2138  // entire User object stored in there.
2139  if ( !$this->mId ) {
2140  global $wgDisableAnonTalk;
2141  if ( $wgDisableAnonTalk ) {
2142  // Anon newtalk disabled by configuration.
2143  $this->mNewtalk = false;
2144  } else {
2145  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2146  }
2147  } else {
2148  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2149  }
2150  }
2151 
2152  return (bool)$this->mNewtalk;
2153  }
2154 
2168  public function getNewMessageLinks() {
2169  $talks = [];
2170  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$this, &$talks ] ) ) {
2171  return $talks;
2172  } elseif ( !$this->getNewtalk() ) {
2173  return [];
2174  }
2175  $utp = $this->getTalkPage();
2176  $dbr = wfGetDB( DB_SLAVE );
2177  // Get the "last viewed rev" timestamp from the oldest message notification
2178  $timestamp = $dbr->selectField( 'user_newtalk',
2179  'MIN(user_last_timestamp)',
2180  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2181  __METHOD__ );
2183  return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2184  }
2185 
2191  public function getNewMessageRevisionId() {
2192  $newMessageRevisionId = null;
2193  $newMessageLinks = $this->getNewMessageLinks();
2194  if ( $newMessageLinks ) {
2195  // Note: getNewMessageLinks() never returns more than a single link
2196  // and it is always for the same wiki, but we double-check here in
2197  // case that changes some time in the future.
2198  if ( count( $newMessageLinks ) === 1
2199  && $newMessageLinks[0]['wiki'] === wfWikiID()
2200  && $newMessageLinks[0]['rev']
2201  ) {
2203  $newMessageRevision = $newMessageLinks[0]['rev'];
2204  $newMessageRevisionId = $newMessageRevision->getId();
2205  }
2206  }
2207  return $newMessageRevisionId;
2208  }
2209 
2218  protected function checkNewtalk( $field, $id ) {
2219  $dbr = wfGetDB( DB_SLAVE );
2220 
2221  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2222 
2223  return $ok !== false;
2224  }
2225 
2233  protected function updateNewtalk( $field, $id, $curRev = null ) {
2234  // Get timestamp of the talk page revision prior to the current one
2235  $prevRev = $curRev ? $curRev->getPrevious() : false;
2236  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2237  // Mark the user as having new messages since this revision
2238  $dbw = wfGetDB( DB_MASTER );
2239  $dbw->insert( 'user_newtalk',
2240  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2241  __METHOD__,
2242  'IGNORE' );
2243  if ( $dbw->affectedRows() ) {
2244  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2245  return true;
2246  } else {
2247  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2248  return false;
2249  }
2250  }
2251 
2258  protected function deleteNewtalk( $field, $id ) {
2259  $dbw = wfGetDB( DB_MASTER );
2260  $dbw->delete( 'user_newtalk',
2261  [ $field => $id ],
2262  __METHOD__ );
2263  if ( $dbw->affectedRows() ) {
2264  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2265  return true;
2266  } else {
2267  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2268  return false;
2269  }
2270  }
2271 
2278  public function setNewtalk( $val, $curRev = null ) {
2279  if ( wfReadOnly() ) {
2280  return;
2281  }
2282 
2283  $this->load();
2284  $this->mNewtalk = $val;
2285 
2286  if ( $this->isAnon() ) {
2287  $field = 'user_ip';
2288  $id = $this->getName();
2289  } else {
2290  $field = 'user_id';
2291  $id = $this->getId();
2292  }
2293 
2294  if ( $val ) {
2295  $changed = $this->updateNewtalk( $field, $id, $curRev );
2296  } else {
2297  $changed = $this->deleteNewtalk( $field, $id );
2298  }
2299 
2300  if ( $changed ) {
2301  $this->invalidateCache();
2302  }
2303  }
2304 
2310  private function newTouchedTimestamp() {
2312 
2313  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2314  if ( $this->mTouched && $time <= $this->mTouched ) {
2315  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2316  }
2317 
2318  return $time;
2319  }
2320 
2331  public function clearSharedCache( $mode = 'changed' ) {
2332  if ( !$this->getId() ) {
2333  return;
2334  }
2335 
2337  $processCache = self::getInProcessCache();
2338  $key = $this->getCacheKey( $cache );
2339  if ( $mode === 'refresh' ) {
2340  $cache->delete( $key, 1 );
2341  $processCache->delete( $key );
2342  } else {
2343  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
2344  function() use ( $cache, $processCache, $key ) {
2345  $cache->delete( $key );
2346  $processCache->delete( $key );
2347  }
2348  );
2349  }
2350  }
2351 
2357  public function invalidateCache() {
2358  $this->touch();
2359  $this->clearSharedCache();
2360  }
2361 
2374  public function touch() {
2375  $id = $this->getId();
2376  if ( $id ) {
2377  $key = wfMemcKey( 'user-quicktouched', 'id', $id );
2378  ObjectCache::getMainWANInstance()->touchCheckKey( $key );
2379  $this->mQuickTouched = null;
2380  }
2381  }
2382 
2388  public function validateCache( $timestamp ) {
2389  return ( $timestamp >= $this->getTouched() );
2390  }
2391 
2400  public function getTouched() {
2401  $this->load();
2402 
2403  if ( $this->mId ) {
2404  if ( $this->mQuickTouched === null ) {
2405  $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
2407 
2408  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2409  }
2410 
2411  return max( $this->mTouched, $this->mQuickTouched );
2412  }
2413 
2414  return $this->mTouched;
2415  }
2416 
2422  public function getDBTouched() {
2423  $this->load();
2424 
2425  return $this->mTouched;
2426  }
2427 
2433  public function getPassword() {
2434  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2435  }
2436 
2442  public function getTemporaryPassword() {
2443  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2444  }
2445 
2462  public function setPassword( $str ) {
2463  return $this->setPasswordInternal( $str );
2464  }
2465 
2474  public function setInternalPassword( $str ) {
2475  $this->setPasswordInternal( $str );
2476  }
2477 
2486  private function setPasswordInternal( $str ) {
2487  $manager = AuthManager::singleton();
2488 
2489  // If the user doesn't exist yet, fail
2490  if ( !$manager->userExists( $this->getName() ) ) {
2491  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2492  }
2493 
2494  $status = $this->changeAuthenticationData( [
2495  'username' => $this->getName(),
2496  'password' => $str,
2497  'retype' => $str,
2498  ] );
2499  if ( !$status->isGood() ) {
2501  ->info( __METHOD__ . ': Password change rejected: '
2502  . $status->getWikiText( null, null, 'en' ) );
2503  return false;
2504  }
2505 
2506  $this->setOption( 'watchlisttoken', false );
2507  SessionManager::singleton()->invalidateSessionsForUser( $this );
2508 
2509  return true;
2510  }
2511 
2524  public function changeAuthenticationData( array $data ) {
2525  $manager = AuthManager::singleton();
2526  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2527  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2528 
2529  $status = Status::newGood( 'ignored' );
2530  foreach ( $reqs as $req ) {
2531  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2532  }
2533  if ( $status->getValue() === 'ignored' ) {
2534  $status->warning( 'authenticationdatachange-ignored' );
2535  }
2536 
2537  if ( $status->isGood() ) {
2538  foreach ( $reqs as $req ) {
2539  $manager->changeAuthenticationData( $req );
2540  }
2541  }
2542  return $status;
2543  }
2544 
2551  public function getToken( $forceCreation = true ) {
2552  global $wgAuthenticationTokenVersion;
2553 
2554  $this->load();
2555  if ( !$this->mToken && $forceCreation ) {
2556  $this->setToken();
2557  }
2558 
2559  if ( !$this->mToken ) {
2560  // The user doesn't have a token, return null to indicate that.
2561  return null;
2562  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2563  // We return a random value here so existing token checks are very
2564  // likely to fail.
2565  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2566  } elseif ( $wgAuthenticationTokenVersion === null ) {
2567  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2568  return $this->mToken;
2569  } else {
2570  // $wgAuthenticationTokenVersion in use, so hmac it.
2571  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2572 
2573  // The raw hash can be overly long. Shorten it up.
2574  $len = max( 32, self::TOKEN_LENGTH );
2575  if ( strlen( $ret ) < $len ) {
2576  // Should never happen, even md5 is 128 bits
2577  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2578  }
2579  return substr( $ret, -$len );
2580  }
2581  }
2582 
2589  public function setToken( $token = false ) {
2590  $this->load();
2591  if ( $this->mToken === self::INVALID_TOKEN ) {
2593  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2594  } elseif ( !$token ) {
2595  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2596  } else {
2597  $this->mToken = $token;
2598  }
2599  }
2600 
2609  public function setNewpassword( $str, $throttle = true ) {
2610  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2611  }
2612 
2619  public function isPasswordReminderThrottled() {
2620  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2621  }
2622 
2627  public function getEmail() {
2628  $this->load();
2629  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2630  return $this->mEmail;
2631  }
2632 
2638  $this->load();
2639  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2641  }
2642 
2647  public function setEmail( $str ) {
2648  $this->load();
2649  if ( $str == $this->mEmail ) {
2650  return;
2651  }
2652  $this->invalidateEmail();
2653  $this->mEmail = $str;
2654  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2655  }
2656 
2664  public function setEmailWithConfirmation( $str ) {
2666 
2667  if ( !$wgEnableEmail ) {
2668  return Status::newFatal( 'emaildisabled' );
2669  }
2670 
2671  $oldaddr = $this->getEmail();
2672  if ( $str === $oldaddr ) {
2673  return Status::newGood( true );
2674  }
2675 
2676  $type = $oldaddr != '' ? 'changed' : 'set';
2677  $notificationResult = null;
2678 
2679  if ( $wgEmailAuthentication ) {
2680  // Send the user an email notifying the user of the change in registered
2681  // email address on their previous email address
2682  if ( $type == 'changed' ) {
2683  $change = $str != '' ? 'changed' : 'removed';
2684  $notificationResult = $this->sendMail(
2685  wfMessage( 'notificationemail_subject_' . $change )->text(),
2686  wfMessage( 'notificationemail_body_' . $change,
2687  $this->getRequest()->getIP(),
2688  $this->getName(),
2689  $str )->text()
2690  );
2691  }
2692  }
2693 
2694  $this->setEmail( $str );
2695 
2696  if ( $str !== '' && $wgEmailAuthentication ) {
2697  // Send a confirmation request to the new address if needed
2698  $result = $this->sendConfirmationMail( $type );
2699 
2700  if ( $notificationResult !== null ) {
2701  $result->merge( $notificationResult );
2702  }
2703 
2704  if ( $result->isGood() ) {
2705  // Say to the caller that a confirmation and notification mail has been sent
2706  $result->value = 'eauth';
2707  }
2708  } else {
2709  $result = Status::newGood( true );
2710  }
2711 
2712  return $result;
2713  }
2714 
2719  public function getRealName() {
2720  if ( !$this->isItemLoaded( 'realname' ) ) {
2721  $this->load();
2722  }
2723 
2724  return $this->mRealName;
2725  }
2726 
2731  public function setRealName( $str ) {
2732  $this->load();
2733  $this->mRealName = $str;
2734  }
2735 
2746  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2747  global $wgHiddenPrefs;
2748  $this->loadOptions();
2749 
2750  # We want 'disabled' preferences to always behave as the default value for
2751  # users, even if they have set the option explicitly in their settings (ie they
2752  # set it, and then it was disabled removing their ability to change it). But
2753  # we don't want to erase the preferences in the database in case the preference
2754  # is re-enabled again. So don't touch $mOptions, just override the returned value
2755  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2756  return self::getDefaultOption( $oname );
2757  }
2758 
2759  if ( array_key_exists( $oname, $this->mOptions ) ) {
2760  return $this->mOptions[$oname];
2761  } else {
2762  return $defaultOverride;
2763  }
2764  }
2765 
2774  public function getOptions( $flags = 0 ) {
2775  global $wgHiddenPrefs;
2776  $this->loadOptions();
2778 
2779  # We want 'disabled' preferences to always behave as the default value for
2780  # users, even if they have set the option explicitly in their settings (ie they
2781  # set it, and then it was disabled removing their ability to change it). But
2782  # we don't want to erase the preferences in the database in case the preference
2783  # is re-enabled again. So don't touch $mOptions, just override the returned value
2784  foreach ( $wgHiddenPrefs as $pref ) {
2785  $default = self::getDefaultOption( $pref );
2786  if ( $default !== null ) {
2787  $options[$pref] = $default;
2788  }
2789  }
2790 
2791  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2792  $options = array_diff_assoc( $options, self::getDefaultOptions() );
2793  }
2794 
2795  return $options;
2796  }
2797 
2805  public function getBoolOption( $oname ) {
2806  return (bool)$this->getOption( $oname );
2807  }
2808 
2817  public function getIntOption( $oname, $defaultOverride = 0 ) {
2818  $val = $this->getOption( $oname );
2819  if ( $val == '' ) {
2820  $val = $defaultOverride;
2821  }
2822  return intval( $val );
2823  }
2824 
2833  public function setOption( $oname, $val ) {
2834  $this->loadOptions();
2835 
2836  // Explicitly NULL values should refer to defaults
2837  if ( is_null( $val ) ) {
2838  $val = self::getDefaultOption( $oname );
2839  }
2840 
2841  $this->mOptions[$oname] = $val;
2842  }
2843 
2854  public function getTokenFromOption( $oname ) {
2855  global $wgHiddenPrefs;
2856 
2857  $id = $this->getId();
2858  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2859  return false;
2860  }
2861 
2862  $token = $this->getOption( $oname );
2863  if ( !$token ) {
2864  // Default to a value based on the user token to avoid space
2865  // wasted on storing tokens for all users. When this option
2866  // is set manually by the user, only then is it stored.
2867  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2868  }
2869 
2870  return $token;
2871  }
2872 
2882  public function resetTokenFromOption( $oname ) {
2883  global $wgHiddenPrefs;
2884  if ( in_array( $oname, $wgHiddenPrefs ) ) {
2885  return false;
2886  }
2887 
2888  $token = MWCryptRand::generateHex( 40 );
2889  $this->setOption( $oname, $token );
2890  return $token;
2891  }
2892 
2916  public static function listOptionKinds() {
2917  return [
2918  'registered',
2919  'registered-multiselect',
2920  'registered-checkmatrix',
2921  'userjs',
2922  'special',
2923  'unused'
2924  ];
2925  }
2926 
2939  public function getOptionKinds( IContextSource $context, $options = null ) {
2940  $this->loadOptions();
2941  if ( $options === null ) {
2943  }
2944 
2945  $prefs = Preferences::getPreferences( $this, $context );
2946  $mapping = [];
2947 
2948  // Pull out the "special" options, so they don't get converted as
2949  // multiselect or checkmatrix.
2950  $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
2951  foreach ( $specialOptions as $name => $value ) {
2952  unset( $prefs[$name] );
2953  }
2954 
2955  // Multiselect and checkmatrix options are stored in the database with
2956  // one key per option, each having a boolean value. Extract those keys.
2957  $multiselectOptions = [];
2958  foreach ( $prefs as $name => $info ) {
2959  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
2960  ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
2961  $opts = HTMLFormField::flattenOptions( $info['options'] );
2962  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2963 
2964  foreach ( $opts as $value ) {
2965  $multiselectOptions["$prefix$value"] = true;
2966  }
2967 
2968  unset( $prefs[$name] );
2969  }
2970  }
2971  $checkmatrixOptions = [];
2972  foreach ( $prefs as $name => $info ) {
2973  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
2974  ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
2975  $columns = HTMLFormField::flattenOptions( $info['columns'] );
2976  $rows = HTMLFormField::flattenOptions( $info['rows'] );
2977  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2978 
2979  foreach ( $columns as $column ) {
2980  foreach ( $rows as $row ) {
2981  $checkmatrixOptions["$prefix$column-$row"] = true;
2982  }
2983  }
2984 
2985  unset( $prefs[$name] );
2986  }
2987  }
2988 
2989  // $value is ignored
2990  foreach ( $options as $key => $value ) {
2991  if ( isset( $prefs[$key] ) ) {
2992  $mapping[$key] = 'registered';
2993  } elseif ( isset( $multiselectOptions[$key] ) ) {
2994  $mapping[$key] = 'registered-multiselect';
2995  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
2996  $mapping[$key] = 'registered-checkmatrix';
2997  } elseif ( isset( $specialOptions[$key] ) ) {
2998  $mapping[$key] = 'special';
2999  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3000  $mapping[$key] = 'userjs';
3001  } else {
3002  $mapping[$key] = 'unused';
3003  }
3004  }
3005 
3006  return $mapping;
3007  }
3008 
3023  public function resetOptions(
3024  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3025  IContextSource $context = null
3026  ) {
3027  $this->load();
3028  $defaultOptions = self::getDefaultOptions();
3029 
3030  if ( !is_array( $resetKinds ) ) {
3031  $resetKinds = [ $resetKinds ];
3032  }
3033 
3034  if ( in_array( 'all', $resetKinds ) ) {
3035  $newOptions = $defaultOptions;
3036  } else {
3037  if ( $context === null ) {
3039  }
3040 
3041  $optionKinds = $this->getOptionKinds( $context );
3042  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3043  $newOptions = [];
3044 
3045  // Use default values for the options that should be deleted, and
3046  // copy old values for the ones that shouldn't.
3047  foreach ( $this->mOptions as $key => $value ) {
3048  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3049  if ( array_key_exists( $key, $defaultOptions ) ) {
3050  $newOptions[$key] = $defaultOptions[$key];
3051  }
3052  } else {
3053  $newOptions[$key] = $value;
3054  }
3055  }
3056  }
3057 
3058  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3059 
3060  $this->mOptions = $newOptions;
3061  $this->mOptionsLoaded = true;
3062  }
3063 
3068  public function getDatePreference() {
3069  // Important migration for old data rows
3070  if ( is_null( $this->mDatePreference ) ) {
3071  global $wgLang;
3072  $value = $this->getOption( 'date' );
3073  $map = $wgLang->getDatePreferenceMigrationMap();
3074  if ( isset( $map[$value] ) ) {
3075  $value = $map[$value];
3076  }
3077  $this->mDatePreference = $value;
3078  }
3079  return $this->mDatePreference;
3080  }
3081 
3088  public function requiresHTTPS() {
3089  global $wgSecureLogin;
3090  if ( !$wgSecureLogin ) {
3091  return false;
3092  } else {
3093  $https = $this->getBoolOption( 'prefershttps' );
3094  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3095  if ( $https ) {
3096  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3097  }
3098  return $https;
3099  }
3100  }
3101 
3107  public function getStubThreshold() {
3108  global $wgMaxArticleSize; # Maximum article size, in Kb
3109  $threshold = $this->getIntOption( 'stubthreshold' );
3110  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3111  // If they have set an impossible value, disable the preference
3112  // so we can use the parser cache again.
3113  $threshold = 0;
3114  }
3115  return $threshold;
3116  }
3117 
3122  public function getRights() {
3123  if ( is_null( $this->mRights ) ) {
3124  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3125  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3126 
3127  // Deny any rights denied by the user's session, unless this
3128  // endpoint has no sessions.
3129  if ( !defined( 'MW_NO_SESSION' ) ) {
3130  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3131  if ( $allowedRights !== null ) {
3132  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3133  }
3134  }
3135 
3136  // Force reindexation of rights when a hook has unset one of them
3137  $this->mRights = array_values( array_unique( $this->mRights ) );
3138 
3139  // If block disables login, we should also remove any
3140  // extra rights blocked users might have, in case the
3141  // blocked user has a pre-existing session (T129738).
3142  // This is checked here for cases where people only call
3143  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3144  // to give a better error message in the common case.
3145  $config = RequestContext::getMain()->getConfig();
3146  if (
3147  $this->isLoggedIn() &&
3148  $config->get( 'BlockDisablesLogin' ) &&
3149  $this->isBlocked()
3150  ) {
3151  $anon = new User;
3152  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3153  }
3154  }
3155  return $this->mRights;
3156  }
3157 
3163  public function getGroups() {
3164  $this->load();
3165  $this->loadGroups();
3166  return $this->mGroups;
3167  }
3168 
3176  public function getEffectiveGroups( $recache = false ) {
3177  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3178  $this->mEffectiveGroups = array_unique( array_merge(
3179  $this->getGroups(), // explicit groups
3180  $this->getAutomaticGroups( $recache ) // implicit groups
3181  ) );
3182  // Hook for additional groups
3183  Hooks::run( 'UserEffectiveGroups', [ &$this, &$this->mEffectiveGroups ] );
3184  // Force reindexation of groups when a hook has unset one of them
3185  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3186  }
3187  return $this->mEffectiveGroups;
3188  }
3189 
3197  public function getAutomaticGroups( $recache = false ) {
3198  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3199  $this->mImplicitGroups = [ '*' ];
3200  if ( $this->getId() ) {
3201  $this->mImplicitGroups[] = 'user';
3202 
3203  $this->mImplicitGroups = array_unique( array_merge(
3204  $this->mImplicitGroups,
3206  ) );
3207  }
3208  if ( $recache ) {
3209  // Assure data consistency with rights/groups,
3210  // as getEffectiveGroups() depends on this function
3211  $this->mEffectiveGroups = null;
3212  }
3213  }
3214  return $this->mImplicitGroups;
3215  }
3216 
3226  public function getFormerGroups() {
3227  $this->load();
3228 
3229  if ( is_null( $this->mFormerGroups ) ) {
3230  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3231  ? wfGetDB( DB_MASTER )
3232  : wfGetDB( DB_SLAVE );
3233  $res = $db->select( 'user_former_groups',
3234  [ 'ufg_group' ],
3235  [ 'ufg_user' => $this->mId ],
3236  __METHOD__ );
3237  $this->mFormerGroups = [];
3238  foreach ( $res as $row ) {
3239  $this->mFormerGroups[] = $row->ufg_group;
3240  }
3241  }
3242 
3243  return $this->mFormerGroups;
3244  }
3245 
3250  public function getEditCount() {
3251  if ( !$this->getId() ) {
3252  return null;
3253  }
3254 
3255  if ( $this->mEditCount === null ) {
3256  /* Populate the count, if it has not been populated yet */
3257  $dbr = wfGetDB( DB_SLAVE );
3258  // check if the user_editcount field has been initialized
3259  $count = $dbr->selectField(
3260  'user', 'user_editcount',
3261  [ 'user_id' => $this->mId ],
3262  __METHOD__
3263  );
3264 
3265  if ( $count === null ) {
3266  // it has not been initialized. do so.
3267  $count = $this->initEditCount();
3268  }
3269  $this->mEditCount = $count;
3270  }
3271  return (int)$this->mEditCount;
3272  }
3273 
3280  public function addGroup( $group ) {
3281  $this->load();
3282 
3283  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group ] ) ) {
3284  return false;
3285  }
3286 
3287  $dbw = wfGetDB( DB_MASTER );
3288  if ( $this->getId() ) {
3289  $dbw->insert( 'user_groups',
3290  [
3291  'ug_user' => $this->getId(),
3292  'ug_group' => $group,
3293  ],
3294  __METHOD__,
3295  [ 'IGNORE' ] );
3296  }
3297 
3298  $this->loadGroups();
3299  $this->mGroups[] = $group;
3300  // In case loadGroups was not called before, we now have the right twice.
3301  // Get rid of the duplicate.
3302  $this->mGroups = array_unique( $this->mGroups );
3303 
3304  // Refresh the groups caches, and clear the rights cache so it will be
3305  // refreshed on the next call to $this->getRights().
3306  $this->getEffectiveGroups( true );
3307  $this->mRights = null;
3308 
3309  $this->invalidateCache();
3310 
3311  return true;
3312  }
3313 
3320  public function removeGroup( $group ) {
3321  $this->load();
3322  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3323  return false;
3324  }
3325 
3326  $dbw = wfGetDB( DB_MASTER );
3327  $dbw->delete( 'user_groups',
3328  [
3329  'ug_user' => $this->getId(),
3330  'ug_group' => $group,
3331  ], __METHOD__
3332  );
3333  // Remember that the user was in this group
3334  $dbw->insert( 'user_former_groups',
3335  [
3336  'ufg_user' => $this->getId(),
3337  'ufg_group' => $group,
3338  ],
3339  __METHOD__,
3340  [ 'IGNORE' ]
3341  );
3342 
3343  $this->loadGroups();
3344  $this->mGroups = array_diff( $this->mGroups, [ $group ] );
3345 
3346  // Refresh the groups caches, and clear the rights cache so it will be
3347  // refreshed on the next call to $this->getRights().
3348  $this->getEffectiveGroups( true );
3349  $this->mRights = null;
3350 
3351  $this->invalidateCache();
3352 
3353  return true;
3354  }
3355 
3360  public function isLoggedIn() {
3361  return $this->getId() != 0;
3362  }
3363 
3368  public function isAnon() {
3369  return !$this->isLoggedIn();
3370  }
3371 
3378  public function isAllowedAny() {
3379  $permissions = func_get_args();
3380  foreach ( $permissions as $permission ) {
3381  if ( $this->isAllowed( $permission ) ) {
3382  return true;
3383  }
3384  }
3385  return false;
3386  }
3387 
3393  public function isAllowedAll() {
3394  $permissions = func_get_args();
3395  foreach ( $permissions as $permission ) {
3396  if ( !$this->isAllowed( $permission ) ) {
3397  return false;
3398  }
3399  }
3400  return true;
3401  }
3402 
3408  public function isAllowed( $action = '' ) {
3409  if ( $action === '' ) {
3410  return true; // In the spirit of DWIM
3411  }
3412  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3413  // by misconfiguration: 0 == 'foo'
3414  return in_array( $action, $this->getRights(), true );
3415  }
3416 
3421  public function useRCPatrol() {
3422  global $wgUseRCPatrol;
3423  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3424  }
3425 
3430  public function useNPPatrol() {
3431  global $wgUseRCPatrol, $wgUseNPPatrol;
3432  return (
3433  ( $wgUseRCPatrol || $wgUseNPPatrol )
3434  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3435  );
3436  }
3437 
3442  public function useFilePatrol() {
3443  global $wgUseRCPatrol, $wgUseFilePatrol;
3444  return (
3445  ( $wgUseRCPatrol || $wgUseFilePatrol )
3446  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3447  );
3448  }
3449 
3455  public function getRequest() {
3456  if ( $this->mRequest ) {
3457  return $this->mRequest;
3458  } else {
3460  return $wgRequest;
3461  }
3462  }
3463 
3472  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3473  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3474  return WatchedItemStore::getDefaultInstance()->isWatched( $this, $title );
3475  }
3476  return false;
3477  }
3478 
3486  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3487  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3488  WatchedItemStore::getDefaultInstance()->addWatchBatchForUser(
3489  $this,
3490  [ $title->getSubjectPage(), $title->getTalkPage() ]
3491  );
3492  }
3493  $this->invalidateCache();
3494  }
3495 
3503  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3504  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3505  WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getSubjectPage() );
3506  WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getTalkPage() );
3507  }
3508  $this->invalidateCache();
3509  }
3510 
3519  public function clearNotification( &$title, $oldid = 0 ) {
3520  global $wgUseEnotif, $wgShowUpdatedMarker;
3521 
3522  // Do nothing if the database is locked to writes
3523  if ( wfReadOnly() ) {
3524  return;
3525  }
3526 
3527  // Do nothing if not allowed to edit the watchlist
3528  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3529  return;
3530  }
3531 
3532  // If we're working on user's talk page, we should update the talk page message indicator
3533  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3534  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$this, $oldid ] ) ) {
3535  return;
3536  }
3537 
3538  // Try to update the DB post-send and only if needed...
3539  DeferredUpdates::addCallableUpdate( function() use ( $title, $oldid ) {
3540  if ( !$this->getNewtalk() ) {
3541  return; // no notifications to clear
3542  }
3543 
3544  // Delete the last notifications (they stack up)
3545  $this->setNewtalk( false );
3546 
3547  // If there is a new, unseen, revision, use its timestamp
3548  $nextid = $oldid
3549  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3550  : null;
3551  if ( $nextid ) {
3552  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3553  }
3554  } );
3555  }
3556 
3557  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3558  return;
3559  }
3560 
3561  if ( $this->isAnon() ) {
3562  // Nothing else to do...
3563  return;
3564  }
3565 
3566  // Only update the timestamp if the page is being watched.
3567  // The query to find out if it is watched is cached both in memcached and per-invocation,
3568  // and when it does have to be executed, it can be on a slave
3569  // If this is the user's newtalk page, we always update the timestamp
3570  $force = '';
3571  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3572  $force = 'force';
3573  }
3574 
3576  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3577  }
3578 
3585  public function clearAllNotifications() {
3586  if ( wfReadOnly() ) {
3587  return;
3588  }
3589 
3590  // Do nothing if not allowed to edit the watchlist
3591  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3592  return;
3593  }
3594 
3595  global $wgUseEnotif, $wgShowUpdatedMarker;
3596  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3597  $this->setNewtalk( false );
3598  return;
3599  }
3600  $id = $this->getId();
3601  if ( $id != 0 ) {
3602  $dbw = wfGetDB( DB_MASTER );
3603  $dbw->update( 'watchlist',
3604  [ /* SET */ 'wl_notificationtimestamp' => null ],
3605  [ /* WHERE */ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3606  __METHOD__
3607  );
3608  // We also need to clear here the "you have new message" notification for the own user_talk page;
3609  // it's cleared one page view later in WikiPage::doViewUpdates().
3610  }
3611  }
3612 
3629  protected function setCookie(
3630  $name, $value, $exp = 0, $secure = null, $params = [], $request = null
3631  ) {
3632  wfDeprecated( __METHOD__, '1.27' );
3633  if ( $request === null ) {
3634  $request = $this->getRequest();
3635  }
3636  $params['secure'] = $secure;
3637  $request->response()->setCookie( $name, $value, $exp, $params );
3638  }
3639 
3650  protected function clearCookie( $name, $secure = null, $params = [] ) {
3651  wfDeprecated( __METHOD__, '1.27' );
3652  $this->setCookie( $name, '', time() - 86400, $secure, $params );
3653  }
3654 
3670  protected function setExtendedLoginCookie( $name, $value, $secure ) {
3671  global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
3672 
3673  wfDeprecated( __METHOD__, '1.27' );
3674 
3675  $exp = time();
3676  $exp += $wgExtendedLoginCookieExpiration !== null
3677  ? $wgExtendedLoginCookieExpiration
3678  : $wgCookieExpiration;
3679 
3680  $this->setCookie( $name, $value, $exp, $secure );
3681  }
3682 
3691  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3692  $this->load();
3693  if ( 0 == $this->mId ) {
3694  return;
3695  }
3696 
3697  $session = $this->getRequest()->getSession();
3698  if ( $request && $session->getRequest() !== $request ) {
3699  $session = $session->sessionWithRequest( $request );
3700  }
3701  $delay = $session->delaySave();
3702 
3703  if ( !$session->getUser()->equals( $this ) ) {
3704  if ( !$session->canSetUser() ) {
3706  ->warning( __METHOD__ .
3707  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3708  );
3709  return;
3710  }
3711  $session->setUser( $this );
3712  }
3713 
3714  $session->setRememberUser( $rememberMe );
3715  if ( $secure !== null ) {
3716  $session->setForceHTTPS( $secure );
3717  }
3718 
3719  $session->persist();
3720 
3721  ScopedCallback::consume( $delay );
3722  }
3723 
3727  public function logout() {
3728  if ( Hooks::run( 'UserLogout', [ &$this ] ) ) {
3729  $this->doLogout();
3730  }
3731  }
3732 
3737  public function doLogout() {
3738  $session = $this->getRequest()->getSession();
3739  if ( !$session->canSetUser() ) {
3741  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3742  } elseif ( !$session->getUser()->equals( $this ) ) {
3744  ->warning( __METHOD__ .
3745  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3746  );
3747  // But we still may as well make this user object anon
3748  $this->clearInstanceCache( 'defaults' );
3749  } else {
3750  $this->clearInstanceCache( 'defaults' );
3751  $delay = $session->delaySave();
3752  $session->unpersist(); // Clear cookies (T127436)
3753  $session->setLoggedOutTimestamp( time() );
3754  $session->setUser( new User );
3755  $session->set( 'wsUserID', 0 ); // Other code expects this
3756  $session->resetAllTokens();
3757  ScopedCallback::consume( $delay );
3758  }
3759  }
3760 
3765  public function saveSettings() {
3766  if ( wfReadOnly() ) {
3767  // @TODO: caller should deal with this instead!
3768  // This should really just be an exception.
3770  null,
3771  "Could not update user with ID '{$this->mId}'; DB is read-only."
3772  ) );
3773  return;
3774  }
3775 
3776  $this->load();
3777  if ( 0 == $this->mId ) {
3778  return; // anon
3779  }
3780 
3781  // Get a new user_touched that is higher than the old one.
3782  // This will be used for a CAS check as a last-resort safety
3783  // check against race conditions and slave lag.
3784  $oldTouched = $this->mTouched;
3785  $newTouched = $this->newTouchedTimestamp();
3786 
3787  $dbw = wfGetDB( DB_MASTER );
3788  $dbw->update( 'user',
3789  [ /* SET */
3790  'user_name' => $this->mName,
3791  'user_real_name' => $this->mRealName,
3792  'user_email' => $this->mEmail,
3793  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3794  'user_touched' => $dbw->timestamp( $newTouched ),
3795  'user_token' => strval( $this->mToken ),
3796  'user_email_token' => $this->mEmailToken,
3797  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3798  ], [ /* WHERE */
3799  'user_id' => $this->mId,
3800  'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
3801  ], __METHOD__
3802  );
3803 
3804  if ( !$dbw->affectedRows() ) {
3805  // Maybe the problem was a missed cache update; clear it to be safe
3806  $this->clearSharedCache( 'refresh' );
3807  // User was changed in the meantime or loaded with stale data
3808  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'slave';
3809  throw new MWException(
3810  "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
3811  " the version of the user to be saved is older than the current version."
3812  );
3813  }
3814 
3815  $this->mTouched = $newTouched;
3816  $this->saveOptions();
3817 
3818  Hooks::run( 'UserSaveSettings', [ $this ] );
3819  $this->clearSharedCache();
3820  $this->getUserPage()->invalidateCache();
3821  }
3822 
3829  public function idForName( $flags = 0 ) {
3830  $s = trim( $this->getName() );
3831  if ( $s === '' ) {
3832  return 0;
3833  }
3834 
3835  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
3836  ? wfGetDB( DB_MASTER )
3837  : wfGetDB( DB_SLAVE );
3838 
3839  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
3840  ? [ 'LOCK IN SHARE MODE' ]
3841  : [];
3842 
3843  $id = $db->selectField( 'user',
3844  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
3845 
3846  return (int)$id;
3847  }
3848 
3864  public static function createNew( $name, $params = [] ) {
3865  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
3866  if ( isset( $params[$field] ) ) {
3867  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
3868  unset( $params[$field] );
3869  }
3870  }
3871 
3872  $user = new User;
3873  $user->load();
3874  $user->setToken(); // init token
3875  if ( isset( $params['options'] ) ) {
3876  $user->mOptions = $params['options'] + (array)$user->mOptions;
3877  unset( $params['options'] );
3878  }
3879  $dbw = wfGetDB( DB_MASTER );
3880  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
3881 
3882  $noPass = PasswordFactory::newInvalidPassword()->toString();
3883 
3884  $fields = [
3885  'user_id' => $seqVal,
3886  'user_name' => $name,
3887  'user_password' => $noPass,
3888  'user_newpassword' => $noPass,
3889  'user_email' => $user->mEmail,
3890  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3891  'user_real_name' => $user->mRealName,
3892  'user_token' => strval( $user->mToken ),
3893  'user_registration' => $dbw->timestamp( $user->mRegistration ),
3894  'user_editcount' => 0,
3895  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3896  ];
3897  foreach ( $params as $name => $value ) {
3898  $fields["user_$name"] = $value;
3899  }
3900  $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
3901  if ( $dbw->affectedRows() ) {
3902  $newUser = User::newFromId( $dbw->insertId() );
3903  } else {
3904  $newUser = null;
3905  }
3906  return $newUser;
3907  }
3908 
3935  public function addToDatabase() {
3936  $this->load();
3937  if ( !$this->mToken ) {
3938  $this->setToken(); // init token
3939  }
3940 
3941  $this->mTouched = $this->newTouchedTimestamp();
3942 
3943  $noPass = PasswordFactory::newInvalidPassword()->toString();
3944 
3945  $dbw = wfGetDB( DB_MASTER );
3946  $inWrite = $dbw->writesOrCallbacksPending();
3947  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
3948  $dbw->insert( 'user',
3949  [
3950  'user_id' => $seqVal,
3951  'user_name' => $this->mName,
3952  'user_password' => $noPass,
3953  'user_newpassword' => $noPass,
3954  'user_email' => $this->mEmail,
3955  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3956  'user_real_name' => $this->mRealName,
3957  'user_token' => strval( $this->mToken ),
3958  'user_registration' => $dbw->timestamp( $this->mRegistration ),
3959  'user_editcount' => 0,
3960  'user_touched' => $dbw->timestamp( $this->mTouched ),
3961  ], __METHOD__,
3962  [ 'IGNORE' ]
3963  );
3964  if ( !$dbw->affectedRows() ) {
3965  // The queries below cannot happen in the same REPEATABLE-READ snapshot.
3966  // Handle this by COMMIT, if possible, or by LOCK IN SHARE MODE otherwise.
3967  if ( $inWrite ) {
3968  // Can't commit due to pending writes that may need atomicity.
3969  // This may cause some lock contention unlike the case below.
3970  $options = [ 'LOCK IN SHARE MODE' ];
3971  $flags = self::READ_LOCKING;
3972  } else {
3973  // Often, this case happens early in views before any writes when
3974  // using CentralAuth. It's should be OK to commit and break the snapshot.
3975  $dbw->commit( __METHOD__, 'flush' );
3976  $options = [];
3977  $flags = self::READ_LATEST;
3978  }
3979  $this->mId = $dbw->selectField( 'user', 'user_id',
3980  [ 'user_name' => $this->mName ], __METHOD__, $options );
3981  $loaded = false;
3982  if ( $this->mId ) {
3983  if ( $this->loadFromDatabase( $flags ) ) {
3984  $loaded = true;
3985  }
3986  }
3987  if ( !$loaded ) {
3988  throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
3989  "to insert user '{$this->mName}' row, but it was not present in select!" );
3990  }
3991  return Status::newFatal( 'userexists' );
3992  }
3993  $this->mId = $dbw->insertId();
3994  self::$idCacheByName[$this->mName] = $this->mId;
3995 
3996  // Clear instance cache other than user table data, which is already accurate
3997  $this->clearInstanceCache();
3998 
3999  $this->saveOptions();
4000  return Status::newGood();
4001  }
4002 
4008  public function spreadAnyEditBlock() {
4009  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4010  return $this->spreadBlock();
4011  }
4012 
4013  return false;
4014  }
4015 
4021  protected function spreadBlock() {
4022  wfDebug( __METHOD__ . "()\n" );
4023  $this->load();
4024  if ( $this->mId == 0 ) {
4025  return false;
4026  }
4027 
4028  $userblock = Block::newFromTarget( $this->getName() );
4029  if ( !$userblock ) {
4030  return false;
4031  }
4032 
4033  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4034  }
4035 
4040  public function isBlockedFromCreateAccount() {
4041  $this->getBlockedStatus();
4042  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4043  return $this->mBlock;
4044  }
4045 
4046  # bug 13611: if the IP address the user is trying to create an account from is
4047  # blocked with createaccount disabled, prevent new account creation there even
4048  # when the user is logged in
4049  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4050  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4051  }
4052  return $this->mBlockedFromCreateAccount instanceof Block
4053  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4054  ? $this->mBlockedFromCreateAccount
4055  : false;
4056  }
4057 
4062  public function isBlockedFromEmailuser() {
4063  $this->getBlockedStatus();
4064  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4065  }
4066 
4071  public function isAllowedToCreateAccount() {
4072  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4073  }
4074 
4080  public function getUserPage() {
4081  return Title::makeTitle( NS_USER, $this->getName() );
4082  }
4083 
4089  public function getTalkPage() {
4090  $title = $this->getUserPage();
4091  return $title->getTalkPage();
4092  }
4093 
4099  public function isNewbie() {
4100  return !$this->isAllowed( 'autoconfirmed' );
4101  }
4102 
4109  public function checkPassword( $password ) {
4110  $manager = AuthManager::singleton();
4111  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4112  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4113  [
4114  'username' => $this->getName(),
4115  'password' => $password,
4116  ]
4117  );
4118  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4119  switch ( $res->status ) {
4120  case AuthenticationResponse::PASS:
4121  return true;
4122  case AuthenticationResponse::FAIL:
4123  // Hope it's not a PreAuthenticationProvider that failed...
4125  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4126  return false;
4127  default:
4128  throw new BadMethodCallException(
4129  'AuthManager returned a response unsupported by ' . __METHOD__
4130  );
4131  }
4132  }
4133 
4142  public function checkTemporaryPassword( $plaintext ) {
4143  // Can't check the temporary password individually.
4144  return $this->checkPassword( $plaintext );
4145  }
4146 
4158  public function getEditTokenObject( $salt = '', $request = null ) {
4159  if ( $this->isAnon() ) {
4160  return new LoggedOutEditToken();
4161  }
4162 
4163  if ( !$request ) {
4164  $request = $this->getRequest();
4165  }
4166  return $request->getSession()->getToken( $salt );
4167  }
4168 
4180  public function getEditToken( $salt = '', $request = null ) {
4181  return $this->getEditTokenObject( $salt, $request )->toString();
4182  }
4183 
4190  public static function getEditTokenTimestamp( $val ) {
4191  wfDeprecated( __METHOD__, '1.27' );
4193  }
4194 
4207  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4208  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4209  }
4210 
4221  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4222  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4223  return $this->matchEditToken( $val, $salt, $request, $maxage );
4224  }
4225 
4233  public function sendConfirmationMail( $type = 'created' ) {
4234  global $wgLang;
4235  $expiration = null; // gets passed-by-ref and defined in next line.
4236  $token = $this->confirmationToken( $expiration );
4237  $url = $this->confirmationTokenUrl( $token );
4238  $invalidateURL = $this->invalidationTokenUrl( $token );
4239  $this->saveSettings();
4240 
4241  if ( $type == 'created' || $type === false ) {
4242  $message = 'confirmemail_body';
4243  } elseif ( $type === true ) {
4244  $message = 'confirmemail_body_changed';
4245  } else {
4246  // Messages: confirmemail_body_changed, confirmemail_body_set
4247  $message = 'confirmemail_body_' . $type;
4248  }
4249 
4250  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4251  wfMessage( $message,
4252  $this->getRequest()->getIP(),
4253  $this->getName(),
4254  $url,
4255  $wgLang->userTimeAndDate( $expiration, $this ),
4256  $invalidateURL,
4257  $wgLang->userDate( $expiration, $this ),
4258  $wgLang->userTime( $expiration, $this ) )->text() );
4259  }
4260 
4272  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4274 
4275  if ( $from instanceof User ) {
4276  $sender = MailAddress::newFromUser( $from );
4277  } else {
4278  $sender = new MailAddress( $wgPasswordSender,
4279  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4280  }
4281  $to = MailAddress::newFromUser( $this );
4282 
4283  return UserMailer::send( $to, $sender, $subject, $body, [
4284  'replyTo' => $replyto,
4285  ] );
4286  }
4287 
4298  protected function confirmationToken( &$expiration ) {
4300  $now = time();
4301  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4302  $expiration = wfTimestamp( TS_MW, $expires );
4303  $this->load();
4304  $token = MWCryptRand::generateHex( 32 );
4305  $hash = md5( $token );
4306  $this->mEmailToken = $hash;
4307  $this->mEmailTokenExpires = $expiration;
4308  return $token;
4309  }
4310 
4316  protected function confirmationTokenUrl( $token ) {
4317  return $this->getTokenUrl( 'ConfirmEmail', $token );
4318  }
4319 
4325  protected function invalidationTokenUrl( $token ) {
4326  return $this->getTokenUrl( 'InvalidateEmail', $token );
4327  }
4328 
4343  protected function getTokenUrl( $page, $token ) {
4344  // Hack to bypass localization of 'Special:'
4345  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4346  return $title->getCanonicalURL();
4347  }
4348 
4356  public function confirmEmail() {
4357  // Check if it's already confirmed, so we don't touch the database
4358  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4359  if ( !$this->isEmailConfirmed() ) {
4361  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4362  }
4363  return true;
4364  }
4365 
4373  public function invalidateEmail() {
4374  $this->load();
4375  $this->mEmailToken = null;
4376  $this->mEmailTokenExpires = null;
4377  $this->setEmailAuthenticationTimestamp( null );
4378  $this->mEmail = '';
4379  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4380  return true;
4381  }
4382 
4388  $this->load();
4389  $this->mEmailAuthenticated = $timestamp;
4390  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4391  }
4392 
4398  public function canSendEmail() {
4400  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4401  return false;
4402  }
4403  $canSend = $this->isEmailConfirmed();
4404  Hooks::run( 'UserCanSendEmail', [ &$this, &$canSend ] );
4405  return $canSend;
4406  }
4407 
4413  public function canReceiveEmail() {
4414  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4415  }
4416 
4427  public function isEmailConfirmed() {
4429  $this->load();
4430  $confirmed = true;
4431  if ( Hooks::run( 'EmailConfirmed', [ &$this, &$confirmed ] ) ) {
4432  if ( $this->isAnon() ) {
4433  return false;
4434  }
4435  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4436  return false;
4437  }
4438  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4439  return false;
4440  }
4441  return true;
4442  } else {
4443  return $confirmed;
4444  }
4445  }
4446 
4451  public function isEmailConfirmationPending() {
4453  return $wgEmailAuthentication &&
4454  !$this->isEmailConfirmed() &&
4455  $this->mEmailToken &&
4456  $this->mEmailTokenExpires > wfTimestamp();
4457  }
4458 
4466  public function getRegistration() {
4467  if ( $this->isAnon() ) {
4468  return false;
4469  }
4470  $this->load();
4471  return $this->mRegistration;
4472  }
4473 
4480  public function getFirstEditTimestamp() {
4481  if ( $this->getId() == 0 ) {
4482  return false; // anons
4483  }
4484  $dbr = wfGetDB( DB_SLAVE );
4485  $time = $dbr->selectField( 'revision', 'rev_timestamp',
4486  [ 'rev_user' => $this->getId() ],
4487  __METHOD__,
4488  [ 'ORDER BY' => 'rev_timestamp ASC' ]
4489  );
4490  if ( !$time ) {
4491  return false; // no edits
4492  }
4493  return wfTimestamp( TS_MW, $time );
4494  }
4495 
4502  public static function getGroupPermissions( $groups ) {
4503  global $wgGroupPermissions, $wgRevokePermissions;
4504  $rights = [];
4505  // grant every granted permission first
4506  foreach ( $groups as $group ) {
4507  if ( isset( $wgGroupPermissions[$group] ) ) {
4508  $rights = array_merge( $rights,
4509  // array_filter removes empty items
4510  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4511  }
4512  }
4513  // now revoke the revoked permissions
4514  foreach ( $groups as $group ) {
4515  if ( isset( $wgRevokePermissions[$group] ) ) {
4516  $rights = array_diff( $rights,
4517  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4518  }
4519  }
4520  return array_unique( $rights );
4521  }
4522 
4529  public static function getGroupsWithPermission( $role ) {
4530  global $wgGroupPermissions;
4531  $allowedGroups = [];
4532  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4533  if ( self::groupHasPermission( $group, $role ) ) {
4534  $allowedGroups[] = $group;
4535  }
4536  }
4537  return $allowedGroups;
4538  }
4539 
4552  public static function groupHasPermission( $group, $role ) {
4553  global $wgGroupPermissions, $wgRevokePermissions;
4554  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
4555  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
4556  }
4557 
4572  public static function isEveryoneAllowed( $right ) {
4573  global $wgGroupPermissions, $wgRevokePermissions;
4574  static $cache = [];
4575 
4576  // Use the cached results, except in unit tests which rely on
4577  // being able change the permission mid-request
4578  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
4579  return $cache[$right];
4580  }
4581 
4582  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
4583  $cache[$right] = false;
4584  return false;
4585  }
4586 
4587  // If it's revoked anywhere, then everyone doesn't have it
4588  foreach ( $wgRevokePermissions as $rights ) {
4589  if ( isset( $rights[$right] ) && $rights[$right] ) {
4590  $cache[$right] = false;
4591  return false;
4592  }
4593  }
4594 
4595  // Remove any rights that aren't allowed to the global-session user,
4596  // unless there are no sessions for this endpoint.
4597  if ( !defined( 'MW_NO_SESSION' ) ) {
4598  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
4599  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
4600  $cache[$right] = false;
4601  return false;
4602  }
4603  }
4604 
4605  // Allow extensions to say false
4606  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
4607  $cache[$right] = false;
4608  return false;
4609  }
4610 
4611  $cache[$right] = true;
4612  return true;
4613  }
4614 
4621  public static function getGroupName( $group ) {
4622  $msg = wfMessage( "group-$group" );
4623  return $msg->isBlank() ? $group : $msg->text();
4624  }
4625 
4633  public static function getGroupMember( $group, $username = '#' ) {
4634  $msg = wfMessage( "group-$group-member", $username );
4635  return $msg->isBlank() ? $group : $msg->text();
4636  }
4637 
4644  public static function getAllGroups() {
4645  global $wgGroupPermissions, $wgRevokePermissions;
4646  return array_diff(
4647  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4648  self::getImplicitGroups()
4649  );
4650  }
4651 
4656  public static function getAllRights() {
4657  if ( self::$mAllRights === false ) {
4658  global $wgAvailableRights;
4659  if ( count( $wgAvailableRights ) ) {
4660  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
4661  } else {
4662  self::$mAllRights = self::$mCoreRights;
4663  }
4664  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
4665  }
4666  return self::$mAllRights;
4667  }
4668 
4673  public static function getImplicitGroups() {
4674  global $wgImplicitGroups;
4675 
4676  $groups = $wgImplicitGroups;
4677  # Deprecated, use $wgImplicitGroups instead
4678  Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
4679 
4680  return $groups;
4681  }
4682 
4689  public static function getGroupPage( $group ) {
4690  $msg = wfMessage( 'grouppage-' . $group )->inContentLanguage();
4691  if ( $msg->exists() ) {
4692  $title = Title::newFromText( $msg->text() );
4693  if ( is_object( $title ) ) {
4694  return $title;
4695  }
4696  }
4697  return false;
4698  }
4699 
4708  public static function makeGroupLinkHTML( $group, $text = '' ) {
4709  if ( $text == '' ) {
4710  $text = self::getGroupName( $group );
4711  }
4712  $title = self::getGroupPage( $group );
4713  if ( $title ) {
4714  return Linker::link( $title, htmlspecialchars( $text ) );
4715  } else {
4716  return htmlspecialchars( $text );
4717  }
4718  }
4719 
4728  public static function makeGroupLinkWiki( $group, $text = '' ) {
4729  if ( $text == '' ) {
4730  $text = self::getGroupName( $group );
4731  }
4732  $title = self::getGroupPage( $group );
4733  if ( $title ) {
4734  $page = $title->getFullText();
4735  return "[[$page|$text]]";
4736  } else {
4737  return $text;
4738  }
4739  }
4740 
4750  public static function changeableByGroup( $group ) {
4751  global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
4752 
4753  $groups = [
4754  'add' => [],
4755  'remove' => [],
4756  'add-self' => [],
4757  'remove-self' => []
4758  ];
4759 
4760  if ( empty( $wgAddGroups[$group] ) ) {
4761  // Don't add anything to $groups
4762  } elseif ( $wgAddGroups[$group] === true ) {
4763  // You get everything
4764  $groups['add'] = self::getAllGroups();
4765  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4766  $groups['add'] = $wgAddGroups[$group];
4767  }
4768 
4769  // Same thing for remove
4770  if ( empty( $wgRemoveGroups[$group] ) ) {
4771  // Do nothing
4772  } elseif ( $wgRemoveGroups[$group] === true ) {
4773  $groups['remove'] = self::getAllGroups();
4774  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4775  $groups['remove'] = $wgRemoveGroups[$group];
4776  }
4777 
4778  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4779  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4780  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4781  if ( is_int( $key ) ) {
4782  $wgGroupsAddToSelf['user'][] = $value;
4783  }
4784  }
4785  }
4786 
4787  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4788  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4789  if ( is_int( $key ) ) {
4790  $wgGroupsRemoveFromSelf['user'][] = $value;
4791  }
4792  }
4793  }
4794 
4795  // Now figure out what groups the user can add to him/herself
4796  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4797  // Do nothing
4798  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4799  // No idea WHY this would be used, but it's there
4800  $groups['add-self'] = User::getAllGroups();
4801  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4802  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4803  }
4804 
4805  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4806  // Do nothing
4807  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4808  $groups['remove-self'] = User::getAllGroups();
4809  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4810  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4811  }
4812 
4813  return $groups;
4814  }
4815 
4823  public function changeableGroups() {
4824  if ( $this->isAllowed( 'userrights' ) ) {
4825  // This group gives the right to modify everything (reverse-
4826  // compatibility with old "userrights lets you change
4827  // everything")
4828  // Using array_merge to make the groups reindexed
4829  $all = array_merge( User::getAllGroups() );
4830  return [
4831  'add' => $all,
4832  'remove' => $all,
4833  'add-self' => [],
4834  'remove-self' => []
4835  ];
4836  }
4837 
4838  // Okay, it's not so simple, we will have to go through the arrays
4839  $groups = [
4840  'add' => [],
4841  'remove' => [],
4842  'add-self' => [],
4843  'remove-self' => []
4844  ];
4845  $addergroups = $this->getEffectiveGroups();
4846 
4847  foreach ( $addergroups as $addergroup ) {
4848  $groups = array_merge_recursive(
4849  $groups, $this->changeableByGroup( $addergroup )
4850  );
4851  $groups['add'] = array_unique( $groups['add'] );
4852  $groups['remove'] = array_unique( $groups['remove'] );
4853  $groups['add-self'] = array_unique( $groups['add-self'] );
4854  $groups['remove-self'] = array_unique( $groups['remove-self'] );
4855  }
4856  return $groups;
4857  }
4858 
4862  public function incEditCount() {
4863  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() {
4864  $this->incEditCountImmediate();
4865  } );
4866  }
4867 
4873  public function incEditCountImmediate() {
4874  if ( $this->isAnon() ) {
4875  return;
4876  }
4877 
4878  $dbw = wfGetDB( DB_MASTER );
4879  // No rows will be "affected" if user_editcount is NULL
4880  $dbw->update(
4881  'user',
4882  [ 'user_editcount=user_editcount+1' ],
4883  [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
4884  __METHOD__
4885  );
4886  // Lazy initialization check...
4887  if ( $dbw->affectedRows() == 0 ) {
4888  // Now here's a goddamn hack...
4889  $dbr = wfGetDB( DB_SLAVE );
4890  if ( $dbr !== $dbw ) {
4891  // If we actually have a slave server, the count is
4892  // at least one behind because the current transaction
4893  // has not been committed and replicated.
4894  $this->initEditCount( 1 );
4895  } else {
4896  // But if DB_SLAVE is selecting the master, then the
4897  // count we just read includes the revision that was
4898  // just added in the working transaction.
4899  $this->initEditCount();
4900  }
4901  }
4902  // Edit count in user cache too
4903  $this->invalidateCache();
4904  }
4905 
4912  protected function initEditCount( $add = 0 ) {
4913  // Pull from a slave to be less cruel to servers
4914  // Accuracy isn't the point anyway here
4915  $dbr = wfGetDB( DB_SLAVE );
4916  $count = (int)$dbr->selectField(
4917  'revision',
4918  'COUNT(rev_user)',
4919  [ 'rev_user' => $this->getId() ],
4920  __METHOD__
4921  );
4922  $count = $count + $add;
4923 
4924  $dbw = wfGetDB( DB_MASTER );
4925  $dbw->update(
4926  'user',
4927  [ 'user_editcount' => $count ],
4928  [ 'user_id' => $this->getId() ],
4929  __METHOD__
4930  );
4931 
4932  return $count;
4933  }
4934 
4941  public static function getRightDescription( $right ) {
4942  $key = "right-$right";
4943  $msg = wfMessage( $key );
4944  return $msg->isBlank() ? $right : $msg->text();
4945  }
4946 
4956  public static function crypt( $password, $salt = false ) {
4957  wfDeprecated( __METHOD__, '1.24' );
4958  $passwordFactory = new PasswordFactory();
4959  $passwordFactory->init( RequestContext::getMain()->getConfig() );
4960  $hash = $passwordFactory->newFromPlaintext( $password );
4961  return $hash->toString();
4962  }
4963 
4975  public static function comparePasswords( $hash, $password, $userId = false ) {
4976  wfDeprecated( __METHOD__, '1.24' );
4977 
4978  // Check for *really* old password hashes that don't even have a type
4979  // The old hash format was just an md5 hex hash, with no type information
4980  if ( preg_match( '/^[0-9a-f]{32}$/', $hash ) ) {
4981  global $wgPasswordSalt;
4982  if ( $wgPasswordSalt ) {
4983  $password = ":B:{$userId}:{$hash}";
4984  } else {
4985  $password = ":A:{$hash}";
4986  }
4987  }
4988 
4989  $passwordFactory = new PasswordFactory();
4990  $passwordFactory->init( RequestContext::getMain()->getConfig() );
4991  $hash = $passwordFactory->newFromCiphertext( $hash );
4992  return $hash->equals( $password );
4993  }
4994 
5015  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5016  return true; // disabled
5017  }
5018 
5027  public function addNewUserLogEntryAutoCreate() {
5028  $this->addNewUserLogEntry( 'autocreate' );
5029 
5030  return true;
5031  }
5032 
5038  protected function loadOptions( $data = null ) {
5040 
5041  $this->load();
5042 
5043  if ( $this->mOptionsLoaded ) {
5044  return;
5045  }
5046 
5047  $this->mOptions = self::getDefaultOptions();
5048 
5049  if ( !$this->getId() ) {
5050  // For unlogged-in users, load language/variant options from request.
5051  // There's no need to do it for logged-in users: they can set preferences,
5052  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5053  // so don't override user's choice (especially when the user chooses site default).
5054  $variant = $wgContLang->getDefaultVariant();
5055  $this->mOptions['variant'] = $variant;
5056  $this->mOptions['language'] = $variant;
5057  $this->mOptionsLoaded = true;
5058  return;
5059  }
5060 
5061  // Maybe load from the object
5062  if ( !is_null( $this->mOptionOverrides ) ) {
5063  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5064  foreach ( $this->mOptionOverrides as $key => $value ) {
5065  $this->mOptions[$key] = $value;
5066  }
5067  } else {
5068  if ( !is_array( $data ) ) {
5069  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5070  // Load from database
5071  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5072  ? wfGetDB( DB_MASTER )
5073  : wfGetDB( DB_SLAVE );
5074 
5075  $res = $dbr->select(
5076  'user_properties',
5077  [ 'up_property', 'up_value' ],
5078  [ 'up_user' => $this->getId() ],
5079  __METHOD__
5080  );
5081 
5082  $this->mOptionOverrides = [];
5083  $data = [];
5084  foreach ( $res as $row ) {
5085  $data[$row->up_property] = $row->up_value;
5086  }
5087  }
5088  foreach ( $data as $property => $value ) {
5089  $this->mOptionOverrides[$property] = $value;
5090  $this->mOptions[$property] = $value;
5091  }
5092  }
5093 
5094  $this->mOptionsLoaded = true;
5095 
5096  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5097  }
5098 
5104  protected function saveOptions() {
5105  $this->loadOptions();
5106 
5107  // Not using getOptions(), to keep hidden preferences in database
5108  $saveOptions = $this->mOptions;
5109 
5110  // Allow hooks to abort, for instance to save to a global profile.
5111  // Reset options to default state before saving.
5112  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5113  return;
5114  }
5115 
5116  $userId = $this->getId();
5117 
5118  $insert_rows = []; // all the new preference rows
5119  foreach ( $saveOptions as $key => $value ) {
5120  // Don't bother storing default values
5121  $defaultOption = self::getDefaultOption( $key );
5122  if ( ( $defaultOption === null && $value !== false && $value !== null )
5123  || $value != $defaultOption
5124  ) {
5125  $insert_rows[] = [
5126  'up_user' => $userId,
5127  'up_property' => $key,
5128  'up_value' => $value,
5129  ];
5130  }
5131  }
5132 
5133  $dbw = wfGetDB( DB_MASTER );
5134 
5135  $res = $dbw->select( 'user_properties',
5136  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5137 
5138  // Find prior rows that need to be removed or updated. These rows will
5139  // all be deleted (the later so that INSERT IGNORE applies the new values).
5140  $keysDelete = [];
5141  foreach ( $res as $row ) {
5142  if ( !isset( $saveOptions[$row->up_property] )
5143  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5144  ) {
5145  $keysDelete[] = $row->up_property;
5146  }
5147  }
5148 
5149  if ( count( $keysDelete ) ) {
5150  // Do the DELETE by PRIMARY KEY for prior rows.
5151  // In the past a very large portion of calls to this function are for setting
5152  // 'rememberpassword' for new accounts (a preference that has since been removed).
5153  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5154  // caused gap locks on [max user ID,+infinity) which caused high contention since
5155  // updates would pile up on each other as they are for higher (newer) user IDs.
5156  // It might not be necessary these days, but it shouldn't hurt either.
5157  $dbw->delete( 'user_properties',
5158  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5159  }
5160  // Insert the new preference rows
5161  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5162  }
5163 
5170  public static function getPasswordFactory() {
5171  wfDeprecated( __METHOD__, '1.27' );
5172  $ret = new PasswordFactory();
5173  $ret->init( RequestContext::getMain()->getConfig() );
5174  return $ret;
5175  }
5176 
5201  public static function passwordChangeInputAttribs() {
5202  global $wgMinimalPasswordLength;
5203 
5204  if ( $wgMinimalPasswordLength == 0 ) {
5205  return [];
5206  }
5207 
5208  # Note that the pattern requirement will always be satisfied if the
5209  # input is empty, so we need required in all cases.
5210 
5211  # @todo FIXME: Bug 23769: This needs to not claim the password is required
5212  # if e-mail confirmation is being used. Since HTML5 input validation
5213  # is b0rked anyway in some browsers, just return nothing. When it's
5214  # re-enabled, fix this code to not output required for e-mail
5215  # registration.
5216  # $ret = array( 'required' );
5217  $ret = [];
5218 
5219  # We can't actually do this right now, because Opera 9.6 will print out
5220  # the entered password visibly in its error message! When other
5221  # browsers add support for this attribute, or Opera fixes its support,
5222  # we can add support with a version check to avoid doing this on Opera
5223  # versions where it will be a problem. Reported to Opera as
5224  # DSK-262266, but they don't have a public bug tracker for us to follow.
5225  /*
5226  if ( $wgMinimalPasswordLength > 1 ) {
5227  $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}';
5228  $ret['title'] = wfMessage( 'passwordtooshort' )
5229  ->numParams( $wgMinimalPasswordLength )->text();
5230  }
5231  */
5232 
5233  return $ret;
5234  }
5235 
5241  public static function selectFields() {
5242  return [
5243  'user_id',
5244  'user_name',
5245  'user_real_name',
5246  'user_email',
5247  'user_touched',
5248  'user_token',
5249  'user_email_authenticated',
5250  'user_email_token',
5251  'user_email_token_expires',
5252  'user_registration',
5253  'user_editcount',
5254  ];
5255  }
5256 
5264  static function newFatalPermissionDeniedStatus( $permission ) {
5265  global $wgLang;
5266 
5267  $groups = array_map(
5268  [ 'User', 'makeGroupLinkWiki' ],
5269  User::getGroupsWithPermission( $permission )
5270  );
5271 
5272  if ( $groups ) {
5273  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5274  } else {
5275  return Status::newFatal( 'badaccess-group0' );
5276  }
5277  }
5278 
5288  public function getInstanceForUpdate() {
5289  if ( !$this->getId() ) {
5290  return null; // anon
5291  }
5292 
5293  $user = self::newFromId( $this->getId() );
5294  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5295  return null;
5296  }
5297 
5298  return $user;
5299  }
5300 
5308  public function equals( User $user ) {
5309  return $this->getName() === $user->getName();
5310  }
5311 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:568
addAutopromoteOnceGroups($event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1396
getEmail()
Get the user's e-mail address.
Definition: User.php:2627
static randomPassword()
Return a random password.
Definition: User.php:1121
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
isHidden()
Check if user account is hidden.
Definition: User.php:2044
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:750
string $mBlockedby
Definition: User.php:269
const VERSION
int Serialized record version.
Definition: User.php:68
static getMainWANInstance()
Get the main WAN cache object.
setBlocker($user)
Set the user who implemented (or will implement) this block.
Definition: Block.php:1410
Interface for objects which can provide a MediaWiki context on request.
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
isPasswordReminderThrottled()
Has password reminder email been sent within the last $wgPasswordReminderResendTime hours...
Definition: User.php:2619
static newFromRow($row, $data=null)
Create a new user object from a user row.
Definition: User.php:655
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
$wgMaxArticleSize
Maximum article size in kilobytes.
getBoolOption($oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:2805
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2168
isAllowedAll()
Definition: User.php:3393
string $mDatePreference
Definition: User.php:267
the array() calling protocol came about after MediaWiki 1.4rc1.
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4099
static whoIsReal($id)
Get the real name of a user given their user ID.
Definition: User.php:760
wfCanIPUseHTTPS($ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS...
static HashBagOStuff $inProcessCache
An in-process cache for user data lookup.
Definition: User.php:204
matchEditTokenNoSuffix($val, $salt= '', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4221
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
$property
static sanitizeIP($ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:140
static getEditTokenTimestamp($val)
Get the embedded timestamp from a token.
Definition: User.php:4190
isBlockedFrom($title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition: User.php:1934
$context
Definition: load.php:44
checkPassword($password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4109
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1197
clearInstanceCache($reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1486
loadFromSession()
Load user data from the session.
Definition: User.php:1193
const NS_MAIN
Definition: Defines.php:69
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4071
$success
Block $mBlockedFromCreateAccount
Definition: User.php:303
isValidPassword($password)
Is the input a valid password for this user?
Definition: User.php:962
logout()
Log this user out.
Definition: User.php:3727
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3519
isDnsBlacklisted($ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1653
static getImplicitGroups()
Get a list of implicit groups.
Definition: User.php:4673
saveSettings()
Save this user's settings into the database.
Definition: User.php:3765
static isLocallyBlockedProxy($ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1721
load($flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:362
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4480
static $mCoreRights
Array of Strings Core rights.
Definition: User.php:117
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:74
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3378
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4062
static getCanonicalName($name, $validate= 'valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1050
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:1798
setCookie($name, $value, $exp=0, $secure=null, $params=[], $request=null)
Set a cookie on the user's client.
Definition: User.php:3629
getAutomaticGroups($recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3197
pingLimiter($action= 'edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1776
const TOKEN_LENGTH
int Number of characters in user_token field.
Definition: User.php:51
clearSharedCache($mode= 'changed')
Clear user data from memcached.
Definition: User.php:2331
loadFromUserObject($user)
Load the data for this user object from another user object.
Definition: User.php:1356
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2374
deleteNewtalk($field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2258
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
updateNewtalk($field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2233
static getInstance($channel)
Get a named logger instance from the currently configured logger factory.
isBlockedGlobally($ip= '')
Check if user is blocked on all wikis.
Definition: User.php:1986
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:1955
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:220
static generateRandomPasswordString($minLength=10)
Generate a random string suitable for a password.
static getTimestamp($token)
Decode the timestamp from a token string.
Definition: Token.php:61
__toString()
Definition: User.php:327
static isUsableName($name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:895
static $idCacheByName
Definition: User.php:308
null for the local wiki Added in
Definition: hooks.txt:1418
getRealName()
Get the user's real name.
Definition: User.php:2719
checkPasswordValidity($password, $purpose= 'login')
Check if this is a valid password for this user.
Definition: User.php:1010
$value
Check if a user's password complies with any password policies that apply to that user...
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1446
static newFromId($id)
Static factory method for creation from a given user ID.
Definition: User.php:591
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4823
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3585
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3360
static getLocalClusterInstance()
Get the main cluster-local cache object.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2548
saveToCache()
Save user data to the shared cache.
Definition: User.php:525
getTemporaryPassword()
Definition: User.php:2442
checkNewtalk($field, $id)
Internal uncached check for new messages.
Definition: User.php:2218
setPassword($str)
Set the password and reset the random token.
Definition: User.php:2462
static createNew($name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:3864
invalidationTokenUrl($token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4325
addNewUserLogEntry($action=false, $reason= '')
Add a newuser log entry for this user.
Definition: User.php:5015
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:810
setName($str)
Set the user name.
Definition: User.php:2113
array $mFormerGroups
Definition: User.php:281
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2422
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:277
static isIPv6($ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:90
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
static getPreferences($user, IContextSource $context)
Definition: Preferences.php:83
getOptions($flags=0)
Get all user's options.
Definition: User.php:2774
static newFatal($message)
Factory function for fatal errors.
Definition: Status.php:89
matchEditToken($val, $salt= '', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4207
setToken($token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2589
static isIPAddress($ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:79
Multi-datacenter aware caching interface.
Value object representing a logged-out user's edit token.
static makeGroupLinkHTML($group, $text= '')
Create a link to the group in HTML, if available; else return the group name.
Definition: User.php:4708
static makeGroupLinkWiki($group, $text= '')
Create a link to the group in Wikitext, if available; else return the group name. ...
Definition: User.php:4728
setOption($oname, $val)
Set the given option for a user.
Definition: User.php:2833
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2086
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1612
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5241
string $mName
Cache variables.
Definition: User.php:211
string $mRegistration
Cache variables.
Definition: User.php:230
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
setEmailWithConfirmation($str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:2664
int $mEditCount
Cache variables.
Definition: User.php:232
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2524
static isEveryoneAllowed($right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4572
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
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:Associative array mapping language codes to prefixed links of the form"language:title".&$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':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:1796
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2122
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4644
resetTokenFromOption($oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:2882
loadOptions($data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5038
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:2916
getIntOption($oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2817
sendMail($subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4272
static addCallableUpdate($callable, $type=self::POSTSEND)
Add a callable update.
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
string $mEmailTokenExpires
Cache variables.
Definition: User.php:228
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4656
static getInProcessCache()
Definition: User.php:479
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4427
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getRequest()
Get the WebRequest object.
static purge($wikiId, $userId)
Definition: User.php:458
static isValidUserName($name)
Is the input a valid username?
Definition: User.php:846
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:1798
static edits($uid)
Count the number of edits of a user.
Definition: User.php:1109
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4040
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled $incrBy
Definition: hooks.txt:2338
setNewpassword($str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:2609
static crypt($password, $salt=false)
Make a new-style password hash.
Definition: User.php:4956
wfDebugLog($logGroup, $text, $dest= 'all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
loadDefaults($name=false)
Set cached properties to default.
Definition: User.php:1134
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:243
getBlock($bFromSlave=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1922
sendConfirmationMail($type= 'created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4233
string $mEmailToken
Cache variables.
Definition: User.php:226
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
setEmailAuthenticationTimestamp($timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4387
static comparePasswords($hash, $password, $userId=false)
Compare a password hash with a plain-text password.
Definition: User.php:4975
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4466
wfGetLB($wiki=false)
Get a load balancer object.
wfReadOnly()
Check whether the wiki is in read-only mode.
static getMain()
Static methods.
setNewtalk($val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2278
static groupHasPermission($group, $role)
Check, if the given group has the given permission.
Definition: User.php:4552
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:93
Base class for the more common types of database errors.
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5027
getPassword()
Definition: User.php:2433
static changeableByGroup($group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4750
isAllowed($action= '')
Internal mechanics of testing a permission.
Definition: User.php:3408
isItemLoaded($item, $all= 'all')
Return whether an item has been loaded.
Definition: User.php:1172
Block $mBlock
Definition: User.php:297
string $mBlockreason
Definition: User.php:275
isAnon()
Get whether the user is anonymous.
Definition: User.php:3368
inDnsBlacklist($ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1674
if($limit) $timestamp
resetOptions($resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3023
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:1077
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1004
validateCache($timestamp)
Validate the cache for this account.
Definition: User.php:2388
Stores a single person's name and email address.
Definition: MailAddress.php:32
$res
Definition: database.txt:21
static loadFromTimestamp($db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:290
$wgFullyInitialised
Definition: Setup.php:864
$summary
static getSaveBlacklist()
Definition: Preferences.php:73
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3737
integer $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:306
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:492
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4451
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:49
incEditCount()
Deferred version of incEditCountImmediate()
Definition: User.php:4862
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2310
setTarget($target)
Set the target for this block, and update $this->type accordingly.
Definition: Block.php:1394
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:2939
static isIPv4($ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:101
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
static newSystemUser($name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:695
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:218
clearCookie($name, $secure=null, $params=[])
Clear a cookie on the user's client.
Definition: User.php:3650
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4398
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 an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing 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 unsetoffset-wrap String Wrap the message in html(usually something like"&lt
array $mEffectiveGroups
Definition: User.php:277
$cache
Definition: mcc.php:33
const IGNORE_USER_RIGHTS
Definition: User.php:84
$params
string $mEmailAuthenticated
Cache variables.
Definition: User.php:224
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
WebRequest $mRequest
Definition: User.php:294
static makeTitleSafe($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:548
static isIP($name)
Does the string match an anonymous IPv4 address?
Definition: User.php:830
const DB_SLAVE
Definition: Defines.php:46
getBlockedStatus($bFromSlave=true)
Get blocking information.
Definition: User.php:1560
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:35
static getGroupPage($group)
Get the title of a page describing a particular group.
Definition: User.php:4689
getPasswordValidity($password)
Given unvalidated password input, return error message on failure.
Definition: User.php:973
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3088
array $mImplicitGroups
Definition: User.php:279
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:198
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
static getDBOptions($bitfield)
Get an appropriate DB index and options for a query.
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
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from...
Definition: User.php:4021
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:59
$wgClockSkewFudge
Clock skew or the one-second resolution of time() can occasionally cause cache problems when the user...
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
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:1584
getTokenUrl($page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4343
setInternalPassword($str)
Set the password and reset the random token unconditionally.
Definition: User.php:2474
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:2907
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:912
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
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:762
loadFromDatabase($flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1224
string $mToken
Cache variables.
Definition: User.php:222
isWatched($title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3472
static newInvalidPassword()
Create an InvalidPassword.
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 local account $user
Definition: hooks.txt:242
static link($target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:195
static normalizeKey($key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:93
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3421
$wgPasswordSender
Sender email address for e-mail notifications.
isBlocked($bFromSlave=true)
Check if user is blocked.
Definition: User.php:1912
array $mOptions
Definition: User.php:289
getOption($oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2746
static getGroupName($group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:4621
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:265
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4373
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1366
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5104
idForName($flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:3829
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
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:5201
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:99
setId($v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2077
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3107
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3455
$from
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
string $mEmail
Cache variables.
Definition: User.php:216
setExtendedLoginCookie($name, $value, $secure)
Set an extended login cookie on the user's client.
Definition: User.php:3670
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition: Block.php:1117
this hook is for auditing only $req
Definition: hooks.txt:965
getNewtalk()
Check if the user has new messages.
Definition: User.php:2130
bool $mLocked
Definition: User.php:285
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:762
static newFromConfirmationCode($code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:610
loadFromRow($row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1271
$wgUseEnotif
Definition: Setup.php:361
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3163
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:320
static getGroupMember($group, $username= '#')
Get the localized descriptive name for a member of a group, if it exists.
Definition: User.php:4633
array $mGroups
Cache variables.
Definition: User.php:234
confirmationTokenUrl($token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4316
Class for creating log entries manually, to inject them into the database.
Definition: LogEntry.php:394
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2418
removeGroup($group)
Remove the user from the given group.
Definition: User.php:3320
prevents($action, $x=null)
Get/set whether the Block prevents a given action.
Definition: Block.php:977
static hasFlags($bitfield, $flags)
The ContentHandler facility adds support for arbitrary content types on wiki pages
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from...
Definition: User.php:4008
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3442
static hmac($data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
getEffectiveGroups($recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3176
getId()
Get the user's ID.
Definition: User.php:2061
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2637
addToDatabase()
Add this existing user object to the database.
Definition: User.php:3935
bool $mAllowUsertalk
Definition: User.php:300
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2357
getTokenFromOption($oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:2854
removeWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3503
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4356
getGlobalBlock($ip= '')
Check if user is blocked on all wikis.
Definition: User.php:2000
static generateHex($chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format...
setEmail($str)
Set the user's e-mail address.
Definition: User.php:2647
initEditCount($add=0)
Initialize user_editcount from data out of the revision table.
Definition: User.php:4912
bool $mHideName
Definition: User.php:287
static getPasswordFactory()
Lazily instantiate and return a factory object for making passwords.
Definition: User.php:5170
static isCreatableName($name)
Usernames which fail to pass this function will be blocked from new account registrations, but may be used internally either by batch processes or by user accounts which have already been created.
Definition: User.php:932
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:634
getEditCount()
Get the user's edit count.
Definition: User.php:3250
getUserPage()
Get this user's personal page title.
Definition: User.php:4080
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5288
static idFromName($name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:770
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3430
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired 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 inclusive $limit
Definition: hooks.txt:1004
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 local content language as $wgContLang
Definition: design.txt:56
Interface for database access objects.
setCookies($request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:3691
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3068
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:1973
array $mRights
Definition: User.php:273
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2191
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:260
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1004
static consume(ScopedCallback &$sc=null)
Trigger a scoped callback and destroy it.
$count
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:4180
static newFatalPermissionDeniedStatus($permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5264
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:4158
isLocked()
Check if user account is locked.
Definition: User.php:2029
$messages
wfMemcKey()
Make a cache key for the local wiki.
string $mHash
Definition: User.php:271
const DB_MASTER
Definition: Defines.php:47
static validateEmail($addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1904
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4413
equals(User $user)
Checks if two user objects point to the same user.
Definition: User.php:5308
loadFromId($flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:425
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
setRealName($str)
Set the user's real name.
Definition: User.php:2731
addWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3486
array $mOptionOverrides
Cache variables.
Definition: User.php:236
checkTemporaryPassword($plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4142
static logException($e)
Log an exception to the exception log (if enabled).
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1511
const INVALID_TOKEN
string An invalid value for user_token
Definition: User.php:56
setItemLoaded($item)
Set that an item has been loaded.
Definition: User.php:1182
wfTimestampOrNull($outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
static getDefaultOption($opt)
Get a given default option value.
Definition: User.php:1545
string $mRealName
Cache variables.
Definition: User.php:213
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:345
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
static getSubnet($ip)
Returns the subnet of a given IP.
Definition: IP.php:777
getCacheKey(WANObjectCache $cache)
Definition: User.php:471
static getGroupPermissions($groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4502
const NS_USER_TALK
Definition: Defines.php:72
static array $languagesWithVariants
languages supporting variants
getTalkPage()
Get this user's talk page title.
Definition: User.php:4089
static flattenOptions($options)
flatten an array of options to a single array, for instance, a set of "" inside "...
getToken($forceCreation=true)
Get the user's current token.
Definition: User.php:2551
const EDIT_TOKEN_SUFFIX
Global constant made accessible as class constants so that autoloader magic can be used...
Definition: User.php:63
Definition: Block.php:22
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1751
static getRightDescription($right)
Get the description of a given right.
Definition: User.php:4941
static singleton()
Definition: UserCache.php:34
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk page
Definition: hooks.txt:2338
if(is_null($wgLocalTZoffset)) if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:657
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2338
if($wgRCFilterByAge) $wgDefaultUserOptions['rcdays']
Definition: Setup.php:281
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:92
const CHECK_USER_RIGHTS
Definition: User.php:79
static & makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:524
static getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4529
static newGood($value=null)
Factory function for good results.
Definition: Status.php:101
getRights()
Get the permissions this user has.
Definition: User.php:3122
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3226
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:1964
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2338
setPasswordInternal($str)
Actually set the password and such.
Definition: User.php:2486
incEditCountImmediate()
Increment the user's edit-count field.
Definition: User.php:4873
int $mId
Cache variables.
Definition: User.php:209
addGroup($group)
Add the user to the given group.
Definition: User.php:3280
$wgUser
Definition: Setup.php:794
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4298
getTouched()
Get the user touched timestamp.
Definition: User.php:2400
Block $mGlobalBlock
Definition: User.php:283
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:248
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310