MediaWiki  1.27.1
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 $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  $user->loadGroups();
1359  $user->loadOptions();
1360  foreach ( self::$mCacheVars as $var ) {
1361  $this->$var = $user->$var;
1362  }
1363  }
1364 
1368  private function loadGroups() {
1369  if ( is_null( $this->mGroups ) ) {
1370  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1371  ? wfGetDB( DB_MASTER )
1372  : wfGetDB( DB_SLAVE );
1373  $res = $db->select( 'user_groups',
1374  [ 'ug_group' ],
1375  [ 'ug_user' => $this->mId ],
1376  __METHOD__ );
1377  $this->mGroups = [];
1378  foreach ( $res as $row ) {
1379  $this->mGroups[] = $row->ug_group;
1380  }
1381  }
1382  }
1383 
1398  public function addAutopromoteOnceGroups( $event ) {
1399  global $wgAutopromoteOnceLogInRC;
1400 
1401  if ( wfReadOnly() || !$this->getId() ) {
1402  return [];
1403  }
1404 
1405  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1406  if ( !count( $toPromote ) ) {
1407  return [];
1408  }
1409 
1410  if ( !$this->checkAndSetTouched() ) {
1411  return []; // raced out (bug T48834)
1412  }
1413 
1414  $oldGroups = $this->getGroups(); // previous groups
1415  foreach ( $toPromote as $group ) {
1416  $this->addGroup( $group );
1417  }
1418  // update groups in external authentication database
1419  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false ] );
1420  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1421 
1422  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1423 
1424  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1425  $logEntry->setPerformer( $this );
1426  $logEntry->setTarget( $this->getUserPage() );
1427  $logEntry->setParameters( [
1428  '4::oldgroups' => $oldGroups,
1429  '5::newgroups' => $newGroups,
1430  ] );
1431  $logid = $logEntry->insert();
1432  if ( $wgAutopromoteOnceLogInRC ) {
1433  $logEntry->publish( $logid );
1434  }
1435 
1436  return $toPromote;
1437  }
1438 
1448  protected function checkAndSetTouched() {
1449  $this->load();
1450 
1451  if ( !$this->mId ) {
1452  return false; // anon
1453  }
1454 
1455  // Get a new user_touched that is higher than the old one
1456  $oldTouched = $this->mTouched;
1457  $newTouched = $this->newTouchedTimestamp();
1458 
1459  $dbw = wfGetDB( DB_MASTER );
1460  $dbw->update( 'user',
1461  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1462  [
1463  'user_id' => $this->mId,
1464  'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
1465  ],
1466  __METHOD__
1467  );
1468  $success = ( $dbw->affectedRows() > 0 );
1469 
1470  if ( $success ) {
1471  $this->mTouched = $newTouched;
1472  $this->clearSharedCache();
1473  } else {
1474  // Clears on failure too since that is desired if the cache is stale
1475  $this->clearSharedCache( 'refresh' );
1476  }
1477 
1478  return $success;
1479  }
1480 
1488  public function clearInstanceCache( $reloadFrom = false ) {
1489  $this->mNewtalk = -1;
1490  $this->mDatePreference = null;
1491  $this->mBlockedby = -1; # Unset
1492  $this->mHash = false;
1493  $this->mRights = null;
1494  $this->mEffectiveGroups = null;
1495  $this->mImplicitGroups = null;
1496  $this->mGroups = null;
1497  $this->mOptions = null;
1498  $this->mOptionsLoaded = false;
1499  $this->mEditCount = null;
1500 
1501  if ( $reloadFrom ) {
1502  $this->mLoadedItems = [];
1503  $this->mFrom = $reloadFrom;
1504  }
1505  }
1506 
1513  public static function getDefaultOptions() {
1514  global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
1515 
1516  static $defOpt = null;
1517  if ( !defined( 'MW_PHPUNIT_TEST' ) && $defOpt !== null ) {
1518  // Disabling this for the unit tests, as they rely on being able to change $wgContLang
1519  // mid-request and see that change reflected in the return value of this function.
1520  // Which is insane and would never happen during normal MW operation
1521  return $defOpt;
1522  }
1523 
1524  $defOpt = $wgDefaultUserOptions;
1525  // Default language setting
1526  $defOpt['language'] = $wgContLang->getCode();
1527  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1528  $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
1529  }
1530  $namespaces = MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
1531  foreach ( $namespaces as $nsnum => $nsname ) {
1532  $defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
1533  }
1534  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1535 
1536  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1537 
1538  return $defOpt;
1539  }
1540 
1547  public static function getDefaultOption( $opt ) {
1548  $defOpts = self::getDefaultOptions();
1549  if ( isset( $defOpts[$opt] ) ) {
1550  return $defOpts[$opt];
1551  } else {
1552  return null;
1553  }
1554  }
1555 
1562  private function getBlockedStatus( $bFromSlave = true ) {
1563  global $wgProxyWhitelist, $wgUser, $wgApplyIpBlocksToXff;
1564 
1565  if ( -1 != $this->mBlockedby ) {
1566  return;
1567  }
1568 
1569  wfDebug( __METHOD__ . ": checking...\n" );
1570 
1571  // Initialize data...
1572  // Otherwise something ends up stomping on $this->mBlockedby when
1573  // things get lazy-loaded later, causing false positive block hits
1574  // due to -1 !== 0. Probably session-related... Nothing should be
1575  // overwriting mBlockedby, surely?
1576  $this->load();
1577 
1578  # We only need to worry about passing the IP address to the Block generator if the
1579  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1580  # know which IP address they're actually coming from
1581  $ip = null;
1582  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1583  // $wgUser->getName() only works after the end of Setup.php. Until
1584  // then, assume it's a logged-out user.
1585  $globalUserName = $wgUser->isSafeToLoad()
1586  ? $wgUser->getName()
1587  : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1588  if ( $this->getName() === $globalUserName ) {
1589  $ip = $this->getRequest()->getIP();
1590  }
1591  }
1592 
1593  // User/IP blocking
1594  $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1595 
1596  // Proxy blocking
1597  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1598  // Local list
1599  if ( self::isLocallyBlockedProxy( $ip ) ) {
1600  $block = new Block;
1601  $block->setBlocker( wfMessage( 'proxyblocker' )->text() );
1602  $block->mReason = wfMessage( 'proxyblockreason' )->text();
1603  $block->setTarget( $ip );
1604  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1605  $block = new Block;
1606  $block->setBlocker( wfMessage( 'sorbs' )->text() );
1607  $block->mReason = wfMessage( 'sorbsreason' )->text();
1608  $block->setTarget( $ip );
1609  }
1610  }
1611 
1612  // (bug 23343) Apply IP blocks to the contents of XFF headers, if enabled
1613  if ( !$block instanceof Block
1614  && $wgApplyIpBlocksToXff
1615  && $ip !== null
1616  && !in_array( $ip, $wgProxyWhitelist )
1617  ) {
1618  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1619  $xff = array_map( 'trim', explode( ',', $xff ) );
1620  $xff = array_diff( $xff, [ $ip ] );
1621  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1622  $block = Block::chooseBlock( $xffblocks, $xff );
1623  if ( $block instanceof Block ) {
1624  # Mangle the reason to alert the user that the block
1625  # originated from matching the X-Forwarded-For header.
1626  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
1627  }
1628  }
1629 
1630  if ( $block instanceof Block ) {
1631  wfDebug( __METHOD__ . ": Found block.\n" );
1632  $this->mBlock = $block;
1633  $this->mBlockedby = $block->getByName();
1634  $this->mBlockreason = $block->mReason;
1635  $this->mHideName = $block->mHideName;
1636  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1637  } else {
1638  $this->mBlockedby = '';
1639  $this->mHideName = 0;
1640  $this->mAllowUsertalk = false;
1641  }
1642 
1643  // Extensions
1644  Hooks::run( 'GetBlockedStatus', [ &$this ] );
1645 
1646  }
1647 
1655  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1656  global $wgEnableDnsBlacklist, $wgDnsBlacklistUrls, $wgProxyWhitelist;
1657 
1658  if ( !$wgEnableDnsBlacklist ) {
1659  return false;
1660  }
1661 
1662  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
1663  return false;
1664  }
1665 
1666  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
1667  }
1668 
1676  public function inDnsBlacklist( $ip, $bases ) {
1677 
1678  $found = false;
1679  // @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
1680  if ( IP::isIPv4( $ip ) ) {
1681  // Reverse IP, bug 21255
1682  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1683 
1684  foreach ( (array)$bases as $base ) {
1685  // Make hostname
1686  // If we have an access key, use that too (ProjectHoneypot, etc.)
1687  $basename = $base;
1688  if ( is_array( $base ) ) {
1689  if ( count( $base ) >= 2 ) {
1690  // Access key is 1, base URL is 0
1691  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1692  } else {
1693  $host = "$ipReversed.{$base[0]}";
1694  }
1695  $basename = $base[0];
1696  } else {
1697  $host = "$ipReversed.$base";
1698  }
1699 
1700  // Send query
1701  $ipList = gethostbynamel( $host );
1702 
1703  if ( $ipList ) {
1704  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1705  $found = true;
1706  break;
1707  } else {
1708  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1709  }
1710  }
1711  }
1712 
1713  return $found;
1714  }
1715 
1723  public static function isLocallyBlockedProxy( $ip ) {
1724  global $wgProxyList;
1725 
1726  if ( !$wgProxyList ) {
1727  return false;
1728  }
1729 
1730  if ( !is_array( $wgProxyList ) ) {
1731  // Load from the specified file
1732  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1733  }
1734 
1735  if ( !is_array( $wgProxyList ) ) {
1736  $ret = false;
1737  } elseif ( array_search( $ip, $wgProxyList ) !== false ) {
1738  $ret = true;
1739  } elseif ( array_key_exists( $ip, $wgProxyList ) ) {
1740  // Old-style flipped proxy list
1741  $ret = true;
1742  } else {
1743  $ret = false;
1744  }
1745  return $ret;
1746  }
1747 
1753  public function isPingLimitable() {
1754  global $wgRateLimitsExcludedIPs;
1755  if ( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1756  // No other good way currently to disable rate limits
1757  // for specific IPs. :P
1758  // But this is a crappy hack and should die.
1759  return false;
1760  }
1761  return !$this->isAllowed( 'noratelimit' );
1762  }
1763 
1778  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1779  // Call the 'PingLimiter' hook
1780  $result = false;
1781  if ( !Hooks::run( 'PingLimiter', [ &$this, $action, &$result, $incrBy ] ) ) {
1782  return $result;
1783  }
1784 
1785  global $wgRateLimits;
1786  if ( !isset( $wgRateLimits[$action] ) ) {
1787  return false;
1788  }
1789 
1790  // Some groups shouldn't trigger the ping limiter, ever
1791  if ( !$this->isPingLimitable() ) {
1792  return false;
1793  }
1794 
1795  $limits = $wgRateLimits[$action];
1796  $keys = [];
1797  $id = $this->getId();
1798  $userLimit = false;
1799  $isNewbie = $this->isNewbie();
1800 
1801  if ( $id == 0 ) {
1802  // limits for anons
1803  if ( isset( $limits['anon'] ) ) {
1804  $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1805  }
1806  } else {
1807  // limits for logged-in users
1808  if ( isset( $limits['user'] ) ) {
1809  $userLimit = $limits['user'];
1810  }
1811  // limits for newbie logged-in users
1812  if ( $isNewbie && isset( $limits['newbie'] ) ) {
1813  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
1814  }
1815  }
1816 
1817  // limits for anons and for newbie logged-in users
1818  if ( $isNewbie ) {
1819  // ip-based limits
1820  if ( isset( $limits['ip'] ) ) {
1821  $ip = $this->getRequest()->getIP();
1822  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1823  }
1824  // subnet-based limits
1825  if ( isset( $limits['subnet'] ) ) {
1826  $ip = $this->getRequest()->getIP();
1827  $subnet = IP::getSubnet( $ip );
1828  if ( $subnet !== false ) {
1829  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1830  }
1831  }
1832  }
1833 
1834  // Check for group-specific permissions
1835  // If more than one group applies, use the group with the highest limit ratio (max/period)
1836  foreach ( $this->getGroups() as $group ) {
1837  if ( isset( $limits[$group] ) ) {
1838  if ( $userLimit === false
1839  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1840  ) {
1841  $userLimit = $limits[$group];
1842  }
1843  }
1844  }
1845 
1846  // Set the user limit key
1847  if ( $userLimit !== false ) {
1848  list( $max, $period ) = $userLimit;
1849  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
1850  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit;
1851  }
1852 
1853  // ip-based limits for all ping-limitable users
1854  if ( isset( $limits['ip-all'] ) ) {
1855  $ip = $this->getRequest()->getIP();
1856  // ignore if user limit is more permissive
1857  if ( $isNewbie || $userLimit === false
1858  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1859  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
1860  }
1861  }
1862 
1863  // subnet-based limits for all ping-limitable users
1864  if ( isset( $limits['subnet-all'] ) ) {
1865  $ip = $this->getRequest()->getIP();
1866  $subnet = IP::getSubnet( $ip );
1867  if ( $subnet !== false ) {
1868  // ignore if user limit is more permissive
1869  if ( $isNewbie || $userLimit === false
1870  || $limits['ip-all'][0] / $limits['ip-all'][1]
1871  > $userLimit[0] / $userLimit[1] ) {
1872  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
1873  }
1874  }
1875  }
1876 
1878 
1879  $triggered = false;
1880  foreach ( $keys as $key => $limit ) {
1881  list( $max, $period ) = $limit;
1882  $summary = "(limit $max in {$period}s)";
1883  $count = $cache->get( $key );
1884  // Already pinged?
1885  if ( $count ) {
1886  if ( $count >= $max ) {
1887  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
1888  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
1889  $triggered = true;
1890  } else {
1891  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
1892  }
1893  } else {
1894  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
1895  if ( $incrBy > 0 ) {
1896  $cache->add( $key, 0, intval( $period ) ); // first ping
1897  }
1898  }
1899  if ( $incrBy > 0 ) {
1900  $cache->incr( $key, $incrBy );
1901  }
1902  }
1903 
1904  return $triggered;
1905  }
1906 
1914  public function isBlocked( $bFromSlave = true ) {
1915  return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
1916  }
1917 
1924  public function getBlock( $bFromSlave = true ) {
1925  $this->getBlockedStatus( $bFromSlave );
1926  return $this->mBlock instanceof Block ? $this->mBlock : null;
1927  }
1928 
1936  public function isBlockedFrom( $title, $bFromSlave = false ) {
1937  global $wgBlockAllowsUTEdit;
1938 
1939  $blocked = $this->isBlocked( $bFromSlave );
1940  $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
1941  // If a user's name is suppressed, they cannot make edits anywhere
1942  if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
1943  && $title->getNamespace() == NS_USER_TALK ) {
1944  $blocked = false;
1945  wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
1946  }
1947 
1948  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
1949 
1950  return $blocked;
1951  }
1952 
1957  public function blockedBy() {
1958  $this->getBlockedStatus();
1959  return $this->mBlockedby;
1960  }
1961 
1966  public function blockedFor() {
1967  $this->getBlockedStatus();
1968  return $this->mBlockreason;
1969  }
1970 
1975  public function getBlockId() {
1976  $this->getBlockedStatus();
1977  return ( $this->mBlock ? $this->mBlock->getId() : false );
1978  }
1979 
1988  public function isBlockedGlobally( $ip = '' ) {
1989  return $this->getGlobalBlock( $ip ) instanceof Block;
1990  }
1991 
2002  public function getGlobalBlock( $ip = '' ) {
2003  if ( $this->mGlobalBlock !== null ) {
2004  return $this->mGlobalBlock ?: null;
2005  }
2006  // User is already an IP?
2007  if ( IP::isIPAddress( $this->getName() ) ) {
2008  $ip = $this->getName();
2009  } elseif ( !$ip ) {
2010  $ip = $this->getRequest()->getIP();
2011  }
2012  $blocked = false;
2013  $block = null;
2014  Hooks::run( 'UserIsBlockedGlobally', [ &$this, $ip, &$blocked, &$block ] );
2015 
2016  if ( $blocked && $block === null ) {
2017  // back-compat: UserIsBlockedGlobally didn't have $block param first
2018  $block = new Block;
2019  $block->setTarget( $ip );
2020  }
2021 
2022  $this->mGlobalBlock = $blocked ? $block : false;
2023  return $this->mGlobalBlock ?: null;
2024  }
2025 
2031  public function isLocked() {
2032  if ( $this->mLocked !== null ) {
2033  return $this->mLocked;
2034  }
2035  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2036  $this->mLocked = $authUser && $authUser->isLocked();
2037  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2038  return $this->mLocked;
2039  }
2040 
2046  public function isHidden() {
2047  if ( $this->mHideName !== null ) {
2048  return $this->mHideName;
2049  }
2050  $this->getBlockedStatus();
2051  if ( !$this->mHideName ) {
2052  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2053  $this->mHideName = $authUser && $authUser->isHidden();
2054  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2055  }
2056  return $this->mHideName;
2057  }
2058 
2063  public function getId() {
2064  if ( $this->mId === null && $this->mName !== null && User::isIP( $this->mName ) ) {
2065  // Special case, we know the user is anonymous
2066  return 0;
2067  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2068  // Don't load if this was initialized from an ID
2069  $this->load();
2070  }
2071 
2072  return (int)$this->mId;
2073  }
2074 
2079  public function setId( $v ) {
2080  $this->mId = $v;
2081  $this->clearInstanceCache( 'id' );
2082  }
2083 
2088  public function getName() {
2089  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2090  // Special case optimisation
2091  return $this->mName;
2092  } else {
2093  $this->load();
2094  if ( $this->mName === false ) {
2095  // Clean up IPs
2096  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2097  }
2098  return $this->mName;
2099  }
2100  }
2101 
2115  public function setName( $str ) {
2116  $this->load();
2117  $this->mName = $str;
2118  }
2119 
2124  public function getTitleKey() {
2125  return str_replace( ' ', '_', $this->getName() );
2126  }
2127 
2132  public function getNewtalk() {
2133  $this->load();
2134 
2135  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2136  if ( $this->mNewtalk === -1 ) {
2137  $this->mNewtalk = false; # reset talk page status
2138 
2139  // Check memcached separately for anons, who have no
2140  // entire User object stored in there.
2141  if ( !$this->mId ) {
2142  global $wgDisableAnonTalk;
2143  if ( $wgDisableAnonTalk ) {
2144  // Anon newtalk disabled by configuration.
2145  $this->mNewtalk = false;
2146  } else {
2147  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2148  }
2149  } else {
2150  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2151  }
2152  }
2153 
2154  return (bool)$this->mNewtalk;
2155  }
2156 
2170  public function getNewMessageLinks() {
2171  $talks = [];
2172  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$this, &$talks ] ) ) {
2173  return $talks;
2174  } elseif ( !$this->getNewtalk() ) {
2175  return [];
2176  }
2177  $utp = $this->getTalkPage();
2178  $dbr = wfGetDB( DB_SLAVE );
2179  // Get the "last viewed rev" timestamp from the oldest message notification
2180  $timestamp = $dbr->selectField( 'user_newtalk',
2181  'MIN(user_last_timestamp)',
2182  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2183  __METHOD__ );
2185  return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2186  }
2187 
2193  public function getNewMessageRevisionId() {
2194  $newMessageRevisionId = null;
2195  $newMessageLinks = $this->getNewMessageLinks();
2196  if ( $newMessageLinks ) {
2197  // Note: getNewMessageLinks() never returns more than a single link
2198  // and it is always for the same wiki, but we double-check here in
2199  // case that changes some time in the future.
2200  if ( count( $newMessageLinks ) === 1
2201  && $newMessageLinks[0]['wiki'] === wfWikiID()
2202  && $newMessageLinks[0]['rev']
2203  ) {
2205  $newMessageRevision = $newMessageLinks[0]['rev'];
2206  $newMessageRevisionId = $newMessageRevision->getId();
2207  }
2208  }
2209  return $newMessageRevisionId;
2210  }
2211 
2220  protected function checkNewtalk( $field, $id ) {
2221  $dbr = wfGetDB( DB_SLAVE );
2222 
2223  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2224 
2225  return $ok !== false;
2226  }
2227 
2235  protected function updateNewtalk( $field, $id, $curRev = null ) {
2236  // Get timestamp of the talk page revision prior to the current one
2237  $prevRev = $curRev ? $curRev->getPrevious() : false;
2238  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2239  // Mark the user as having new messages since this revision
2240  $dbw = wfGetDB( DB_MASTER );
2241  $dbw->insert( 'user_newtalk',
2242  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2243  __METHOD__,
2244  'IGNORE' );
2245  if ( $dbw->affectedRows() ) {
2246  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2247  return true;
2248  } else {
2249  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2250  return false;
2251  }
2252  }
2253 
2260  protected function deleteNewtalk( $field, $id ) {
2261  $dbw = wfGetDB( DB_MASTER );
2262  $dbw->delete( 'user_newtalk',
2263  [ $field => $id ],
2264  __METHOD__ );
2265  if ( $dbw->affectedRows() ) {
2266  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2267  return true;
2268  } else {
2269  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2270  return false;
2271  }
2272  }
2273 
2280  public function setNewtalk( $val, $curRev = null ) {
2281  if ( wfReadOnly() ) {
2282  return;
2283  }
2284 
2285  $this->load();
2286  $this->mNewtalk = $val;
2287 
2288  if ( $this->isAnon() ) {
2289  $field = 'user_ip';
2290  $id = $this->getName();
2291  } else {
2292  $field = 'user_id';
2293  $id = $this->getId();
2294  }
2295 
2296  if ( $val ) {
2297  $changed = $this->updateNewtalk( $field, $id, $curRev );
2298  } else {
2299  $changed = $this->deleteNewtalk( $field, $id );
2300  }
2301 
2302  if ( $changed ) {
2303  $this->invalidateCache();
2304  }
2305  }
2306 
2312  private function newTouchedTimestamp() {
2314 
2315  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2316  if ( $this->mTouched && $time <= $this->mTouched ) {
2317  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2318  }
2319 
2320  return $time;
2321  }
2322 
2333  public function clearSharedCache( $mode = 'changed' ) {
2334  if ( !$this->getId() ) {
2335  return;
2336  }
2337 
2339  $processCache = self::getInProcessCache();
2340  $key = $this->getCacheKey( $cache );
2341  if ( $mode === 'refresh' ) {
2342  $cache->delete( $key, 1 );
2343  $processCache->delete( $key );
2344  } else {
2345  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
2346  function() use ( $cache, $processCache, $key ) {
2347  $cache->delete( $key );
2348  $processCache->delete( $key );
2349  }
2350  );
2351  }
2352  }
2353 
2359  public function invalidateCache() {
2360  $this->touch();
2361  $this->clearSharedCache();
2362  }
2363 
2376  public function touch() {
2377  $id = $this->getId();
2378  if ( $id ) {
2379  $key = wfMemcKey( 'user-quicktouched', 'id', $id );
2380  ObjectCache::getMainWANInstance()->touchCheckKey( $key );
2381  $this->mQuickTouched = null;
2382  }
2383  }
2384 
2390  public function validateCache( $timestamp ) {
2391  return ( $timestamp >= $this->getTouched() );
2392  }
2393 
2402  public function getTouched() {
2403  $this->load();
2404 
2405  if ( $this->mId ) {
2406  if ( $this->mQuickTouched === null ) {
2407  $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
2409 
2410  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2411  }
2412 
2413  return max( $this->mTouched, $this->mQuickTouched );
2414  }
2415 
2416  return $this->mTouched;
2417  }
2418 
2424  public function getDBTouched() {
2425  $this->load();
2426 
2427  return $this->mTouched;
2428  }
2429 
2435  public function getPassword() {
2436  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2437  }
2438 
2444  public function getTemporaryPassword() {
2445  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2446  }
2447 
2464  public function setPassword( $str ) {
2465  return $this->setPasswordInternal( $str );
2466  }
2467 
2476  public function setInternalPassword( $str ) {
2477  $this->setPasswordInternal( $str );
2478  }
2479 
2488  private function setPasswordInternal( $str ) {
2489  $manager = AuthManager::singleton();
2490 
2491  // If the user doesn't exist yet, fail
2492  if ( !$manager->userExists( $this->getName() ) ) {
2493  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2494  }
2495 
2496  $status = $this->changeAuthenticationData( [
2497  'username' => $this->getName(),
2498  'password' => $str,
2499  'retype' => $str,
2500  ] );
2501  if ( !$status->isGood() ) {
2503  ->info( __METHOD__ . ': Password change rejected: '
2504  . $status->getWikiText( null, null, 'en' ) );
2505  return false;
2506  }
2507 
2508  $this->setOption( 'watchlisttoken', false );
2509  SessionManager::singleton()->invalidateSessionsForUser( $this );
2510 
2511  return true;
2512  }
2513 
2526  public function changeAuthenticationData( array $data ) {
2527  $manager = AuthManager::singleton();
2528  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2529  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2530 
2531  $status = Status::newGood( 'ignored' );
2532  foreach ( $reqs as $req ) {
2533  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2534  }
2535  if ( $status->getValue() === 'ignored' ) {
2536  $status->warning( 'authenticationdatachange-ignored' );
2537  }
2538 
2539  if ( $status->isGood() ) {
2540  foreach ( $reqs as $req ) {
2541  $manager->changeAuthenticationData( $req );
2542  }
2543  }
2544  return $status;
2545  }
2546 
2553  public function getToken( $forceCreation = true ) {
2554  global $wgAuthenticationTokenVersion;
2555 
2556  $this->load();
2557  if ( !$this->mToken && $forceCreation ) {
2558  $this->setToken();
2559  }
2560 
2561  if ( !$this->mToken ) {
2562  // The user doesn't have a token, return null to indicate that.
2563  return null;
2564  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2565  // We return a random value here so existing token checks are very
2566  // likely to fail.
2567  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2568  } elseif ( $wgAuthenticationTokenVersion === null ) {
2569  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2570  return $this->mToken;
2571  } else {
2572  // $wgAuthenticationTokenVersion in use, so hmac it.
2573  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2574 
2575  // The raw hash can be overly long. Shorten it up.
2576  $len = max( 32, self::TOKEN_LENGTH );
2577  if ( strlen( $ret ) < $len ) {
2578  // Should never happen, even md5 is 128 bits
2579  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2580  }
2581  return substr( $ret, -$len );
2582  }
2583  }
2584 
2591  public function setToken( $token = false ) {
2592  $this->load();
2593  if ( $this->mToken === self::INVALID_TOKEN ) {
2595  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2596  } elseif ( !$token ) {
2597  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2598  } else {
2599  $this->mToken = $token;
2600  }
2601  }
2602 
2611  public function setNewpassword( $str, $throttle = true ) {
2612  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2613  }
2614 
2621  public function isPasswordReminderThrottled() {
2622  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2623  }
2624 
2629  public function getEmail() {
2630  $this->load();
2631  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2632  return $this->mEmail;
2633  }
2634 
2640  $this->load();
2641  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2643  }
2644 
2649  public function setEmail( $str ) {
2650  $this->load();
2651  if ( $str == $this->mEmail ) {
2652  return;
2653  }
2654  $this->invalidateEmail();
2655  $this->mEmail = $str;
2656  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2657  }
2658 
2666  public function setEmailWithConfirmation( $str ) {
2668 
2669  if ( !$wgEnableEmail ) {
2670  return Status::newFatal( 'emaildisabled' );
2671  }
2672 
2673  $oldaddr = $this->getEmail();
2674  if ( $str === $oldaddr ) {
2675  return Status::newGood( true );
2676  }
2677 
2678  $type = $oldaddr != '' ? 'changed' : 'set';
2679  $notificationResult = null;
2680 
2681  if ( $wgEmailAuthentication ) {
2682  // Send the user an email notifying the user of the change in registered
2683  // email address on their previous email address
2684  if ( $type == 'changed' ) {
2685  $change = $str != '' ? 'changed' : 'removed';
2686  $notificationResult = $this->sendMail(
2687  wfMessage( 'notificationemail_subject_' . $change )->text(),
2688  wfMessage( 'notificationemail_body_' . $change,
2689  $this->getRequest()->getIP(),
2690  $this->getName(),
2691  $str )->text()
2692  );
2693  }
2694  }
2695 
2696  $this->setEmail( $str );
2697 
2698  if ( $str !== '' && $wgEmailAuthentication ) {
2699  // Send a confirmation request to the new address if needed
2700  $result = $this->sendConfirmationMail( $type );
2701 
2702  if ( $notificationResult !== null ) {
2703  $result->merge( $notificationResult );
2704  }
2705 
2706  if ( $result->isGood() ) {
2707  // Say to the caller that a confirmation and notification mail has been sent
2708  $result->value = 'eauth';
2709  }
2710  } else {
2711  $result = Status::newGood( true );
2712  }
2713 
2714  return $result;
2715  }
2716 
2721  public function getRealName() {
2722  if ( !$this->isItemLoaded( 'realname' ) ) {
2723  $this->load();
2724  }
2725 
2726  return $this->mRealName;
2727  }
2728 
2733  public function setRealName( $str ) {
2734  $this->load();
2735  $this->mRealName = $str;
2736  }
2737 
2748  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2749  global $wgHiddenPrefs;
2750  $this->loadOptions();
2751 
2752  # We want 'disabled' preferences to always behave as the default value for
2753  # users, even if they have set the option explicitly in their settings (ie they
2754  # set it, and then it was disabled removing their ability to change it). But
2755  # we don't want to erase the preferences in the database in case the preference
2756  # is re-enabled again. So don't touch $mOptions, just override the returned value
2757  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2758  return self::getDefaultOption( $oname );
2759  }
2760 
2761  if ( array_key_exists( $oname, $this->mOptions ) ) {
2762  return $this->mOptions[$oname];
2763  } else {
2764  return $defaultOverride;
2765  }
2766  }
2767 
2776  public function getOptions( $flags = 0 ) {
2777  global $wgHiddenPrefs;
2778  $this->loadOptions();
2780 
2781  # We want 'disabled' preferences to always behave as the default value for
2782  # users, even if they have set the option explicitly in their settings (ie they
2783  # set it, and then it was disabled removing their ability to change it). But
2784  # we don't want to erase the preferences in the database in case the preference
2785  # is re-enabled again. So don't touch $mOptions, just override the returned value
2786  foreach ( $wgHiddenPrefs as $pref ) {
2787  $default = self::getDefaultOption( $pref );
2788  if ( $default !== null ) {
2789  $options[$pref] = $default;
2790  }
2791  }
2792 
2793  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2794  $options = array_diff_assoc( $options, self::getDefaultOptions() );
2795  }
2796 
2797  return $options;
2798  }
2799 
2807  public function getBoolOption( $oname ) {
2808  return (bool)$this->getOption( $oname );
2809  }
2810 
2819  public function getIntOption( $oname, $defaultOverride = 0 ) {
2820  $val = $this->getOption( $oname );
2821  if ( $val == '' ) {
2822  $val = $defaultOverride;
2823  }
2824  return intval( $val );
2825  }
2826 
2835  public function setOption( $oname, $val ) {
2836  $this->loadOptions();
2837 
2838  // Explicitly NULL values should refer to defaults
2839  if ( is_null( $val ) ) {
2840  $val = self::getDefaultOption( $oname );
2841  }
2842 
2843  $this->mOptions[$oname] = $val;
2844  }
2845 
2856  public function getTokenFromOption( $oname ) {
2857  global $wgHiddenPrefs;
2858 
2859  $id = $this->getId();
2860  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2861  return false;
2862  }
2863 
2864  $token = $this->getOption( $oname );
2865  if ( !$token ) {
2866  // Default to a value based on the user token to avoid space
2867  // wasted on storing tokens for all users. When this option
2868  // is set manually by the user, only then is it stored.
2869  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2870  }
2871 
2872  return $token;
2873  }
2874 
2884  public function resetTokenFromOption( $oname ) {
2885  global $wgHiddenPrefs;
2886  if ( in_array( $oname, $wgHiddenPrefs ) ) {
2887  return false;
2888  }
2889 
2890  $token = MWCryptRand::generateHex( 40 );
2891  $this->setOption( $oname, $token );
2892  return $token;
2893  }
2894 
2918  public static function listOptionKinds() {
2919  return [
2920  'registered',
2921  'registered-multiselect',
2922  'registered-checkmatrix',
2923  'userjs',
2924  'special',
2925  'unused'
2926  ];
2927  }
2928 
2941  public function getOptionKinds( IContextSource $context, $options = null ) {
2942  $this->loadOptions();
2943  if ( $options === null ) {
2945  }
2946 
2947  $prefs = Preferences::getPreferences( $this, $context );
2948  $mapping = [];
2949 
2950  // Pull out the "special" options, so they don't get converted as
2951  // multiselect or checkmatrix.
2952  $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
2953  foreach ( $specialOptions as $name => $value ) {
2954  unset( $prefs[$name] );
2955  }
2956 
2957  // Multiselect and checkmatrix options are stored in the database with
2958  // one key per option, each having a boolean value. Extract those keys.
2959  $multiselectOptions = [];
2960  foreach ( $prefs as $name => $info ) {
2961  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
2962  ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
2963  $opts = HTMLFormField::flattenOptions( $info['options'] );
2964  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2965 
2966  foreach ( $opts as $value ) {
2967  $multiselectOptions["$prefix$value"] = true;
2968  }
2969 
2970  unset( $prefs[$name] );
2971  }
2972  }
2973  $checkmatrixOptions = [];
2974  foreach ( $prefs as $name => $info ) {
2975  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
2976  ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
2977  $columns = HTMLFormField::flattenOptions( $info['columns'] );
2978  $rows = HTMLFormField::flattenOptions( $info['rows'] );
2979  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2980 
2981  foreach ( $columns as $column ) {
2982  foreach ( $rows as $row ) {
2983  $checkmatrixOptions["$prefix$column-$row"] = true;
2984  }
2985  }
2986 
2987  unset( $prefs[$name] );
2988  }
2989  }
2990 
2991  // $value is ignored
2992  foreach ( $options as $key => $value ) {
2993  if ( isset( $prefs[$key] ) ) {
2994  $mapping[$key] = 'registered';
2995  } elseif ( isset( $multiselectOptions[$key] ) ) {
2996  $mapping[$key] = 'registered-multiselect';
2997  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
2998  $mapping[$key] = 'registered-checkmatrix';
2999  } elseif ( isset( $specialOptions[$key] ) ) {
3000  $mapping[$key] = 'special';
3001  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3002  $mapping[$key] = 'userjs';
3003  } else {
3004  $mapping[$key] = 'unused';
3005  }
3006  }
3007 
3008  return $mapping;
3009  }
3010 
3025  public function resetOptions(
3026  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3027  IContextSource $context = null
3028  ) {
3029  $this->load();
3030  $defaultOptions = self::getDefaultOptions();
3031 
3032  if ( !is_array( $resetKinds ) ) {
3033  $resetKinds = [ $resetKinds ];
3034  }
3035 
3036  if ( in_array( 'all', $resetKinds ) ) {
3037  $newOptions = $defaultOptions;
3038  } else {
3039  if ( $context === null ) {
3041  }
3042 
3043  $optionKinds = $this->getOptionKinds( $context );
3044  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3045  $newOptions = [];
3046 
3047  // Use default values for the options that should be deleted, and
3048  // copy old values for the ones that shouldn't.
3049  foreach ( $this->mOptions as $key => $value ) {
3050  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3051  if ( array_key_exists( $key, $defaultOptions ) ) {
3052  $newOptions[$key] = $defaultOptions[$key];
3053  }
3054  } else {
3055  $newOptions[$key] = $value;
3056  }
3057  }
3058  }
3059 
3060  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3061 
3062  $this->mOptions = $newOptions;
3063  $this->mOptionsLoaded = true;
3064  }
3065 
3070  public function getDatePreference() {
3071  // Important migration for old data rows
3072  if ( is_null( $this->mDatePreference ) ) {
3073  global $wgLang;
3074  $value = $this->getOption( 'date' );
3075  $map = $wgLang->getDatePreferenceMigrationMap();
3076  if ( isset( $map[$value] ) ) {
3077  $value = $map[$value];
3078  }
3079  $this->mDatePreference = $value;
3080  }
3081  return $this->mDatePreference;
3082  }
3083 
3090  public function requiresHTTPS() {
3091  global $wgSecureLogin;
3092  if ( !$wgSecureLogin ) {
3093  return false;
3094  } else {
3095  $https = $this->getBoolOption( 'prefershttps' );
3096  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3097  if ( $https ) {
3098  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3099  }
3100  return $https;
3101  }
3102  }
3103 
3109  public function getStubThreshold() {
3110  global $wgMaxArticleSize; # Maximum article size, in Kb
3111  $threshold = $this->getIntOption( 'stubthreshold' );
3112  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3113  // If they have set an impossible value, disable the preference
3114  // so we can use the parser cache again.
3115  $threshold = 0;
3116  }
3117  return $threshold;
3118  }
3119 
3124  public function getRights() {
3125  if ( is_null( $this->mRights ) ) {
3126  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3127  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3128 
3129  // Deny any rights denied by the user's session, unless this
3130  // endpoint has no sessions.
3131  if ( !defined( 'MW_NO_SESSION' ) ) {
3132  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3133  if ( $allowedRights !== null ) {
3134  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3135  }
3136  }
3137 
3138  // Force reindexation of rights when a hook has unset one of them
3139  $this->mRights = array_values( array_unique( $this->mRights ) );
3140 
3141  // If block disables login, we should also remove any
3142  // extra rights blocked users might have, in case the
3143  // blocked user has a pre-existing session (T129738).
3144  // This is checked here for cases where people only call
3145  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3146  // to give a better error message in the common case.
3147  $config = RequestContext::getMain()->getConfig();
3148  if (
3149  $this->isLoggedIn() &&
3150  $config->get( 'BlockDisablesLogin' ) &&
3151  $this->isBlocked()
3152  ) {
3153  $anon = new User;
3154  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3155  }
3156  }
3157  return $this->mRights;
3158  }
3159 
3165  public function getGroups() {
3166  $this->load();
3167  $this->loadGroups();
3168  return $this->mGroups;
3169  }
3170 
3178  public function getEffectiveGroups( $recache = false ) {
3179  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3180  $this->mEffectiveGroups = array_unique( array_merge(
3181  $this->getGroups(), // explicit groups
3182  $this->getAutomaticGroups( $recache ) // implicit groups
3183  ) );
3184  // Hook for additional groups
3185  Hooks::run( 'UserEffectiveGroups', [ &$this, &$this->mEffectiveGroups ] );
3186  // Force reindexation of groups when a hook has unset one of them
3187  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3188  }
3189  return $this->mEffectiveGroups;
3190  }
3191 
3199  public function getAutomaticGroups( $recache = false ) {
3200  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3201  $this->mImplicitGroups = [ '*' ];
3202  if ( $this->getId() ) {
3203  $this->mImplicitGroups[] = 'user';
3204 
3205  $this->mImplicitGroups = array_unique( array_merge(
3206  $this->mImplicitGroups,
3208  ) );
3209  }
3210  if ( $recache ) {
3211  // Assure data consistency with rights/groups,
3212  // as getEffectiveGroups() depends on this function
3213  $this->mEffectiveGroups = null;
3214  }
3215  }
3216  return $this->mImplicitGroups;
3217  }
3218 
3228  public function getFormerGroups() {
3229  $this->load();
3230 
3231  if ( is_null( $this->mFormerGroups ) ) {
3232  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3233  ? wfGetDB( DB_MASTER )
3234  : wfGetDB( DB_SLAVE );
3235  $res = $db->select( 'user_former_groups',
3236  [ 'ufg_group' ],
3237  [ 'ufg_user' => $this->mId ],
3238  __METHOD__ );
3239  $this->mFormerGroups = [];
3240  foreach ( $res as $row ) {
3241  $this->mFormerGroups[] = $row->ufg_group;
3242  }
3243  }
3244 
3245  return $this->mFormerGroups;
3246  }
3247 
3252  public function getEditCount() {
3253  if ( !$this->getId() ) {
3254  return null;
3255  }
3256 
3257  if ( $this->mEditCount === null ) {
3258  /* Populate the count, if it has not been populated yet */
3259  $dbr = wfGetDB( DB_SLAVE );
3260  // check if the user_editcount field has been initialized
3261  $count = $dbr->selectField(
3262  'user', 'user_editcount',
3263  [ 'user_id' => $this->mId ],
3264  __METHOD__
3265  );
3266 
3267  if ( $count === null ) {
3268  // it has not been initialized. do so.
3269  $count = $this->initEditCount();
3270  }
3271  $this->mEditCount = $count;
3272  }
3273  return (int)$this->mEditCount;
3274  }
3275 
3282  public function addGroup( $group ) {
3283  $this->load();
3284 
3285  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group ] ) ) {
3286  return false;
3287  }
3288 
3289  $dbw = wfGetDB( DB_MASTER );
3290  if ( $this->getId() ) {
3291  $dbw->insert( 'user_groups',
3292  [
3293  'ug_user' => $this->getId(),
3294  'ug_group' => $group,
3295  ],
3296  __METHOD__,
3297  [ 'IGNORE' ] );
3298  }
3299 
3300  $this->loadGroups();
3301  $this->mGroups[] = $group;
3302  // In case loadGroups was not called before, we now have the right twice.
3303  // Get rid of the duplicate.
3304  $this->mGroups = array_unique( $this->mGroups );
3305 
3306  // Refresh the groups caches, and clear the rights cache so it will be
3307  // refreshed on the next call to $this->getRights().
3308  $this->getEffectiveGroups( true );
3309  $this->mRights = null;
3310 
3311  $this->invalidateCache();
3312 
3313  return true;
3314  }
3315 
3322  public function removeGroup( $group ) {
3323  $this->load();
3324  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3325  return false;
3326  }
3327 
3328  $dbw = wfGetDB( DB_MASTER );
3329  $dbw->delete( 'user_groups',
3330  [
3331  'ug_user' => $this->getId(),
3332  'ug_group' => $group,
3333  ], __METHOD__
3334  );
3335  // Remember that the user was in this group
3336  $dbw->insert( 'user_former_groups',
3337  [
3338  'ufg_user' => $this->getId(),
3339  'ufg_group' => $group,
3340  ],
3341  __METHOD__,
3342  [ 'IGNORE' ]
3343  );
3344 
3345  $this->loadGroups();
3346  $this->mGroups = array_diff( $this->mGroups, [ $group ] );
3347 
3348  // Refresh the groups caches, and clear the rights cache so it will be
3349  // refreshed on the next call to $this->getRights().
3350  $this->getEffectiveGroups( true );
3351  $this->mRights = null;
3352 
3353  $this->invalidateCache();
3354 
3355  return true;
3356  }
3357 
3362  public function isLoggedIn() {
3363  return $this->getId() != 0;
3364  }
3365 
3370  public function isAnon() {
3371  return !$this->isLoggedIn();
3372  }
3373 
3380  public function isAllowedAny() {
3381  $permissions = func_get_args();
3382  foreach ( $permissions as $permission ) {
3383  if ( $this->isAllowed( $permission ) ) {
3384  return true;
3385  }
3386  }
3387  return false;
3388  }
3389 
3395  public function isAllowedAll() {
3396  $permissions = func_get_args();
3397  foreach ( $permissions as $permission ) {
3398  if ( !$this->isAllowed( $permission ) ) {
3399  return false;
3400  }
3401  }
3402  return true;
3403  }
3404 
3410  public function isAllowed( $action = '' ) {
3411  if ( $action === '' ) {
3412  return true; // In the spirit of DWIM
3413  }
3414  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3415  // by misconfiguration: 0 == 'foo'
3416  return in_array( $action, $this->getRights(), true );
3417  }
3418 
3423  public function useRCPatrol() {
3424  global $wgUseRCPatrol;
3425  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3426  }
3427 
3432  public function useNPPatrol() {
3433  global $wgUseRCPatrol, $wgUseNPPatrol;
3434  return (
3435  ( $wgUseRCPatrol || $wgUseNPPatrol )
3436  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3437  );
3438  }
3439 
3444  public function useFilePatrol() {
3445  global $wgUseRCPatrol, $wgUseFilePatrol;
3446  return (
3447  ( $wgUseRCPatrol || $wgUseFilePatrol )
3448  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3449  );
3450  }
3451 
3457  public function getRequest() {
3458  if ( $this->mRequest ) {
3459  return $this->mRequest;
3460  } else {
3462  return $wgRequest;
3463  }
3464  }
3465 
3474  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3475  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3476  return WatchedItemStore::getDefaultInstance()->isWatched( $this, $title );
3477  }
3478  return false;
3479  }
3480 
3488  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3489  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3490  WatchedItemStore::getDefaultInstance()->addWatchBatchForUser(
3491  $this,
3492  [ $title->getSubjectPage(), $title->getTalkPage() ]
3493  );
3494  }
3495  $this->invalidateCache();
3496  }
3497 
3505  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3506  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3507  WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getSubjectPage() );
3508  WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getTalkPage() );
3509  }
3510  $this->invalidateCache();
3511  }
3512 
3521  public function clearNotification( &$title, $oldid = 0 ) {
3522  global $wgUseEnotif, $wgShowUpdatedMarker;
3523 
3524  // Do nothing if the database is locked to writes
3525  if ( wfReadOnly() ) {
3526  return;
3527  }
3528 
3529  // Do nothing if not allowed to edit the watchlist
3530  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3531  return;
3532  }
3533 
3534  // If we're working on user's talk page, we should update the talk page message indicator
3535  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3536  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$this, $oldid ] ) ) {
3537  return;
3538  }
3539 
3540  // Try to update the DB post-send and only if needed...
3541  DeferredUpdates::addCallableUpdate( function() use ( $title, $oldid ) {
3542  if ( !$this->getNewtalk() ) {
3543  return; // no notifications to clear
3544  }
3545 
3546  // Delete the last notifications (they stack up)
3547  $this->setNewtalk( false );
3548 
3549  // If there is a new, unseen, revision, use its timestamp
3550  $nextid = $oldid
3551  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3552  : null;
3553  if ( $nextid ) {
3554  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3555  }
3556  } );
3557  }
3558 
3559  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3560  return;
3561  }
3562 
3563  if ( $this->isAnon() ) {
3564  // Nothing else to do...
3565  return;
3566  }
3567 
3568  // Only update the timestamp if the page is being watched.
3569  // The query to find out if it is watched is cached both in memcached and per-invocation,
3570  // and when it does have to be executed, it can be on a slave
3571  // If this is the user's newtalk page, we always update the timestamp
3572  $force = '';
3573  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3574  $force = 'force';
3575  }
3576 
3578  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3579  }
3580 
3587  public function clearAllNotifications() {
3588  if ( wfReadOnly() ) {
3589  return;
3590  }
3591 
3592  // Do nothing if not allowed to edit the watchlist
3593  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3594  return;
3595  }
3596 
3597  global $wgUseEnotif, $wgShowUpdatedMarker;
3598  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3599  $this->setNewtalk( false );
3600  return;
3601  }
3602  $id = $this->getId();
3603  if ( $id != 0 ) {
3604  $dbw = wfGetDB( DB_MASTER );
3605  $dbw->update( 'watchlist',
3606  [ /* SET */ 'wl_notificationtimestamp' => null ],
3607  [ /* WHERE */ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3608  __METHOD__
3609  );
3610  // We also need to clear here the "you have new message" notification for the own user_talk page;
3611  // it's cleared one page view later in WikiPage::doViewUpdates().
3612  }
3613  }
3614 
3631  protected function setCookie(
3632  $name, $value, $exp = 0, $secure = null, $params = [], $request = null
3633  ) {
3634  wfDeprecated( __METHOD__, '1.27' );
3635  if ( $request === null ) {
3636  $request = $this->getRequest();
3637  }
3638  $params['secure'] = $secure;
3639  $request->response()->setCookie( $name, $value, $exp, $params );
3640  }
3641 
3652  protected function clearCookie( $name, $secure = null, $params = [] ) {
3653  wfDeprecated( __METHOD__, '1.27' );
3654  $this->setCookie( $name, '', time() - 86400, $secure, $params );
3655  }
3656 
3672  protected function setExtendedLoginCookie( $name, $value, $secure ) {
3673  global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
3674 
3675  wfDeprecated( __METHOD__, '1.27' );
3676 
3677  $exp = time();
3678  $exp += $wgExtendedLoginCookieExpiration !== null
3679  ? $wgExtendedLoginCookieExpiration
3680  : $wgCookieExpiration;
3681 
3682  $this->setCookie( $name, $value, $exp, $secure );
3683  }
3684 
3693  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3694  $this->load();
3695  if ( 0 == $this->mId ) {
3696  return;
3697  }
3698 
3699  $session = $this->getRequest()->getSession();
3700  if ( $request && $session->getRequest() !== $request ) {
3701  $session = $session->sessionWithRequest( $request );
3702  }
3703  $delay = $session->delaySave();
3704 
3705  if ( !$session->getUser()->equals( $this ) ) {
3706  if ( !$session->canSetUser() ) {
3708  ->warning( __METHOD__ .
3709  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3710  );
3711  return;
3712  }
3713  $session->setUser( $this );
3714  }
3715 
3716  $session->setRememberUser( $rememberMe );
3717  if ( $secure !== null ) {
3718  $session->setForceHTTPS( $secure );
3719  }
3720 
3721  $session->persist();
3722 
3723  ScopedCallback::consume( $delay );
3724  }
3725 
3729  public function logout() {
3730  if ( Hooks::run( 'UserLogout', [ &$this ] ) ) {
3731  $this->doLogout();
3732  }
3733  }
3734 
3739  public function doLogout() {
3740  $session = $this->getRequest()->getSession();
3741  if ( !$session->canSetUser() ) {
3743  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3744  } elseif ( !$session->getUser()->equals( $this ) ) {
3746  ->warning( __METHOD__ .
3747  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3748  );
3749  // But we still may as well make this user object anon
3750  $this->clearInstanceCache( 'defaults' );
3751  } else {
3752  $this->clearInstanceCache( 'defaults' );
3753  $delay = $session->delaySave();
3754  $session->unpersist(); // Clear cookies (T127436)
3755  $session->setLoggedOutTimestamp( time() );
3756  $session->setUser( new User );
3757  $session->set( 'wsUserID', 0 ); // Other code expects this
3758  $session->resetAllTokens();
3759  ScopedCallback::consume( $delay );
3760  }
3761  }
3762 
3767  public function saveSettings() {
3768  if ( wfReadOnly() ) {
3769  // @TODO: caller should deal with this instead!
3770  // This should really just be an exception.
3772  null,
3773  "Could not update user with ID '{$this->mId}'; DB is read-only."
3774  ) );
3775  return;
3776  }
3777 
3778  $this->load();
3779  if ( 0 == $this->mId ) {
3780  return; // anon
3781  }
3782 
3783  // Get a new user_touched that is higher than the old one.
3784  // This will be used for a CAS check as a last-resort safety
3785  // check against race conditions and slave lag.
3786  $oldTouched = $this->mTouched;
3787  $newTouched = $this->newTouchedTimestamp();
3788 
3789  $dbw = wfGetDB( DB_MASTER );
3790  $dbw->update( 'user',
3791  [ /* SET */
3792  'user_name' => $this->mName,
3793  'user_real_name' => $this->mRealName,
3794  'user_email' => $this->mEmail,
3795  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3796  'user_touched' => $dbw->timestamp( $newTouched ),
3797  'user_token' => strval( $this->mToken ),
3798  'user_email_token' => $this->mEmailToken,
3799  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3800  ], [ /* WHERE */
3801  'user_id' => $this->mId,
3802  'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
3803  ], __METHOD__
3804  );
3805 
3806  if ( !$dbw->affectedRows() ) {
3807  // Maybe the problem was a missed cache update; clear it to be safe
3808  $this->clearSharedCache( 'refresh' );
3809  // User was changed in the meantime or loaded with stale data
3810  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'slave';
3811  throw new MWException(
3812  "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
3813  " the version of the user to be saved is older than the current version."
3814  );
3815  }
3816 
3817  $this->mTouched = $newTouched;
3818  $this->saveOptions();
3819 
3820  Hooks::run( 'UserSaveSettings', [ $this ] );
3821  $this->clearSharedCache();
3822  $this->getUserPage()->invalidateCache();
3823  }
3824 
3831  public function idForName( $flags = 0 ) {
3832  $s = trim( $this->getName() );
3833  if ( $s === '' ) {
3834  return 0;
3835  }
3836 
3837  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
3838  ? wfGetDB( DB_MASTER )
3839  : wfGetDB( DB_SLAVE );
3840 
3841  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
3842  ? [ 'LOCK IN SHARE MODE' ]
3843  : [];
3844 
3845  $id = $db->selectField( 'user',
3846  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
3847 
3848  return (int)$id;
3849  }
3850 
3866  public static function createNew( $name, $params = [] ) {
3867  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
3868  if ( isset( $params[$field] ) ) {
3869  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
3870  unset( $params[$field] );
3871  }
3872  }
3873 
3874  $user = new User;
3875  $user->load();
3876  $user->setToken(); // init token
3877  if ( isset( $params['options'] ) ) {
3878  $user->mOptions = $params['options'] + (array)$user->mOptions;
3879  unset( $params['options'] );
3880  }
3881  $dbw = wfGetDB( DB_MASTER );
3882  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
3883 
3884  $noPass = PasswordFactory::newInvalidPassword()->toString();
3885 
3886  $fields = [
3887  'user_id' => $seqVal,
3888  'user_name' => $name,
3889  'user_password' => $noPass,
3890  'user_newpassword' => $noPass,
3891  'user_email' => $user->mEmail,
3892  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3893  'user_real_name' => $user->mRealName,
3894  'user_token' => strval( $user->mToken ),
3895  'user_registration' => $dbw->timestamp( $user->mRegistration ),
3896  'user_editcount' => 0,
3897  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3898  ];
3899  foreach ( $params as $name => $value ) {
3900  $fields["user_$name"] = $value;
3901  }
3902  $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
3903  if ( $dbw->affectedRows() ) {
3904  $newUser = User::newFromId( $dbw->insertId() );
3905  } else {
3906  $newUser = null;
3907  }
3908  return $newUser;
3909  }
3910 
3937  public function addToDatabase() {
3938  $this->load();
3939  if ( !$this->mToken ) {
3940  $this->setToken(); // init token
3941  }
3942 
3943  $this->mTouched = $this->newTouchedTimestamp();
3944 
3945  $noPass = PasswordFactory::newInvalidPassword()->toString();
3946 
3947  $dbw = wfGetDB( DB_MASTER );
3948  $inWrite = $dbw->writesOrCallbacksPending();
3949  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
3950  $dbw->insert( 'user',
3951  [
3952  'user_id' => $seqVal,
3953  'user_name' => $this->mName,
3954  'user_password' => $noPass,
3955  'user_newpassword' => $noPass,
3956  'user_email' => $this->mEmail,
3957  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3958  'user_real_name' => $this->mRealName,
3959  'user_token' => strval( $this->mToken ),
3960  'user_registration' => $dbw->timestamp( $this->mRegistration ),
3961  'user_editcount' => 0,
3962  'user_touched' => $dbw->timestamp( $this->mTouched ),
3963  ], __METHOD__,
3964  [ 'IGNORE' ]
3965  );
3966  if ( !$dbw->affectedRows() ) {
3967  // The queries below cannot happen in the same REPEATABLE-READ snapshot.
3968  // Handle this by COMMIT, if possible, or by LOCK IN SHARE MODE otherwise.
3969  if ( $inWrite ) {
3970  // Can't commit due to pending writes that may need atomicity.
3971  // This may cause some lock contention unlike the case below.
3972  $options = [ 'LOCK IN SHARE MODE' ];
3973  $flags = self::READ_LOCKING;
3974  } else {
3975  // Often, this case happens early in views before any writes when
3976  // using CentralAuth. It's should be OK to commit and break the snapshot.
3977  $dbw->commit( __METHOD__, 'flush' );
3978  $options = [];
3979  $flags = self::READ_LATEST;
3980  }
3981  $this->mId = $dbw->selectField( 'user', 'user_id',
3982  [ 'user_name' => $this->mName ], __METHOD__, $options );
3983  $loaded = false;
3984  if ( $this->mId ) {
3985  if ( $this->loadFromDatabase( $flags ) ) {
3986  $loaded = true;
3987  }
3988  }
3989  if ( !$loaded ) {
3990  throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
3991  "to insert user '{$this->mName}' row, but it was not present in select!" );
3992  }
3993  return Status::newFatal( 'userexists' );
3994  }
3995  $this->mId = $dbw->insertId();
3996  self::$idCacheByName[$this->mName] = $this->mId;
3997 
3998  // Clear instance cache other than user table data, which is already accurate
3999  $this->clearInstanceCache();
4000 
4001  $this->saveOptions();
4002  return Status::newGood();
4003  }
4004 
4010  public function spreadAnyEditBlock() {
4011  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4012  return $this->spreadBlock();
4013  }
4014 
4015  return false;
4016  }
4017 
4023  protected function spreadBlock() {
4024  wfDebug( __METHOD__ . "()\n" );
4025  $this->load();
4026  if ( $this->mId == 0 ) {
4027  return false;
4028  }
4029 
4030  $userblock = Block::newFromTarget( $this->getName() );
4031  if ( !$userblock ) {
4032  return false;
4033  }
4034 
4035  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4036  }
4037 
4042  public function isBlockedFromCreateAccount() {
4043  $this->getBlockedStatus();
4044  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4045  return $this->mBlock;
4046  }
4047 
4048  # bug 13611: if the IP address the user is trying to create an account from is
4049  # blocked with createaccount disabled, prevent new account creation there even
4050  # when the user is logged in
4051  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4052  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4053  }
4054  return $this->mBlockedFromCreateAccount instanceof Block
4055  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4056  ? $this->mBlockedFromCreateAccount
4057  : false;
4058  }
4059 
4064  public function isBlockedFromEmailuser() {
4065  $this->getBlockedStatus();
4066  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4067  }
4068 
4073  public function isAllowedToCreateAccount() {
4074  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4075  }
4076 
4082  public function getUserPage() {
4083  return Title::makeTitle( NS_USER, $this->getName() );
4084  }
4085 
4091  public function getTalkPage() {
4092  $title = $this->getUserPage();
4093  return $title->getTalkPage();
4094  }
4095 
4101  public function isNewbie() {
4102  return !$this->isAllowed( 'autoconfirmed' );
4103  }
4104 
4111  public function checkPassword( $password ) {
4112  $manager = AuthManager::singleton();
4113  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4114  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4115  [
4116  'username' => $this->getName(),
4117  'password' => $password,
4118  ]
4119  );
4120  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4121  switch ( $res->status ) {
4122  case AuthenticationResponse::PASS:
4123  return true;
4124  case AuthenticationResponse::FAIL:
4125  // Hope it's not a PreAuthenticationProvider that failed...
4127  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4128  return false;
4129  default:
4130  throw new BadMethodCallException(
4131  'AuthManager returned a response unsupported by ' . __METHOD__
4132  );
4133  }
4134  }
4135 
4144  public function checkTemporaryPassword( $plaintext ) {
4145  // Can't check the temporary password individually.
4146  return $this->checkPassword( $plaintext );
4147  }
4148 
4160  public function getEditTokenObject( $salt = '', $request = null ) {
4161  if ( $this->isAnon() ) {
4162  return new LoggedOutEditToken();
4163  }
4164 
4165  if ( !$request ) {
4166  $request = $this->getRequest();
4167  }
4168  return $request->getSession()->getToken( $salt );
4169  }
4170 
4182  public function getEditToken( $salt = '', $request = null ) {
4183  return $this->getEditTokenObject( $salt, $request )->toString();
4184  }
4185 
4192  public static function getEditTokenTimestamp( $val ) {
4193  wfDeprecated( __METHOD__, '1.27' );
4195  }
4196 
4209  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4210  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4211  }
4212 
4223  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4224  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4225  return $this->matchEditToken( $val, $salt, $request, $maxage );
4226  }
4227 
4235  public function sendConfirmationMail( $type = 'created' ) {
4236  global $wgLang;
4237  $expiration = null; // gets passed-by-ref and defined in next line.
4238  $token = $this->confirmationToken( $expiration );
4239  $url = $this->confirmationTokenUrl( $token );
4240  $invalidateURL = $this->invalidationTokenUrl( $token );
4241  $this->saveSettings();
4242 
4243  if ( $type == 'created' || $type === false ) {
4244  $message = 'confirmemail_body';
4245  } elseif ( $type === true ) {
4246  $message = 'confirmemail_body_changed';
4247  } else {
4248  // Messages: confirmemail_body_changed, confirmemail_body_set
4249  $message = 'confirmemail_body_' . $type;
4250  }
4251 
4252  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4253  wfMessage( $message,
4254  $this->getRequest()->getIP(),
4255  $this->getName(),
4256  $url,
4257  $wgLang->userTimeAndDate( $expiration, $this ),
4258  $invalidateURL,
4259  $wgLang->userDate( $expiration, $this ),
4260  $wgLang->userTime( $expiration, $this ) )->text() );
4261  }
4262 
4274  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4276 
4277  if ( $from instanceof User ) {
4278  $sender = MailAddress::newFromUser( $from );
4279  } else {
4280  $sender = new MailAddress( $wgPasswordSender,
4281  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4282  }
4283  $to = MailAddress::newFromUser( $this );
4284 
4285  return UserMailer::send( $to, $sender, $subject, $body, [
4286  'replyTo' => $replyto,
4287  ] );
4288  }
4289 
4300  protected function confirmationToken( &$expiration ) {
4302  $now = time();
4303  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4304  $expiration = wfTimestamp( TS_MW, $expires );
4305  $this->load();
4306  $token = MWCryptRand::generateHex( 32 );
4307  $hash = md5( $token );
4308  $this->mEmailToken = $hash;
4309  $this->mEmailTokenExpires = $expiration;
4310  return $token;
4311  }
4312 
4318  protected function confirmationTokenUrl( $token ) {
4319  return $this->getTokenUrl( 'ConfirmEmail', $token );
4320  }
4321 
4327  protected function invalidationTokenUrl( $token ) {
4328  return $this->getTokenUrl( 'InvalidateEmail', $token );
4329  }
4330 
4345  protected function getTokenUrl( $page, $token ) {
4346  // Hack to bypass localization of 'Special:'
4347  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4348  return $title->getCanonicalURL();
4349  }
4350 
4358  public function confirmEmail() {
4359  // Check if it's already confirmed, so we don't touch the database
4360  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4361  if ( !$this->isEmailConfirmed() ) {
4363  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4364  }
4365  return true;
4366  }
4367 
4375  public function invalidateEmail() {
4376  $this->load();
4377  $this->mEmailToken = null;
4378  $this->mEmailTokenExpires = null;
4379  $this->setEmailAuthenticationTimestamp( null );
4380  $this->mEmail = '';
4381  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4382  return true;
4383  }
4384 
4390  $this->load();
4391  $this->mEmailAuthenticated = $timestamp;
4392  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4393  }
4394 
4400  public function canSendEmail() {
4402  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4403  return false;
4404  }
4405  $canSend = $this->isEmailConfirmed();
4406  Hooks::run( 'UserCanSendEmail', [ &$this, &$canSend ] );
4407  return $canSend;
4408  }
4409 
4415  public function canReceiveEmail() {
4416  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4417  }
4418 
4429  public function isEmailConfirmed() {
4431  $this->load();
4432  $confirmed = true;
4433  if ( Hooks::run( 'EmailConfirmed', [ &$this, &$confirmed ] ) ) {
4434  if ( $this->isAnon() ) {
4435  return false;
4436  }
4437  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4438  return false;
4439  }
4440  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4441  return false;
4442  }
4443  return true;
4444  } else {
4445  return $confirmed;
4446  }
4447  }
4448 
4453  public function isEmailConfirmationPending() {
4455  return $wgEmailAuthentication &&
4456  !$this->isEmailConfirmed() &&
4457  $this->mEmailToken &&
4458  $this->mEmailTokenExpires > wfTimestamp();
4459  }
4460 
4468  public function getRegistration() {
4469  if ( $this->isAnon() ) {
4470  return false;
4471  }
4472  $this->load();
4473  return $this->mRegistration;
4474  }
4475 
4482  public function getFirstEditTimestamp() {
4483  if ( $this->getId() == 0 ) {
4484  return false; // anons
4485  }
4486  $dbr = wfGetDB( DB_SLAVE );
4487  $time = $dbr->selectField( 'revision', 'rev_timestamp',
4488  [ 'rev_user' => $this->getId() ],
4489  __METHOD__,
4490  [ 'ORDER BY' => 'rev_timestamp ASC' ]
4491  );
4492  if ( !$time ) {
4493  return false; // no edits
4494  }
4495  return wfTimestamp( TS_MW, $time );
4496  }
4497 
4504  public static function getGroupPermissions( $groups ) {
4505  global $wgGroupPermissions, $wgRevokePermissions;
4506  $rights = [];
4507  // grant every granted permission first
4508  foreach ( $groups as $group ) {
4509  if ( isset( $wgGroupPermissions[$group] ) ) {
4510  $rights = array_merge( $rights,
4511  // array_filter removes empty items
4512  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4513  }
4514  }
4515  // now revoke the revoked permissions
4516  foreach ( $groups as $group ) {
4517  if ( isset( $wgRevokePermissions[$group] ) ) {
4518  $rights = array_diff( $rights,
4519  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4520  }
4521  }
4522  return array_unique( $rights );
4523  }
4524 
4531  public static function getGroupsWithPermission( $role ) {
4532  global $wgGroupPermissions;
4533  $allowedGroups = [];
4534  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4535  if ( self::groupHasPermission( $group, $role ) ) {
4536  $allowedGroups[] = $group;
4537  }
4538  }
4539  return $allowedGroups;
4540  }
4541 
4554  public static function groupHasPermission( $group, $role ) {
4555  global $wgGroupPermissions, $wgRevokePermissions;
4556  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
4557  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
4558  }
4559 
4574  public static function isEveryoneAllowed( $right ) {
4575  global $wgGroupPermissions, $wgRevokePermissions;
4576  static $cache = [];
4577 
4578  // Use the cached results, except in unit tests which rely on
4579  // being able change the permission mid-request
4580  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
4581  return $cache[$right];
4582  }
4583 
4584  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
4585  $cache[$right] = false;
4586  return false;
4587  }
4588 
4589  // If it's revoked anywhere, then everyone doesn't have it
4590  foreach ( $wgRevokePermissions as $rights ) {
4591  if ( isset( $rights[$right] ) && $rights[$right] ) {
4592  $cache[$right] = false;
4593  return false;
4594  }
4595  }
4596 
4597  // Remove any rights that aren't allowed to the global-session user,
4598  // unless there are no sessions for this endpoint.
4599  if ( !defined( 'MW_NO_SESSION' ) ) {
4600  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
4601  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
4602  $cache[$right] = false;
4603  return false;
4604  }
4605  }
4606 
4607  // Allow extensions to say false
4608  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
4609  $cache[$right] = false;
4610  return false;
4611  }
4612 
4613  $cache[$right] = true;
4614  return true;
4615  }
4616 
4623  public static function getGroupName( $group ) {
4624  $msg = wfMessage( "group-$group" );
4625  return $msg->isBlank() ? $group : $msg->text();
4626  }
4627 
4635  public static function getGroupMember( $group, $username = '#' ) {
4636  $msg = wfMessage( "group-$group-member", $username );
4637  return $msg->isBlank() ? $group : $msg->text();
4638  }
4639 
4646  public static function getAllGroups() {
4647  global $wgGroupPermissions, $wgRevokePermissions;
4648  return array_diff(
4649  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4650  self::getImplicitGroups()
4651  );
4652  }
4653 
4658  public static function getAllRights() {
4659  if ( self::$mAllRights === false ) {
4660  global $wgAvailableRights;
4661  if ( count( $wgAvailableRights ) ) {
4662  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
4663  } else {
4664  self::$mAllRights = self::$mCoreRights;
4665  }
4666  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
4667  }
4668  return self::$mAllRights;
4669  }
4670 
4675  public static function getImplicitGroups() {
4676  global $wgImplicitGroups;
4677 
4678  $groups = $wgImplicitGroups;
4679  # Deprecated, use $wgImplicitGroups instead
4680  Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
4681 
4682  return $groups;
4683  }
4684 
4691  public static function getGroupPage( $group ) {
4692  $msg = wfMessage( 'grouppage-' . $group )->inContentLanguage();
4693  if ( $msg->exists() ) {
4694  $title = Title::newFromText( $msg->text() );
4695  if ( is_object( $title ) ) {
4696  return $title;
4697  }
4698  }
4699  return false;
4700  }
4701 
4710  public static function makeGroupLinkHTML( $group, $text = '' ) {
4711  if ( $text == '' ) {
4712  $text = self::getGroupName( $group );
4713  }
4714  $title = self::getGroupPage( $group );
4715  if ( $title ) {
4716  return Linker::link( $title, htmlspecialchars( $text ) );
4717  } else {
4718  return htmlspecialchars( $text );
4719  }
4720  }
4721 
4730  public static function makeGroupLinkWiki( $group, $text = '' ) {
4731  if ( $text == '' ) {
4732  $text = self::getGroupName( $group );
4733  }
4734  $title = self::getGroupPage( $group );
4735  if ( $title ) {
4736  $page = $title->getFullText();
4737  return "[[$page|$text]]";
4738  } else {
4739  return $text;
4740  }
4741  }
4742 
4752  public static function changeableByGroup( $group ) {
4753  global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
4754 
4755  $groups = [
4756  'add' => [],
4757  'remove' => [],
4758  'add-self' => [],
4759  'remove-self' => []
4760  ];
4761 
4762  if ( empty( $wgAddGroups[$group] ) ) {
4763  // Don't add anything to $groups
4764  } elseif ( $wgAddGroups[$group] === true ) {
4765  // You get everything
4766  $groups['add'] = self::getAllGroups();
4767  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4768  $groups['add'] = $wgAddGroups[$group];
4769  }
4770 
4771  // Same thing for remove
4772  if ( empty( $wgRemoveGroups[$group] ) ) {
4773  // Do nothing
4774  } elseif ( $wgRemoveGroups[$group] === true ) {
4775  $groups['remove'] = self::getAllGroups();
4776  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4777  $groups['remove'] = $wgRemoveGroups[$group];
4778  }
4779 
4780  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4781  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4782  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4783  if ( is_int( $key ) ) {
4784  $wgGroupsAddToSelf['user'][] = $value;
4785  }
4786  }
4787  }
4788 
4789  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4790  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4791  if ( is_int( $key ) ) {
4792  $wgGroupsRemoveFromSelf['user'][] = $value;
4793  }
4794  }
4795  }
4796 
4797  // Now figure out what groups the user can add to him/herself
4798  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4799  // Do nothing
4800  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4801  // No idea WHY this would be used, but it's there
4802  $groups['add-self'] = User::getAllGroups();
4803  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4804  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4805  }
4806 
4807  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4808  // Do nothing
4809  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4810  $groups['remove-self'] = User::getAllGroups();
4811  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4812  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4813  }
4814 
4815  return $groups;
4816  }
4817 
4825  public function changeableGroups() {
4826  if ( $this->isAllowed( 'userrights' ) ) {
4827  // This group gives the right to modify everything (reverse-
4828  // compatibility with old "userrights lets you change
4829  // everything")
4830  // Using array_merge to make the groups reindexed
4831  $all = array_merge( User::getAllGroups() );
4832  return [
4833  'add' => $all,
4834  'remove' => $all,
4835  'add-self' => [],
4836  'remove-self' => []
4837  ];
4838  }
4839 
4840  // Okay, it's not so simple, we will have to go through the arrays
4841  $groups = [
4842  'add' => [],
4843  'remove' => [],
4844  'add-self' => [],
4845  'remove-self' => []
4846  ];
4847  $addergroups = $this->getEffectiveGroups();
4848 
4849  foreach ( $addergroups as $addergroup ) {
4850  $groups = array_merge_recursive(
4851  $groups, $this->changeableByGroup( $addergroup )
4852  );
4853  $groups['add'] = array_unique( $groups['add'] );
4854  $groups['remove'] = array_unique( $groups['remove'] );
4855  $groups['add-self'] = array_unique( $groups['add-self'] );
4856  $groups['remove-self'] = array_unique( $groups['remove-self'] );
4857  }
4858  return $groups;
4859  }
4860 
4864  public function incEditCount() {
4865  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() {
4866  $this->incEditCountImmediate();
4867  } );
4868  }
4869 
4875  public function incEditCountImmediate() {
4876  if ( $this->isAnon() ) {
4877  return;
4878  }
4879 
4880  $dbw = wfGetDB( DB_MASTER );
4881  // No rows will be "affected" if user_editcount is NULL
4882  $dbw->update(
4883  'user',
4884  [ 'user_editcount=user_editcount+1' ],
4885  [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
4886  __METHOD__
4887  );
4888  // Lazy initialization check...
4889  if ( $dbw->affectedRows() == 0 ) {
4890  // Now here's a goddamn hack...
4891  $dbr = wfGetDB( DB_SLAVE );
4892  if ( $dbr !== $dbw ) {
4893  // If we actually have a slave server, the count is
4894  // at least one behind because the current transaction
4895  // has not been committed and replicated.
4896  $this->initEditCount( 1 );
4897  } else {
4898  // But if DB_SLAVE is selecting the master, then the
4899  // count we just read includes the revision that was
4900  // just added in the working transaction.
4901  $this->initEditCount();
4902  }
4903  }
4904  // Edit count in user cache too
4905  $this->invalidateCache();
4906  }
4907 
4914  protected function initEditCount( $add = 0 ) {
4915  // Pull from a slave to be less cruel to servers
4916  // Accuracy isn't the point anyway here
4917  $dbr = wfGetDB( DB_SLAVE );
4918  $count = (int)$dbr->selectField(
4919  'revision',
4920  'COUNT(rev_user)',
4921  [ 'rev_user' => $this->getId() ],
4922  __METHOD__
4923  );
4924  $count = $count + $add;
4925 
4926  $dbw = wfGetDB( DB_MASTER );
4927  $dbw->update(
4928  'user',
4929  [ 'user_editcount' => $count ],
4930  [ 'user_id' => $this->getId() ],
4931  __METHOD__
4932  );
4933 
4934  return $count;
4935  }
4936 
4943  public static function getRightDescription( $right ) {
4944  $key = "right-$right";
4945  $msg = wfMessage( $key );
4946  return $msg->isBlank() ? $right : $msg->text();
4947  }
4948 
4958  public static function crypt( $password, $salt = false ) {
4959  wfDeprecated( __METHOD__, '1.24' );
4960  $passwordFactory = new PasswordFactory();
4961  $passwordFactory->init( RequestContext::getMain()->getConfig() );
4962  $hash = $passwordFactory->newFromPlaintext( $password );
4963  return $hash->toString();
4964  }
4965 
4977  public static function comparePasswords( $hash, $password, $userId = false ) {
4978  wfDeprecated( __METHOD__, '1.24' );
4979 
4980  // Check for *really* old password hashes that don't even have a type
4981  // The old hash format was just an md5 hex hash, with no type information
4982  if ( preg_match( '/^[0-9a-f]{32}$/', $hash ) ) {
4983  global $wgPasswordSalt;
4984  if ( $wgPasswordSalt ) {
4985  $password = ":B:{$userId}:{$hash}";
4986  } else {
4987  $password = ":A:{$hash}";
4988  }
4989  }
4990 
4991  $passwordFactory = new PasswordFactory();
4992  $passwordFactory->init( RequestContext::getMain()->getConfig() );
4993  $hash = $passwordFactory->newFromCiphertext( $hash );
4994  return $hash->equals( $password );
4995  }
4996 
5017  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5018  return true; // disabled
5019  }
5020 
5029  public function addNewUserLogEntryAutoCreate() {
5030  $this->addNewUserLogEntry( 'autocreate' );
5031 
5032  return true;
5033  }
5034 
5040  protected function loadOptions( $data = null ) {
5042 
5043  $this->load();
5044 
5045  if ( $this->mOptionsLoaded ) {
5046  return;
5047  }
5048 
5049  $this->mOptions = self::getDefaultOptions();
5050 
5051  if ( !$this->getId() ) {
5052  // For unlogged-in users, load language/variant options from request.
5053  // There's no need to do it for logged-in users: they can set preferences,
5054  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5055  // so don't override user's choice (especially when the user chooses site default).
5056  $variant = $wgContLang->getDefaultVariant();
5057  $this->mOptions['variant'] = $variant;
5058  $this->mOptions['language'] = $variant;
5059  $this->mOptionsLoaded = true;
5060  return;
5061  }
5062 
5063  // Maybe load from the object
5064  if ( !is_null( $this->mOptionOverrides ) ) {
5065  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5066  foreach ( $this->mOptionOverrides as $key => $value ) {
5067  $this->mOptions[$key] = $value;
5068  }
5069  } else {
5070  if ( !is_array( $data ) ) {
5071  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5072  // Load from database
5073  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5074  ? wfGetDB( DB_MASTER )
5075  : wfGetDB( DB_SLAVE );
5076 
5077  $res = $dbr->select(
5078  'user_properties',
5079  [ 'up_property', 'up_value' ],
5080  [ 'up_user' => $this->getId() ],
5081  __METHOD__
5082  );
5083 
5084  $this->mOptionOverrides = [];
5085  $data = [];
5086  foreach ( $res as $row ) {
5087  $data[$row->up_property] = $row->up_value;
5088  }
5089  }
5090  foreach ( $data as $property => $value ) {
5091  $this->mOptionOverrides[$property] = $value;
5092  $this->mOptions[$property] = $value;
5093  }
5094  }
5095 
5096  $this->mOptionsLoaded = true;
5097 
5098  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5099  }
5100 
5106  protected function saveOptions() {
5107  $this->loadOptions();
5108 
5109  // Not using getOptions(), to keep hidden preferences in database
5110  $saveOptions = $this->mOptions;
5111 
5112  // Allow hooks to abort, for instance to save to a global profile.
5113  // Reset options to default state before saving.
5114  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5115  return;
5116  }
5117 
5118  $userId = $this->getId();
5119 
5120  $insert_rows = []; // all the new preference rows
5121  foreach ( $saveOptions as $key => $value ) {
5122  // Don't bother storing default values
5123  $defaultOption = self::getDefaultOption( $key );
5124  if ( ( $defaultOption === null && $value !== false && $value !== null )
5125  || $value != $defaultOption
5126  ) {
5127  $insert_rows[] = [
5128  'up_user' => $userId,
5129  'up_property' => $key,
5130  'up_value' => $value,
5131  ];
5132  }
5133  }
5134 
5135  $dbw = wfGetDB( DB_MASTER );
5136 
5137  $res = $dbw->select( 'user_properties',
5138  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5139 
5140  // Find prior rows that need to be removed or updated. These rows will
5141  // all be deleted (the later so that INSERT IGNORE applies the new values).
5142  $keysDelete = [];
5143  foreach ( $res as $row ) {
5144  if ( !isset( $saveOptions[$row->up_property] )
5145  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5146  ) {
5147  $keysDelete[] = $row->up_property;
5148  }
5149  }
5150 
5151  if ( count( $keysDelete ) ) {
5152  // Do the DELETE by PRIMARY KEY for prior rows.
5153  // In the past a very large portion of calls to this function are for setting
5154  // 'rememberpassword' for new accounts (a preference that has since been removed).
5155  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5156  // caused gap locks on [max user ID,+infinity) which caused high contention since
5157  // updates would pile up on each other as they are for higher (newer) user IDs.
5158  // It might not be necessary these days, but it shouldn't hurt either.
5159  $dbw->delete( 'user_properties',
5160  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5161  }
5162  // Insert the new preference rows
5163  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5164  }
5165 
5172  public static function getPasswordFactory() {
5173  wfDeprecated( __METHOD__, '1.27' );
5174  $ret = new PasswordFactory();
5175  $ret->init( RequestContext::getMain()->getConfig() );
5176  return $ret;
5177  }
5178 
5203  public static function passwordChangeInputAttribs() {
5204  global $wgMinimalPasswordLength;
5205 
5206  if ( $wgMinimalPasswordLength == 0 ) {
5207  return [];
5208  }
5209 
5210  # Note that the pattern requirement will always be satisfied if the
5211  # input is empty, so we need required in all cases.
5212 
5213  # @todo FIXME: Bug 23769: This needs to not claim the password is required
5214  # if e-mail confirmation is being used. Since HTML5 input validation
5215  # is b0rked anyway in some browsers, just return nothing. When it's
5216  # re-enabled, fix this code to not output required for e-mail
5217  # registration.
5218  # $ret = array( 'required' );
5219  $ret = [];
5220 
5221  # We can't actually do this right now, because Opera 9.6 will print out
5222  # the entered password visibly in its error message! When other
5223  # browsers add support for this attribute, or Opera fixes its support,
5224  # we can add support with a version check to avoid doing this on Opera
5225  # versions where it will be a problem. Reported to Opera as
5226  # DSK-262266, but they don't have a public bug tracker for us to follow.
5227  /*
5228  if ( $wgMinimalPasswordLength > 1 ) {
5229  $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}';
5230  $ret['title'] = wfMessage( 'passwordtooshort' )
5231  ->numParams( $wgMinimalPasswordLength )->text();
5232  }
5233  */
5234 
5235  return $ret;
5236  }
5237 
5243  public static function selectFields() {
5244  return [
5245  'user_id',
5246  'user_name',
5247  'user_real_name',
5248  'user_email',
5249  'user_touched',
5250  'user_token',
5251  'user_email_authenticated',
5252  'user_email_token',
5253  'user_email_token_expires',
5254  'user_registration',
5255  'user_editcount',
5256  ];
5257  }
5258 
5266  static function newFatalPermissionDeniedStatus( $permission ) {
5267  global $wgLang;
5268 
5269  $groups = array_map(
5270  [ 'User', 'makeGroupLinkWiki' ],
5271  User::getGroupsWithPermission( $permission )
5272  );
5273 
5274  if ( $groups ) {
5275  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5276  } else {
5277  return Status::newFatal( 'badaccess-group0' );
5278  }
5279  }
5280 
5290  public function getInstanceForUpdate() {
5291  if ( !$this->getId() ) {
5292  return null; // anon
5293  }
5294 
5295  $user = self::newFromId( $this->getId() );
5296  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5297  return null;
5298  }
5299 
5300  return $user;
5301  }
5302 
5310  public function equals( User $user ) {
5311  return $this->getName() === $user->getName();
5312  }
5313 }
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:1398
getEmail()
Get the user's e-mail address.
Definition: User.php:2629
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:2046
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:2621
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:2807
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2170
isAllowedAll()
Definition: User.php:3395
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:4101
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:4223
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:4192
isBlockedFrom($title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition: User.php:1936
$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:4111
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:1488
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:4073
$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:3729
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3521
isDnsBlacklisted($ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1655
static getImplicitGroups()
Get a list of implicit groups.
Definition: User.php:4675
saveSettings()
Save this user's settings into the database.
Definition: User.php:3767
static isLocallyBlockedProxy($ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1723
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:4482
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:3380
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4064
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:3631
getAutomaticGroups($recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3199
pingLimiter($action= 'edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1778
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:2333
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:2376
deleteNewtalk($field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2260
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:2235
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:1988
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:1957
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:2721
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:1448
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:4825
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3587
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3362
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:2444
checkNewtalk($field, $id)
Internal uncached check for new messages.
Definition: User.php:2220
setPassword($str)
Set the password and reset the random token.
Definition: User.php:2464
static createNew($name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:3866
invalidationTokenUrl($token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4327
addNewUserLogEntry($action=false, $reason= '')
Add a newuser log entry for this user.
Definition: User.php:5017
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:810
setName($str)
Set the user name.
Definition: User.php:2115
array $mFormerGroups
Definition: User.php:281
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2424
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:2776
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:4209
setToken($token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2591
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:4710
static makeGroupLinkWiki($group, $text= '')
Create a link to the group in Wikitext, if available; else return the group name. ...
Definition: User.php:4730
setOption($oname, $val)
Set the given option for a user.
Definition: User.php:2835
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2088
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:5243
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:2666
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:2526
static isEveryoneAllowed($right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4574
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:2124
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4646
resetTokenFromOption($oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:2884
loadOptions($data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5040
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:2918
getIntOption($oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2819
sendMail($subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4274
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:4658
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:4429
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:4042
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:2611
static crypt($password, $salt=false)
Make a new-style password hash.
Definition: User.php:4958
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:1924
sendConfirmationMail($type= 'created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4235
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:4389
static comparePasswords($hash, $password, $userId=false)
Compare a password hash with a plain-text password.
Definition: User.php:4977
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4468
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:2280
static groupHasPermission($group, $role)
Check, if the given group has the given permission.
Definition: User.php:4554
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:5029
getPassword()
Definition: User.php:2435
static changeableByGroup($group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4752
isAllowed($action= '')
Internal mechanics of testing a permission.
Definition: User.php:3410
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:3370
inDnsBlacklist($ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1676
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:3025
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:2390
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:3739
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:4453
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:49
incEditCount()
Deferred version of incEditCountImmediate()
Definition: User.php:4864
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2312
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:2941
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:3652
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4400
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:1562
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:4691
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:3090
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:4023
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:4345
setInternalPassword($str)
Set the password and reset the random token unconditionally.
Definition: User.php:2476
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:2904
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:3474
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:3423
$wgPasswordSender
Sender email address for e-mail notifications.
isBlocked($bFromSlave=true)
Check if user is blocked.
Definition: User.php:1914
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:2748
static getGroupName($group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:4623
$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:4375
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1368
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5106
idForName($flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:3831
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:5203
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:2079
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3109
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3457
$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:3672
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:2132
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:3165
__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:4635
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:4318
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:3322
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:4010
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3444
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:3178
getId()
Get the user's ID.
Definition: User.php:2063
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2639
addToDatabase()
Add this existing user object to the database.
Definition: User.php:3937
bool $mAllowUsertalk
Definition: User.php:300
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2359
getTokenFromOption($oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:2856
removeWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3505
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4358
getGlobalBlock($ip= '')
Check if user is blocked on all wikis.
Definition: User.php:2002
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:2649
initEditCount($add=0)
Initialize user_editcount from data out of the revision table.
Definition: User.php:4914
bool $mHideName
Definition: User.php:287
static getPasswordFactory()
Lazily instantiate and return a factory object for making passwords.
Definition: User.php:5172
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:3252
getUserPage()
Get this user's personal page title.
Definition: User.php:4082
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5290
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:3432
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:3693
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3070
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:1975
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:2193
$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:4182
static newFatalPermissionDeniedStatus($permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5266
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:4160
isLocked()
Check if user account is locked.
Definition: User.php:2031
$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:1903
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4415
equals(User $user)
Checks if two user objects point to the same user.
Definition: User.php:5310
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:2733
addWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3488
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:4144
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:1513
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:1547
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:4504
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:4091
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:2553
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:1753
static getRightDescription($right)
Get the description of a given right.
Definition: User.php:4943
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:4531
static newGood($value=null)
Factory function for good results.
Definition: Status.php:101
getRights()
Get the permissions this user has.
Definition: User.php:3124
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3228
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:1966
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:2488
incEditCountImmediate()
Increment the user's edit-count field.
Definition: User.php:4875
int $mId
Cache variables.
Definition: User.php:209
addGroup($group)
Add the user to the given group.
Definition: User.php:3282
$wgUser
Definition: Setup.php:794
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4300
getTouched()
Get the user touched timestamp.
Definition: User.php:2402
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