MediaWiki  1.27.4
User.php
Go to the documentation of this file.
1 <?php
29 
35 define( 'EDIT_TOKEN_SUFFIX', Token::SUFFIX );
36 
47 class User implements IDBAccessObject {
51  const TOKEN_LENGTH = 32;
52 
56  const INVALID_TOKEN = '*** INVALID ***';
57 
64 
68  const VERSION = 10;
69 
75 
79  const CHECK_USER_RIGHTS = true;
80 
84  const IGNORE_USER_RIGHTS = false;
85 
92  protected static $mCacheVars = [
93  // user table
94  'mId',
95  'mName',
96  'mRealName',
97  'mEmail',
98  'mTouched',
99  'mToken',
100  'mEmailAuthenticated',
101  'mEmailToken',
102  'mEmailTokenExpires',
103  'mRegistration',
104  'mEditCount',
105  // user_groups table
106  'mGroups',
107  // user_properties table
108  'mOptionOverrides',
109  ];
110 
117  protected static $mCoreRights = [
118  'apihighlimits',
119  'applychangetags',
120  'autoconfirmed',
121  'autocreateaccount',
122  'autopatrol',
123  'bigdelete',
124  'block',
125  'blockemail',
126  'bot',
127  'browsearchive',
128  'changetags',
129  'createaccount',
130  'createpage',
131  'createtalk',
132  'delete',
133  'deletedhistory',
134  'deletedtext',
135  'deletelogentry',
136  'deleterevision',
137  'edit',
138  'editcontentmodel',
139  'editinterface',
140  'editprotected',
141  'editmyoptions',
142  'editmyprivateinfo',
143  'editmyusercss',
144  'editmyuserjs',
145  'editmywatchlist',
146  'editsemiprotected',
147  'editusercssjs', # deprecated
148  'editusercss',
149  'edituserjs',
150  'hideuser',
151  'import',
152  'importupload',
153  'ipblock-exempt',
154  'managechangetags',
155  'markbotedits',
156  'mergehistory',
157  'minoredit',
158  'move',
159  'movefile',
160  'move-categorypages',
161  'move-rootuserpages',
162  'move-subpages',
163  'nominornewtalk',
164  'noratelimit',
165  'override-export-depth',
166  'pagelang',
167  'passwordreset',
168  'patrol',
169  'patrolmarks',
170  'protect',
171  'purge',
172  'read',
173  'reupload',
174  'reupload-own',
175  'reupload-shared',
176  'rollback',
177  'sendemail',
178  'siteadmin',
179  'suppressionlog',
180  'suppressredirect',
181  'suppressrevision',
182  'unblockself',
183  'undelete',
184  'unwatchedpages',
185  'upload',
186  'upload_by_url',
187  'userrights',
188  'userrights-interwiki',
189  'viewmyprivateinfo',
190  'viewmywatchlist',
191  'viewsuppressed',
192  'writeapi',
193  ];
194 
198  protected static $mAllRights = false;
199 
204  protected static $inProcessCache;
205 
207  // @{
209  public $mId;
211  public $mName;
213  public $mRealName;
214 
216  public $mEmail;
218  public $mTouched;
220  protected $mQuickTouched;
222  protected $mToken;
226  protected $mEmailToken;
230  protected $mRegistration;
232  protected $mEditCount;
234  public $mGroups;
236  protected $mOptionOverrides;
237  // @}
238 
242  // @{
244 
248  protected $mLoadedItems = [];
249  // @}
250 
260  public $mFrom;
261 
265  protected $mNewtalk;
267  protected $mDatePreference;
269  public $mBlockedby;
271  protected $mHash;
273  public $mRights;
275  protected $mBlockreason;
277  protected $mEffectiveGroups;
279  protected $mImplicitGroups;
281  protected $mFormerGroups;
283  protected $mGlobalBlock;
285  protected $mLocked;
287  public $mHideName;
289  public $mOptions;
290 
294  private $mRequest;
295 
297  public $mBlock;
298 
300  protected $mAllowUsertalk;
301 
303  private $mBlockedFromCreateAccount = false;
304 
306  protected $queryFlagsUsed = self::READ_NORMAL;
307 
308  public static $idCacheByName = [];
309 
320  public function __construct() {
321  $this->clearInstanceCache( 'defaults' );
322  }
323 
327  public function __toString() {
328  return (string)$this->getName();
329  }
330 
345  public function isSafeToLoad() {
347 
348  // The user is safe to load if:
349  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
350  // * mLoadedItems === true (already loaded)
351  // * mFrom !== 'session' (sessions not involved at all)
352 
353  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
354  $this->mLoadedItems === true || $this->mFrom !== 'session';
355  }
356 
362  public function load( $flags = self::READ_NORMAL ) {
364 
365  if ( $this->mLoadedItems === true ) {
366  return;
367  }
368 
369  // Set it now to avoid infinite recursion in accessors
370  $oldLoadedItems = $this->mLoadedItems;
371  $this->mLoadedItems = true;
372  $this->queryFlagsUsed = $flags;
373 
374  // If this is called too early, things are likely to break.
375  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
377  ->warning( 'User::loadFromSession called before the end of Setup.php', [
378  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
379  ] );
380  $this->loadDefaults();
381  $this->mLoadedItems = $oldLoadedItems;
382  return;
383  }
384 
385  switch ( $this->mFrom ) {
386  case 'defaults':
387  $this->loadDefaults();
388  break;
389  case 'name':
390  // Make sure this thread sees its own changes
391  if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
392  $flags |= self::READ_LATEST;
393  $this->queryFlagsUsed = $flags;
394  }
395 
396  $this->mId = self::idFromName( $this->mName, $flags );
397  if ( !$this->mId ) {
398  // Nonexistent user placeholder object
399  $this->loadDefaults( $this->mName );
400  } else {
401  $this->loadFromId( $flags );
402  }
403  break;
404  case 'id':
405  $this->loadFromId( $flags );
406  break;
407  case 'session':
408  if ( !$this->loadFromSession() ) {
409  // Loading from session failed. Load defaults.
410  $this->loadDefaults();
411  }
412  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
413  break;
414  default:
415  throw new UnexpectedValueException(
416  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
417  }
418  }
419 
425  public function loadFromId( $flags = self::READ_NORMAL ) {
426  if ( $this->mId == 0 ) {
427  $this->loadDefaults();
428  return false;
429  }
430 
431  // Try cache (unless this needs data from the master DB).
432  // NOTE: if this thread called saveSettings(), the cache was cleared.
433  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
434  if ( $latest || !$this->loadFromCache() ) {
435  wfDebug( "User: cache miss for user {$this->mId}\n" );
436  // Load from DB (make sure this thread sees its own changes)
437  if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
438  $flags |= self::READ_LATEST;
439  }
440  if ( !$this->loadFromDatabase( $flags ) ) {
441  // Can't load from ID, user is anonymous
442  return false;
443  }
444  $this->saveToCache();
445  }
446 
447  $this->mLoadedItems = true;
448  $this->queryFlagsUsed = $flags;
449 
450  return true;
451  }
452 
458  public static function purge( $wikiId, $userId ) {
460  $processCache = self::getInProcessCache();
461  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
462  $cache->delete( $key );
463  $processCache->delete( $key );
464  }
465 
471  protected function getCacheKey( WANObjectCache $cache ) {
472  return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId );
473  }
474 
479  protected static function getInProcessCache() {
480  if ( !self::$inProcessCache ) {
481  self::$inProcessCache = new HashBagOStuff( [ 'maxKeys' => 10 ] );
482  }
483  return self::$inProcessCache;
484  }
485 
492  protected function loadFromCache() {
493  if ( $this->mId == 0 ) {
494  $this->loadDefaults();
495  return false;
496  }
497 
499  $processCache = self::getInProcessCache();
500  $key = $this->getCacheKey( $cache );
501  $data = $processCache->get( $key );
502  if ( !is_array( $data ) ) {
503  $data = $cache->get( $key );
504  if ( !is_array( $data ) || $data['mVersion'] < self::VERSION ) {
505  // Object is expired
506  return false;
507  }
508  $processCache->set( $key, $data );
509  }
510  wfDebug( "User: got user {$this->mId} from cache\n" );
511 
512  // Restore from cache
513  foreach ( self::$mCacheVars as $name ) {
514  $this->$name = $data[$name];
515  }
516 
517  return true;
518  }
519 
525  public function saveToCache() {
526  $this->load();
527  $this->loadGroups();
528  $this->loadOptions();
529 
530  if ( $this->isAnon() ) {
531  // Anonymous users are uncached
532  return;
533  }
534 
535  $data = [];
536  foreach ( self::$mCacheVars as $name ) {
537  $data[$name] = $this->$name;
538  }
539  $data['mVersion'] = self::VERSION;
541 
543  $processCache = self::getInProcessCache();
544  $key = $this->getCacheKey( $cache );
545  $cache->set( $key, $data, $cache::TTL_HOUR, $opts );
546  $processCache->set( $key, $data );
547  }
548 
550  // @{
551 
568  public static function newFromName( $name, $validate = 'valid' ) {
569  if ( $validate === true ) {
570  $validate = 'valid';
571  }
572  $name = self::getCanonicalName( $name, $validate );
573  if ( $name === false ) {
574  return false;
575  } else {
576  // Create unloaded user object
577  $u = new User;
578  $u->mName = $name;
579  $u->mFrom = 'name';
580  $u->setItemLoaded( 'name' );
581  return $u;
582  }
583  }
584 
591  public static function newFromId( $id ) {
592  $u = new User;
593  $u->mId = $id;
594  $u->mFrom = 'id';
595  $u->setItemLoaded( 'id' );
596  return $u;
597  }
598 
610  public static function newFromConfirmationCode( $code, $flags = 0 ) {
611  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
612  ? wfGetDB( DB_MASTER )
613  : wfGetDB( DB_SLAVE );
614 
615  $id = $db->selectField(
616  'user',
617  'user_id',
618  [
619  'user_email_token' => md5( $code ),
620  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
621  ]
622  );
623 
624  return $id ? User::newFromId( $id ) : null;
625  }
626 
634  public static function newFromSession( WebRequest $request = null ) {
635  $user = new User;
636  $user->mFrom = 'session';
637  $user->mRequest = $request;
638  return $user;
639  }
640 
655  public static function newFromRow( $row, $data = null ) {
656  $user = new User;
657  $user->loadFromRow( $row, $data );
658  return $user;
659  }
660 
695  public static function newSystemUser( $name, $options = [] ) {
696  $options += [
697  'validate' => 'valid',
698  'create' => true,
699  'steal' => false,
700  ];
701 
702  $name = self::getCanonicalName( $name, $options['validate'] );
703  if ( $name === false ) {
704  return null;
705  }
706 
707  $fields = self::selectFields();
708 
709  $dbw = wfGetDB( DB_MASTER );
710  $row = $dbw->selectRow(
711  'user',
712  $fields,
713  [ 'user_name' => $name ],
714  __METHOD__
715  );
716  if ( !$row ) {
717  // No user. Create it?
718  return $options['create'] ? self::createNew( $name ) : null;
719  }
720  $user = self::newFromRow( $row );
721 
722  // A user is considered to exist as a non-system user if it can
723  // authenticate, or has an email set, or has a non-invalid token.
724  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
725  AuthManager::singleton()->userCanAuthenticate( $name )
726  ) {
727  // User exists. Steal it?
728  if ( !$options['steal'] ) {
729  return null;
730  }
731 
732  AuthManager::singleton()->revokeAccessForUser( $name );
733 
734  $user->invalidateEmail();
735  $user->mToken = self::INVALID_TOKEN;
736  $user->saveSettings();
737  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
738  }
739 
740  return $user;
741  }
742 
743  // @}
744 
750  public static function whoIs( $id ) {
751  return UserCache::singleton()->getProp( $id, 'name' );
752  }
753 
760  public static function whoIsReal( $id ) {
761  return UserCache::singleton()->getProp( $id, 'real_name' );
762  }
763 
770  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
772  if ( is_null( $nt ) ) {
773  // Illegal name
774  return null;
775  }
776 
777  if ( !( $flags & self::READ_LATEST ) && isset( self::$idCacheByName[$name] ) ) {
778  return self::$idCacheByName[$name];
779  }
780 
781  $db = ( $flags & self::READ_LATEST )
782  ? wfGetDB( DB_MASTER )
783  : wfGetDB( DB_SLAVE );
784 
785  $s = $db->selectRow(
786  'user',
787  [ 'user_id' ],
788  [ 'user_name' => $nt->getText() ],
789  __METHOD__
790  );
791 
792  if ( $s === false ) {
793  $result = null;
794  } else {
795  $result = $s->user_id;
796  }
797 
798  self::$idCacheByName[$name] = $result;
799 
800  if ( count( self::$idCacheByName ) > 1000 ) {
801  self::$idCacheByName = [];
802  }
803 
804  return $result;
805  }
806 
810  public static function resetIdByNameCache() {
811  self::$idCacheByName = [];
812  }
813 
830  public static function isIP( $name ) {
831  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
832  || IP::isIPv6( $name );
833  }
834 
846  public static function isValidUserName( $name ) {
847  global $wgContLang, $wgMaxNameChars;
848 
849  if ( $name == ''
850  || User::isIP( $name )
851  || strpos( $name, '/' ) !== false
852  || strlen( $name ) > $wgMaxNameChars
853  || $name != $wgContLang->ucfirst( $name )
854  ) {
855  return false;
856  }
857 
858  // Ensure that the name can't be misresolved as a different title,
859  // such as with extra namespace keys at the start.
860  $parsed = Title::newFromText( $name );
861  if ( is_null( $parsed )
862  || $parsed->getNamespace()
863  || strcmp( $name, $parsed->getPrefixedText() ) ) {
864  return false;
865  }
866 
867  // Check an additional blacklist of troublemaker characters.
868  // Should these be merged into the title char list?
869  $unicodeBlacklist = '/[' .
870  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
871  '\x{00a0}' . # non-breaking space
872  '\x{2000}-\x{200f}' . # various whitespace
873  '\x{2028}-\x{202f}' . # breaks and control chars
874  '\x{3000}' . # ideographic space
875  '\x{e000}-\x{f8ff}' . # private use
876  ']/u';
877  if ( preg_match( $unicodeBlacklist, $name ) ) {
878  return false;
879  }
880 
881  return true;
882  }
883 
895  public static function isUsableName( $name ) {
896  global $wgReservedUsernames;
897  // Must be a valid username, obviously ;)
898  if ( !self::isValidUserName( $name ) ) {
899  return false;
900  }
901 
902  static $reservedUsernames = false;
903  if ( !$reservedUsernames ) {
904  $reservedUsernames = $wgReservedUsernames;
905  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
906  }
907 
908  // Certain names may be reserved for batch processes.
909  foreach ( $reservedUsernames as $reserved ) {
910  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
911  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
912  }
913  if ( $reserved == $name ) {
914  return false;
915  }
916  }
917  return true;
918  }
919 
932  public static function isCreatableName( $name ) {
933  global $wgInvalidUsernameCharacters;
934 
935  // Ensure that the username isn't longer than 235 bytes, so that
936  // (at least for the builtin skins) user javascript and css files
937  // will work. (bug 23080)
938  if ( strlen( $name ) > 235 ) {
939  wfDebugLog( 'username', __METHOD__ .
940  ": '$name' invalid due to length" );
941  return false;
942  }
943 
944  // Preg yells if you try to give it an empty string
945  if ( $wgInvalidUsernameCharacters !== '' ) {
946  if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
947  wfDebugLog( 'username', __METHOD__ .
948  ": '$name' invalid due to wgInvalidUsernameCharacters" );
949  return false;
950  }
951  }
952 
953  return self::isUsableName( $name );
954  }
955 
962  public function isValidPassword( $password ) {
963  // simple boolean wrapper for getPasswordValidity
964  return $this->getPasswordValidity( $password ) === true;
965  }
966 
973  public function getPasswordValidity( $password ) {
974  $result = $this->checkPasswordValidity( $password );
975  if ( $result->isGood() ) {
976  return true;
977  } else {
978  $messages = [];
979  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
980  $messages[] = $error['message'];
981  }
982  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
983  $messages[] = $warning['message'];
984  }
985  if ( count( $messages ) === 1 ) {
986  return $messages[0];
987  }
988  return $messages;
989  }
990  }
991 
1010  public function checkPasswordValidity( $password, $purpose = 'login' ) {
1011  global $wgPasswordPolicy;
1012 
1013  $upp = new UserPasswordPolicy(
1014  $wgPasswordPolicy['policies'],
1015  $wgPasswordPolicy['checks']
1016  );
1017 
1019  $result = false; // init $result to false for the internal checks
1020 
1021  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1022  $status->error( $result );
1023  return $status;
1024  }
1025 
1026  if ( $result === false ) {
1027  $status->merge( $upp->checkUserPassword( $this, $password, $purpose ) );
1028  return $status;
1029  } elseif ( $result === true ) {
1030  return $status;
1031  } else {
1032  $status->error( $result );
1033  return $status; // the isValidPassword hook set a string $result and returned true
1034  }
1035  }
1036 
1050  public static function getCanonicalName( $name, $validate = 'valid' ) {
1051  // Force usernames to capital
1053  $name = $wgContLang->ucfirst( $name );
1054 
1055  # Reject names containing '#'; these will be cleaned up
1056  # with title normalisation, but then it's too late to
1057  # check elsewhere
1058  if ( strpos( $name, '#' ) !== false ) {
1059  return false;
1060  }
1061 
1062  // Clean up name according to title rules,
1063  // but only when validation is requested (bug 12654)
1064  $t = ( $validate !== false ) ?
1066  // Check for invalid titles
1067  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1068  return false;
1069  }
1070 
1071  // Reject various classes of invalid names
1072  $name = AuthManager::callLegacyAuthPlugin(
1073  'getCanonicalName', [ $t->getText() ], $t->getText()
1074  );
1075 
1076  switch ( $validate ) {
1077  case false:
1078  break;
1079  case 'valid':
1080  if ( !User::isValidUserName( $name ) ) {
1081  $name = false;
1082  }
1083  break;
1084  case 'usable':
1085  if ( !User::isUsableName( $name ) ) {
1086  $name = false;
1087  }
1088  break;
1089  case 'creatable':
1090  if ( !User::isCreatableName( $name ) ) {
1091  $name = false;
1092  }
1093  break;
1094  default:
1095  throw new InvalidArgumentException(
1096  'Invalid parameter value for $validate in ' . __METHOD__ );
1097  }
1098  return $name;
1099  }
1100 
1109  public static function edits( $uid ) {
1110  wfDeprecated( __METHOD__, '1.21' );
1111  $user = self::newFromId( $uid );
1112  return $user->getEditCount();
1113  }
1114 
1121  public static function randomPassword() {
1122  global $wgMinimalPasswordLength;
1123  return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1124  }
1125 
1134  public function loadDefaults( $name = false ) {
1135  $this->mId = 0;
1136  $this->mName = $name;
1137  $this->mRealName = '';
1138  $this->mEmail = '';
1139  $this->mOptionOverrides = null;
1140  $this->mOptionsLoaded = false;
1141 
1142  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1143  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1144  if ( $loggedOut !== 0 ) {
1145  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1146  } else {
1147  $this->mTouched = '1'; # Allow any pages to be cached
1148  }
1149 
1150  $this->mToken = null; // Don't run cryptographic functions till we need a token
1151  $this->mEmailAuthenticated = null;
1152  $this->mEmailToken = '';
1153  $this->mEmailTokenExpires = null;
1154  $this->mRegistration = wfTimestamp( TS_MW );
1155  $this->mGroups = [];
1156 
1157  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1158  }
1159 
1172  public function isItemLoaded( $item, $all = 'all' ) {
1173  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1174  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1175  }
1176 
1182  protected function setItemLoaded( $item ) {
1183  if ( is_array( $this->mLoadedItems ) ) {
1184  $this->mLoadedItems[$item] = true;
1185  }
1186  }
1187 
1193  private function loadFromSession() {
1194  // Deprecated hook
1195  $result = null;
1196  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1197  if ( $result !== null ) {
1198  return $result;
1199  }
1200 
1201  // MediaWiki\Session\Session already did the necessary authentication of the user
1202  // returned here, so just use it if applicable.
1203  $session = $this->getRequest()->getSession();
1204  $user = $session->getUser();
1205  if ( $user->isLoggedIn() ) {
1206  $this->loadFromUserObject( $user );
1207  // Other code expects these to be set in the session, so set them.
1208  $session->set( 'wsUserID', $this->getId() );
1209  $session->set( 'wsUserName', $this->getName() );
1210  $session->set( 'wsToken', $this->getToken() );
1211  return true;
1212  }
1213 
1214  return false;
1215  }
1216 
1224  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1225  // Paranoia
1226  $this->mId = intval( $this->mId );
1227 
1228  // Anonymous user
1229  if ( !$this->mId ) {
1230  $this->loadDefaults();
1231  return false;
1232  }
1233 
1235  $db = wfGetDB( $index );
1236 
1237  $s = $db->selectRow(
1238  'user',
1239  self::selectFields(),
1240  [ 'user_id' => $this->mId ],
1241  __METHOD__,
1242  $options
1243  );
1244 
1245  $this->queryFlagsUsed = $flags;
1246  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1247 
1248  if ( $s !== false ) {
1249  // Initialise user table data
1250  $this->loadFromRow( $s );
1251  $this->mGroups = null; // deferred
1252  $this->getEditCount(); // revalidation for nulls
1253  return true;
1254  } else {
1255  // Invalid user_id
1256  $this->mId = 0;
1257  $this->loadDefaults();
1258  return false;
1259  }
1260  }
1261 
1271  protected function loadFromRow( $row, $data = null ) {
1272  $all = true;
1273 
1274  $this->mGroups = null; // deferred
1275 
1276  if ( isset( $row->user_name ) ) {
1277  $this->mName = $row->user_name;
1278  $this->mFrom = 'name';
1279  $this->setItemLoaded( 'name' );
1280  } else {
1281  $all = false;
1282  }
1283 
1284  if ( isset( $row->user_real_name ) ) {
1285  $this->mRealName = $row->user_real_name;
1286  $this->setItemLoaded( 'realname' );
1287  } else {
1288  $all = false;
1289  }
1290 
1291  if ( isset( $row->user_id ) ) {
1292  $this->mId = intval( $row->user_id );
1293  $this->mFrom = 'id';
1294  $this->setItemLoaded( 'id' );
1295  } else {
1296  $all = false;
1297  }
1298 
1299  if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
1300  self::$idCacheByName[$row->user_name] = $row->user_id;
1301  }
1302 
1303  if ( isset( $row->user_editcount ) ) {
1304  $this->mEditCount = $row->user_editcount;
1305  } else {
1306  $all = false;
1307  }
1308 
1309  if ( isset( $row->user_touched ) ) {
1310  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1311  } else {
1312  $all = false;
1313  }
1314 
1315  if ( isset( $row->user_token ) ) {
1316  // The definition for the column is binary(32), so trim the NULs
1317  // that appends. The previous definition was char(32), so trim
1318  // spaces too.
1319  $this->mToken = rtrim( $row->user_token, " \0" );
1320  if ( $this->mToken === '' ) {
1321  $this->mToken = null;
1322  }
1323  } else {
1324  $all = false;
1325  }
1326 
1327  if ( isset( $row->user_email ) ) {
1328  $this->mEmail = $row->user_email;
1329  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1330  $this->mEmailToken = $row->user_email_token;
1331  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1332  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1333  } else {
1334  $all = false;
1335  }
1336 
1337  if ( $all ) {
1338  $this->mLoadedItems = true;
1339  }
1340 
1341  if ( is_array( $data ) ) {
1342  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1343  $this->mGroups = $data['user_groups'];
1344  }
1345  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1346  $this->loadOptions( $data['user_properties'] );
1347  }
1348  }
1349  }
1350 
1356  protected function loadFromUserObject( $user ) {
1357  $user->load();
1358  foreach ( self::$mCacheVars as $var ) {
1359  $this->$var = $user->$var;
1360  }
1361  }
1362 
1366  private function loadGroups() {
1367  if ( is_null( $this->mGroups ) ) {
1368  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1369  ? wfGetDB( DB_MASTER )
1370  : wfGetDB( DB_SLAVE );
1371  $res = $db->select( 'user_groups',
1372  [ 'ug_group' ],
1373  [ 'ug_user' => $this->mId ],
1374  __METHOD__ );
1375  $this->mGroups = [];
1376  foreach ( $res as $row ) {
1377  $this->mGroups[] = $row->ug_group;
1378  }
1379  }
1380  }
1381 
1396  public function addAutopromoteOnceGroups( $event ) {
1397  global $wgAutopromoteOnceLogInRC;
1398 
1399  if ( wfReadOnly() || !$this->getId() ) {
1400  return [];
1401  }
1402 
1403  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1404  if ( !count( $toPromote ) ) {
1405  return [];
1406  }
1407 
1408  if ( !$this->checkAndSetTouched() ) {
1409  return []; // raced out (bug T48834)
1410  }
1411 
1412  $oldGroups = $this->getGroups(); // previous groups
1413  foreach ( $toPromote as $group ) {
1414  $this->addGroup( $group );
1415  }
1416  // update groups in external authentication database
1417  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false ] );
1418  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1419 
1420  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1421 
1422  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1423  $logEntry->setPerformer( $this );
1424  $logEntry->setTarget( $this->getUserPage() );
1425  $logEntry->setParameters( [
1426  '4::oldgroups' => $oldGroups,
1427  '5::newgroups' => $newGroups,
1428  ] );
1429  $logid = $logEntry->insert();
1430  if ( $wgAutopromoteOnceLogInRC ) {
1431  $logEntry->publish( $logid );
1432  }
1433 
1434  return $toPromote;
1435  }
1436 
1446  protected function checkAndSetTouched() {
1447  $this->load();
1448 
1449  if ( !$this->mId ) {
1450  return false; // anon
1451  }
1452 
1453  // Get a new user_touched that is higher than the old one
1454  $oldTouched = $this->mTouched;
1455  $newTouched = $this->newTouchedTimestamp();
1456 
1457  $dbw = wfGetDB( DB_MASTER );
1458  $dbw->update( 'user',
1459  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1460  [
1461  'user_id' => $this->mId,
1462  'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
1463  ],
1464  __METHOD__
1465  );
1466  $success = ( $dbw->affectedRows() > 0 );
1467 
1468  if ( $success ) {
1469  $this->mTouched = $newTouched;
1470  $this->clearSharedCache();
1471  } else {
1472  // Clears on failure too since that is desired if the cache is stale
1473  $this->clearSharedCache( 'refresh' );
1474  }
1475 
1476  return $success;
1477  }
1478 
1486  public function clearInstanceCache( $reloadFrom = false ) {
1487  $this->mNewtalk = -1;
1488  $this->mDatePreference = null;
1489  $this->mBlockedby = -1; # Unset
1490  $this->mHash = false;
1491  $this->mRights = null;
1492  $this->mEffectiveGroups = null;
1493  $this->mImplicitGroups = null;
1494  $this->mGroups = null;
1495  $this->mOptions = null;
1496  $this->mOptionsLoaded = false;
1497  $this->mEditCount = null;
1498 
1499  if ( $reloadFrom ) {
1500  $this->mLoadedItems = [];
1501  $this->mFrom = $reloadFrom;
1502  }
1503  }
1504 
1511  public static function getDefaultOptions() {
1512  global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
1513 
1514  static $defOpt = null;
1515  if ( !defined( 'MW_PHPUNIT_TEST' ) && $defOpt !== null ) {
1516  // Disabling this for the unit tests, as they rely on being able to change $wgContLang
1517  // mid-request and see that change reflected in the return value of this function.
1518  // Which is insane and would never happen during normal MW operation
1519  return $defOpt;
1520  }
1521 
1522  $defOpt = $wgDefaultUserOptions;
1523  // Default language setting
1524  $defOpt['language'] = $wgContLang->getCode();
1525  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1526  $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
1527  }
1528  $namespaces = MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
1529  foreach ( $namespaces as $nsnum => $nsname ) {
1530  $defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
1531  }
1532  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1533 
1534  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1535 
1536  return $defOpt;
1537  }
1538 
1545  public static function getDefaultOption( $opt ) {
1546  $defOpts = self::getDefaultOptions();
1547  if ( isset( $defOpts[$opt] ) ) {
1548  return $defOpts[$opt];
1549  } else {
1550  return null;
1551  }
1552  }
1553 
1560  private function getBlockedStatus( $bFromSlave = true ) {
1561  global $wgProxyWhitelist, $wgUser, $wgApplyIpBlocksToXff;
1562 
1563  if ( -1 != $this->mBlockedby ) {
1564  return;
1565  }
1566 
1567  wfDebug( __METHOD__ . ": checking...\n" );
1568 
1569  // Initialize data...
1570  // Otherwise something ends up stomping on $this->mBlockedby when
1571  // things get lazy-loaded later, causing false positive block hits
1572  // due to -1 !== 0. Probably session-related... Nothing should be
1573  // overwriting mBlockedby, surely?
1574  $this->load();
1575 
1576  # We only need to worry about passing the IP address to the Block generator if the
1577  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1578  # know which IP address they're actually coming from
1579  $ip = null;
1580  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1581  // $wgUser->getName() only works after the end of Setup.php. Until
1582  // then, assume it's a logged-out user.
1583  $globalUserName = $wgUser->isSafeToLoad()
1584  ? $wgUser->getName()
1585  : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1586  if ( $this->getName() === $globalUserName ) {
1587  $ip = $this->getRequest()->getIP();
1588  }
1589  }
1590 
1591  // User/IP blocking
1592  $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1593 
1594  // Proxy blocking
1595  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1596  // Local list
1597  if ( self::isLocallyBlockedProxy( $ip ) ) {
1598  $block = new Block;
1599  $block->setBlocker( wfMessage( 'proxyblocker' )->text() );
1600  $block->mReason = wfMessage( 'proxyblockreason' )->text();
1601  $block->setTarget( $ip );
1602  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1603  $block = new Block;
1604  $block->setBlocker( wfMessage( 'sorbs' )->text() );
1605  $block->mReason = wfMessage( 'sorbsreason' )->text();
1606  $block->setTarget( $ip );
1607  }
1608  }
1609 
1610  // (bug 23343) Apply IP blocks to the contents of XFF headers, if enabled
1611  if ( !$block instanceof Block
1612  && $wgApplyIpBlocksToXff
1613  && $ip !== null
1614  && !in_array( $ip, $wgProxyWhitelist )
1615  ) {
1616  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1617  $xff = array_map( 'trim', explode( ',', $xff ) );
1618  $xff = array_diff( $xff, [ $ip ] );
1619  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1620  $block = Block::chooseBlock( $xffblocks, $xff );
1621  if ( $block instanceof Block ) {
1622  # Mangle the reason to alert the user that the block
1623  # originated from matching the X-Forwarded-For header.
1624  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
1625  }
1626  }
1627 
1628  if ( $block instanceof Block ) {
1629  wfDebug( __METHOD__ . ": Found block.\n" );
1630  $this->mBlock = $block;
1631  $this->mBlockedby = $block->getByName();
1632  $this->mBlockreason = $block->mReason;
1633  $this->mHideName = $block->mHideName;
1634  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1635  } else {
1636  $this->mBlockedby = '';
1637  $this->mHideName = 0;
1638  $this->mAllowUsertalk = false;
1639  }
1640 
1641  // Avoid PHP 7.1 warning of passing $this by reference
1642  $user = $this;
1643  // Extensions
1644  Hooks::run( 'GetBlockedStatus', [ &$user ] );
1645  }
1646 
1654  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1655  global $wgEnableDnsBlacklist, $wgDnsBlacklistUrls, $wgProxyWhitelist;
1656 
1657  if ( !$wgEnableDnsBlacklist ) {
1658  return false;
1659  }
1660 
1661  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
1662  return false;
1663  }
1664 
1665  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
1666  }
1667 
1675  public function inDnsBlacklist( $ip, $bases ) {
1676 
1677  $found = false;
1678  // @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
1679  if ( IP::isIPv4( $ip ) ) {
1680  // Reverse IP, bug 21255
1681  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1682 
1683  foreach ( (array)$bases as $base ) {
1684  // Make hostname
1685  // If we have an access key, use that too (ProjectHoneypot, etc.)
1686  $basename = $base;
1687  if ( is_array( $base ) ) {
1688  if ( count( $base ) >= 2 ) {
1689  // Access key is 1, base URL is 0
1690  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1691  } else {
1692  $host = "$ipReversed.{$base[0]}";
1693  }
1694  $basename = $base[0];
1695  } else {
1696  $host = "$ipReversed.$base";
1697  }
1698 
1699  // Send query
1700  $ipList = gethostbynamel( $host );
1701 
1702  if ( $ipList ) {
1703  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1704  $found = true;
1705  break;
1706  } else {
1707  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1708  }
1709  }
1710  }
1711 
1712  return $found;
1713  }
1714 
1722  public static function isLocallyBlockedProxy( $ip ) {
1723  global $wgProxyList;
1724 
1725  if ( !$wgProxyList ) {
1726  return false;
1727  }
1728 
1729  if ( !is_array( $wgProxyList ) ) {
1730  // Load from the specified file
1731  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1732  }
1733 
1734  if ( !is_array( $wgProxyList ) ) {
1735  $ret = false;
1736  } elseif ( array_search( $ip, $wgProxyList ) !== false ) {
1737  $ret = true;
1738  } elseif ( array_key_exists( $ip, $wgProxyList ) ) {
1739  // Old-style flipped proxy list
1740  $ret = true;
1741  } else {
1742  $ret = false;
1743  }
1744  return $ret;
1745  }
1746 
1752  public function isPingLimitable() {
1753  global $wgRateLimitsExcludedIPs;
1754  if ( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1755  // No other good way currently to disable rate limits
1756  // for specific IPs. :P
1757  // But this is a crappy hack and should die.
1758  return false;
1759  }
1760  return !$this->isAllowed( 'noratelimit' );
1761  }
1762 
1777  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1778  // Avoid PHP 7.1 warning of passing $this by reference
1779  $user = $this;
1780  // Call the 'PingLimiter' hook
1781  $result = false;
1782  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1783  return $result;
1784  }
1785 
1786  global $wgRateLimits;
1787  if ( !isset( $wgRateLimits[$action] ) ) {
1788  return false;
1789  }
1790 
1791  // Some groups shouldn't trigger the ping limiter, ever
1792  if ( !$this->isPingLimitable() ) {
1793  return false;
1794  }
1795 
1796  $limits = $wgRateLimits[$action];
1797  $keys = [];
1798  $id = $this->getId();
1799  $userLimit = false;
1800  $isNewbie = $this->isNewbie();
1801 
1802  if ( $id == 0 ) {
1803  // limits for anons
1804  if ( isset( $limits['anon'] ) ) {
1805  $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1806  }
1807  } else {
1808  // limits for logged-in users
1809  if ( isset( $limits['user'] ) ) {
1810  $userLimit = $limits['user'];
1811  }
1812  // limits for newbie logged-in users
1813  if ( $isNewbie && isset( $limits['newbie'] ) ) {
1814  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
1815  }
1816  }
1817 
1818  // limits for anons and for newbie logged-in users
1819  if ( $isNewbie ) {
1820  // ip-based limits
1821  if ( isset( $limits['ip'] ) ) {
1822  $ip = $this->getRequest()->getIP();
1823  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1824  }
1825  // subnet-based limits
1826  if ( isset( $limits['subnet'] ) ) {
1827  $ip = $this->getRequest()->getIP();
1828  $subnet = IP::getSubnet( $ip );
1829  if ( $subnet !== false ) {
1830  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1831  }
1832  }
1833  }
1834 
1835  // Check for group-specific permissions
1836  // If more than one group applies, use the group with the highest limit ratio (max/period)
1837  foreach ( $this->getGroups() as $group ) {
1838  if ( isset( $limits[$group] ) ) {
1839  if ( $userLimit === false
1840  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1841  ) {
1842  $userLimit = $limits[$group];
1843  }
1844  }
1845  }
1846 
1847  // Set the user limit key
1848  if ( $userLimit !== false ) {
1849  list( $max, $period ) = $userLimit;
1850  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
1851  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit;
1852  }
1853 
1854  // ip-based limits for all ping-limitable users
1855  if ( isset( $limits['ip-all'] ) ) {
1856  $ip = $this->getRequest()->getIP();
1857  // ignore if user limit is more permissive
1858  if ( $isNewbie || $userLimit === false
1859  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1860  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
1861  }
1862  }
1863 
1864  // subnet-based limits for all ping-limitable users
1865  if ( isset( $limits['subnet-all'] ) ) {
1866  $ip = $this->getRequest()->getIP();
1867  $subnet = IP::getSubnet( $ip );
1868  if ( $subnet !== false ) {
1869  // ignore if user limit is more permissive
1870  if ( $isNewbie || $userLimit === false
1871  || $limits['ip-all'][0] / $limits['ip-all'][1]
1872  > $userLimit[0] / $userLimit[1] ) {
1873  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
1874  }
1875  }
1876  }
1877 
1879 
1880  $triggered = false;
1881  foreach ( $keys as $key => $limit ) {
1882  list( $max, $period ) = $limit;
1883  $summary = "(limit $max in {$period}s)";
1884  $count = $cache->get( $key );
1885  // Already pinged?
1886  if ( $count ) {
1887  if ( $count >= $max ) {
1888  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
1889  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
1890  $triggered = true;
1891  } else {
1892  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
1893  }
1894  } else {
1895  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
1896  if ( $incrBy > 0 ) {
1897  $cache->add( $key, 0, intval( $period ) ); // first ping
1898  }
1899  }
1900  if ( $incrBy > 0 ) {
1901  $cache->incr( $key, $incrBy );
1902  }
1903  }
1904 
1905  return $triggered;
1906  }
1907 
1915  public function isBlocked( $bFromSlave = true ) {
1916  return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
1917  }
1918 
1925  public function getBlock( $bFromSlave = true ) {
1926  $this->getBlockedStatus( $bFromSlave );
1927  return $this->mBlock instanceof Block ? $this->mBlock : null;
1928  }
1929 
1937  public function isBlockedFrom( $title, $bFromSlave = false ) {
1938  global $wgBlockAllowsUTEdit;
1939 
1940  $blocked = $this->isBlocked( $bFromSlave );
1941  $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
1942  // If a user's name is suppressed, they cannot make edits anywhere
1943  if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
1944  && $title->getNamespace() == NS_USER_TALK ) {
1945  $blocked = false;
1946  wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
1947  }
1948 
1949  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
1950 
1951  return $blocked;
1952  }
1953 
1958  public function blockedBy() {
1959  $this->getBlockedStatus();
1960  return $this->mBlockedby;
1961  }
1962 
1967  public function blockedFor() {
1968  $this->getBlockedStatus();
1969  return $this->mBlockreason;
1970  }
1971 
1976  public function getBlockId() {
1977  $this->getBlockedStatus();
1978  return ( $this->mBlock ? $this->mBlock->getId() : false );
1979  }
1980 
1989  public function isBlockedGlobally( $ip = '' ) {
1990  return $this->getGlobalBlock( $ip ) instanceof Block;
1991  }
1992 
2003  public function getGlobalBlock( $ip = '' ) {
2004  if ( $this->mGlobalBlock !== null ) {
2005  return $this->mGlobalBlock ?: null;
2006  }
2007  // User is already an IP?
2008  if ( IP::isIPAddress( $this->getName() ) ) {
2009  $ip = $this->getName();
2010  } elseif ( !$ip ) {
2011  $ip = $this->getRequest()->getIP();
2012  }
2013  // Avoid PHP 7.1 warning of passing $this by reference
2014  $user = $this;
2015  $blocked = false;
2016  $block = null;
2017  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2018 
2019  if ( $blocked && $block === null ) {
2020  // back-compat: UserIsBlockedGlobally didn't have $block param first
2021  $block = new Block;
2022  $block->setTarget( $ip );
2023  }
2024 
2025  $this->mGlobalBlock = $blocked ? $block : false;
2026  return $this->mGlobalBlock ?: null;
2027  }
2028 
2034  public function isLocked() {
2035  if ( $this->mLocked !== null ) {
2036  return $this->mLocked;
2037  }
2038  // Avoid PHP 7.1 warning of passing $this by reference
2039  $user = $this;
2040  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2041  $this->mLocked = $authUser && $authUser->isLocked();
2042  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2043  return $this->mLocked;
2044  }
2045 
2051  public function isHidden() {
2052  if ( $this->mHideName !== null ) {
2053  return $this->mHideName;
2054  }
2055  $this->getBlockedStatus();
2056  if ( !$this->mHideName ) {
2057  // Avoid PHP 7.1 warning of passing $this by reference
2058  $user = $this;
2059  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2060  $this->mHideName = $authUser && $authUser->isHidden();
2061  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2062  }
2063  return $this->mHideName;
2064  }
2065 
2070  public function getId() {
2071  if ( $this->mId === null && $this->mName !== null && User::isIP( $this->mName ) ) {
2072  // Special case, we know the user is anonymous
2073  return 0;
2074  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2075  // Don't load if this was initialized from an ID
2076  $this->load();
2077  }
2078 
2079  return (int)$this->mId;
2080  }
2081 
2086  public function setId( $v ) {
2087  $this->mId = $v;
2088  $this->clearInstanceCache( 'id' );
2089  }
2090 
2095  public function getName() {
2096  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2097  // Special case optimisation
2098  return $this->mName;
2099  } else {
2100  $this->load();
2101  if ( $this->mName === false ) {
2102  // Clean up IPs
2103  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2104  }
2105  return $this->mName;
2106  }
2107  }
2108 
2122  public function setName( $str ) {
2123  $this->load();
2124  $this->mName = $str;
2125  }
2126 
2131  public function getTitleKey() {
2132  return str_replace( ' ', '_', $this->getName() );
2133  }
2134 
2139  public function getNewtalk() {
2140  $this->load();
2141 
2142  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2143  if ( $this->mNewtalk === -1 ) {
2144  $this->mNewtalk = false; # reset talk page status
2145 
2146  // Check memcached separately for anons, who have no
2147  // entire User object stored in there.
2148  if ( !$this->mId ) {
2149  global $wgDisableAnonTalk;
2150  if ( $wgDisableAnonTalk ) {
2151  // Anon newtalk disabled by configuration.
2152  $this->mNewtalk = false;
2153  } else {
2154  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2155  }
2156  } else {
2157  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2158  }
2159  }
2160 
2161  return (bool)$this->mNewtalk;
2162  }
2163 
2177  public function getNewMessageLinks() {
2178  // Avoid PHP 7.1 warning of passing $this by reference
2179  $user = $this;
2180  $talks = [];
2181  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2182  return $talks;
2183  } elseif ( !$this->getNewtalk() ) {
2184  return [];
2185  }
2186  $utp = $this->getTalkPage();
2187  $dbr = wfGetDB( DB_SLAVE );
2188  // Get the "last viewed rev" timestamp from the oldest message notification
2189  $timestamp = $dbr->selectField( 'user_newtalk',
2190  'MIN(user_last_timestamp)',
2191  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2192  __METHOD__ );
2194  return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2195  }
2196 
2202  public function getNewMessageRevisionId() {
2203  $newMessageRevisionId = null;
2204  $newMessageLinks = $this->getNewMessageLinks();
2205  if ( $newMessageLinks ) {
2206  // Note: getNewMessageLinks() never returns more than a single link
2207  // and it is always for the same wiki, but we double-check here in
2208  // case that changes some time in the future.
2209  if ( count( $newMessageLinks ) === 1
2210  && $newMessageLinks[0]['wiki'] === wfWikiID()
2211  && $newMessageLinks[0]['rev']
2212  ) {
2214  $newMessageRevision = $newMessageLinks[0]['rev'];
2215  $newMessageRevisionId = $newMessageRevision->getId();
2216  }
2217  }
2218  return $newMessageRevisionId;
2219  }
2220 
2229  protected function checkNewtalk( $field, $id ) {
2230  $dbr = wfGetDB( DB_SLAVE );
2231 
2232  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2233 
2234  return $ok !== false;
2235  }
2236 
2244  protected function updateNewtalk( $field, $id, $curRev = null ) {
2245  // Get timestamp of the talk page revision prior to the current one
2246  $prevRev = $curRev ? $curRev->getPrevious() : false;
2247  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2248  // Mark the user as having new messages since this revision
2249  $dbw = wfGetDB( DB_MASTER );
2250  $dbw->insert( 'user_newtalk',
2251  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2252  __METHOD__,
2253  'IGNORE' );
2254  if ( $dbw->affectedRows() ) {
2255  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2256  return true;
2257  } else {
2258  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2259  return false;
2260  }
2261  }
2262 
2269  protected function deleteNewtalk( $field, $id ) {
2270  $dbw = wfGetDB( DB_MASTER );
2271  $dbw->delete( 'user_newtalk',
2272  [ $field => $id ],
2273  __METHOD__ );
2274  if ( $dbw->affectedRows() ) {
2275  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2276  return true;
2277  } else {
2278  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2279  return false;
2280  }
2281  }
2282 
2289  public function setNewtalk( $val, $curRev = null ) {
2290  if ( wfReadOnly() ) {
2291  return;
2292  }
2293 
2294  $this->load();
2295  $this->mNewtalk = $val;
2296 
2297  if ( $this->isAnon() ) {
2298  $field = 'user_ip';
2299  $id = $this->getName();
2300  } else {
2301  $field = 'user_id';
2302  $id = $this->getId();
2303  }
2304 
2305  if ( $val ) {
2306  $changed = $this->updateNewtalk( $field, $id, $curRev );
2307  } else {
2308  $changed = $this->deleteNewtalk( $field, $id );
2309  }
2310 
2311  if ( $changed ) {
2312  $this->invalidateCache();
2313  }
2314  }
2315 
2321  private function newTouchedTimestamp() {
2323 
2324  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2325  if ( $this->mTouched && $time <= $this->mTouched ) {
2326  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2327  }
2328 
2329  return $time;
2330  }
2331 
2342  public function clearSharedCache( $mode = 'changed' ) {
2343  if ( !$this->getId() ) {
2344  return;
2345  }
2346 
2348  $processCache = self::getInProcessCache();
2349  $key = $this->getCacheKey( $cache );
2350  if ( $mode === 'refresh' ) {
2351  $cache->delete( $key, 1 );
2352  $processCache->delete( $key );
2353  } else {
2354  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
2355  function() use ( $cache, $processCache, $key ) {
2356  $cache->delete( $key );
2357  $processCache->delete( $key );
2358  }
2359  );
2360  }
2361  }
2362 
2368  public function invalidateCache() {
2369  $this->touch();
2370  $this->clearSharedCache();
2371  }
2372 
2385  public function touch() {
2386  $id = $this->getId();
2387  if ( $id ) {
2388  $key = wfMemcKey( 'user-quicktouched', 'id', $id );
2389  ObjectCache::getMainWANInstance()->touchCheckKey( $key );
2390  $this->mQuickTouched = null;
2391  }
2392  }
2393 
2399  public function validateCache( $timestamp ) {
2400  return ( $timestamp >= $this->getTouched() );
2401  }
2402 
2411  public function getTouched() {
2412  $this->load();
2413 
2414  if ( $this->mId ) {
2415  if ( $this->mQuickTouched === null ) {
2416  $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
2418 
2419  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2420  }
2421 
2422  return max( $this->mTouched, $this->mQuickTouched );
2423  }
2424 
2425  return $this->mTouched;
2426  }
2427 
2433  public function getDBTouched() {
2434  $this->load();
2435 
2436  return $this->mTouched;
2437  }
2438 
2444  public function getPassword() {
2445  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2446  }
2447 
2453  public function getTemporaryPassword() {
2454  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2455  }
2456 
2473  public function setPassword( $str ) {
2474  return $this->setPasswordInternal( $str );
2475  }
2476 
2485  public function setInternalPassword( $str ) {
2486  $this->setPasswordInternal( $str );
2487  }
2488 
2497  private function setPasswordInternal( $str ) {
2498  $manager = AuthManager::singleton();
2499 
2500  // If the user doesn't exist yet, fail
2501  if ( !$manager->userExists( $this->getName() ) ) {
2502  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2503  }
2504 
2505  $status = $this->changeAuthenticationData( [
2506  'username' => $this->getName(),
2507  'password' => $str,
2508  'retype' => $str,
2509  ] );
2510  if ( !$status->isGood() ) {
2512  ->info( __METHOD__ . ': Password change rejected: '
2513  . $status->getWikiText( null, null, 'en' ) );
2514  return false;
2515  }
2516 
2517  $this->setOption( 'watchlisttoken', false );
2518  SessionManager::singleton()->invalidateSessionsForUser( $this );
2519 
2520  return true;
2521  }
2522 
2535  public function changeAuthenticationData( array $data ) {
2536  $manager = AuthManager::singleton();
2537  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2538  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2539 
2540  $status = Status::newGood( 'ignored' );
2541  foreach ( $reqs as $req ) {
2542  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2543  }
2544  if ( $status->getValue() === 'ignored' ) {
2545  $status->warning( 'authenticationdatachange-ignored' );
2546  }
2547 
2548  if ( $status->isGood() ) {
2549  foreach ( $reqs as $req ) {
2550  $manager->changeAuthenticationData( $req );
2551  }
2552  }
2553  return $status;
2554  }
2555 
2562  public function getToken( $forceCreation = true ) {
2563  global $wgAuthenticationTokenVersion;
2564 
2565  $this->load();
2566  if ( !$this->mToken && $forceCreation ) {
2567  $this->setToken();
2568  }
2569 
2570  if ( !$this->mToken ) {
2571  // The user doesn't have a token, return null to indicate that.
2572  return null;
2573  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2574  // We return a random value here so existing token checks are very
2575  // likely to fail.
2576  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2577  } elseif ( $wgAuthenticationTokenVersion === null ) {
2578  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2579  return $this->mToken;
2580  } else {
2581  // $wgAuthenticationTokenVersion in use, so hmac it.
2582  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2583 
2584  // The raw hash can be overly long. Shorten it up.
2585  $len = max( 32, self::TOKEN_LENGTH );
2586  if ( strlen( $ret ) < $len ) {
2587  // Should never happen, even md5 is 128 bits
2588  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2589  }
2590  return substr( $ret, -$len );
2591  }
2592  }
2593 
2600  public function setToken( $token = false ) {
2601  $this->load();
2602  if ( $this->mToken === self::INVALID_TOKEN ) {
2604  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2605  } elseif ( !$token ) {
2606  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2607  } else {
2608  $this->mToken = $token;
2609  }
2610  }
2611 
2620  public function setNewpassword( $str, $throttle = true ) {
2621  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2622  }
2623 
2630  public function isPasswordReminderThrottled() {
2631  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2632  }
2633 
2638  public function getEmail() {
2639  $this->load();
2640  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2641  return $this->mEmail;
2642  }
2643 
2649  $this->load();
2650  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2652  }
2653 
2658  public function setEmail( $str ) {
2659  $this->load();
2660  if ( $str == $this->mEmail ) {
2661  return;
2662  }
2663  $this->invalidateEmail();
2664  $this->mEmail = $str;
2665  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2666  }
2667 
2675  public function setEmailWithConfirmation( $str ) {
2677 
2678  if ( !$wgEnableEmail ) {
2679  return Status::newFatal( 'emaildisabled' );
2680  }
2681 
2682  $oldaddr = $this->getEmail();
2683  if ( $str === $oldaddr ) {
2684  return Status::newGood( true );
2685  }
2686 
2687  $type = $oldaddr != '' ? 'changed' : 'set';
2688  $notificationResult = null;
2689 
2690  if ( $wgEmailAuthentication ) {
2691  // Send the user an email notifying the user of the change in registered
2692  // email address on their previous email address
2693  if ( $type == 'changed' ) {
2694  $change = $str != '' ? 'changed' : 'removed';
2695  $notificationResult = $this->sendMail(
2696  wfMessage( 'notificationemail_subject_' . $change )->text(),
2697  wfMessage( 'notificationemail_body_' . $change,
2698  $this->getRequest()->getIP(),
2699  $this->getName(),
2700  $str )->text()
2701  );
2702  }
2703  }
2704 
2705  $this->setEmail( $str );
2706 
2707  if ( $str !== '' && $wgEmailAuthentication ) {
2708  // Send a confirmation request to the new address if needed
2709  $result = $this->sendConfirmationMail( $type );
2710 
2711  if ( $notificationResult !== null ) {
2712  $result->merge( $notificationResult );
2713  }
2714 
2715  if ( $result->isGood() ) {
2716  // Say to the caller that a confirmation and notification mail has been sent
2717  $result->value = 'eauth';
2718  }
2719  } else {
2720  $result = Status::newGood( true );
2721  }
2722 
2723  return $result;
2724  }
2725 
2730  public function getRealName() {
2731  if ( !$this->isItemLoaded( 'realname' ) ) {
2732  $this->load();
2733  }
2734 
2735  return $this->mRealName;
2736  }
2737 
2742  public function setRealName( $str ) {
2743  $this->load();
2744  $this->mRealName = $str;
2745  }
2746 
2757  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2758  global $wgHiddenPrefs;
2759  $this->loadOptions();
2760 
2761  # We want 'disabled' preferences to always behave as the default value for
2762  # users, even if they have set the option explicitly in their settings (ie they
2763  # set it, and then it was disabled removing their ability to change it). But
2764  # we don't want to erase the preferences in the database in case the preference
2765  # is re-enabled again. So don't touch $mOptions, just override the returned value
2766  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2767  return self::getDefaultOption( $oname );
2768  }
2769 
2770  if ( array_key_exists( $oname, $this->mOptions ) ) {
2771  return $this->mOptions[$oname];
2772  } else {
2773  return $defaultOverride;
2774  }
2775  }
2776 
2785  public function getOptions( $flags = 0 ) {
2786  global $wgHiddenPrefs;
2787  $this->loadOptions();
2789 
2790  # We want 'disabled' preferences to always behave as the default value for
2791  # users, even if they have set the option explicitly in their settings (ie they
2792  # set it, and then it was disabled removing their ability to change it). But
2793  # we don't want to erase the preferences in the database in case the preference
2794  # is re-enabled again. So don't touch $mOptions, just override the returned value
2795  foreach ( $wgHiddenPrefs as $pref ) {
2796  $default = self::getDefaultOption( $pref );
2797  if ( $default !== null ) {
2798  $options[$pref] = $default;
2799  }
2800  }
2801 
2802  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2803  $options = array_diff_assoc( $options, self::getDefaultOptions() );
2804  }
2805 
2806  return $options;
2807  }
2808 
2816  public function getBoolOption( $oname ) {
2817  return (bool)$this->getOption( $oname );
2818  }
2819 
2828  public function getIntOption( $oname, $defaultOverride = 0 ) {
2829  $val = $this->getOption( $oname );
2830  if ( $val == '' ) {
2831  $val = $defaultOverride;
2832  }
2833  return intval( $val );
2834  }
2835 
2844  public function setOption( $oname, $val ) {
2845  $this->loadOptions();
2846 
2847  // Explicitly NULL values should refer to defaults
2848  if ( is_null( $val ) ) {
2849  $val = self::getDefaultOption( $oname );
2850  }
2851 
2852  $this->mOptions[$oname] = $val;
2853  }
2854 
2865  public function getTokenFromOption( $oname ) {
2866  global $wgHiddenPrefs;
2867 
2868  $id = $this->getId();
2869  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2870  return false;
2871  }
2872 
2873  $token = $this->getOption( $oname );
2874  if ( !$token ) {
2875  // Default to a value based on the user token to avoid space
2876  // wasted on storing tokens for all users. When this option
2877  // is set manually by the user, only then is it stored.
2878  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2879  }
2880 
2881  return $token;
2882  }
2883 
2893  public function resetTokenFromOption( $oname ) {
2894  global $wgHiddenPrefs;
2895  if ( in_array( $oname, $wgHiddenPrefs ) ) {
2896  return false;
2897  }
2898 
2899  $token = MWCryptRand::generateHex( 40 );
2900  $this->setOption( $oname, $token );
2901  return $token;
2902  }
2903 
2927  public static function listOptionKinds() {
2928  return [
2929  'registered',
2930  'registered-multiselect',
2931  'registered-checkmatrix',
2932  'userjs',
2933  'special',
2934  'unused'
2935  ];
2936  }
2937 
2950  public function getOptionKinds( IContextSource $context, $options = null ) {
2951  $this->loadOptions();
2952  if ( $options === null ) {
2954  }
2955 
2956  $prefs = Preferences::getPreferences( $this, $context );
2957  $mapping = [];
2958 
2959  // Pull out the "special" options, so they don't get converted as
2960  // multiselect or checkmatrix.
2961  $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
2962  foreach ( $specialOptions as $name => $value ) {
2963  unset( $prefs[$name] );
2964  }
2965 
2966  // Multiselect and checkmatrix options are stored in the database with
2967  // one key per option, each having a boolean value. Extract those keys.
2968  $multiselectOptions = [];
2969  foreach ( $prefs as $name => $info ) {
2970  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
2971  ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
2972  $opts = HTMLFormField::flattenOptions( $info['options'] );
2973  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2974 
2975  foreach ( $opts as $value ) {
2976  $multiselectOptions["$prefix$value"] = true;
2977  }
2978 
2979  unset( $prefs[$name] );
2980  }
2981  }
2982  $checkmatrixOptions = [];
2983  foreach ( $prefs as $name => $info ) {
2984  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
2985  ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
2986  $columns = HTMLFormField::flattenOptions( $info['columns'] );
2987  $rows = HTMLFormField::flattenOptions( $info['rows'] );
2988  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2989 
2990  foreach ( $columns as $column ) {
2991  foreach ( $rows as $row ) {
2992  $checkmatrixOptions["$prefix$column-$row"] = true;
2993  }
2994  }
2995 
2996  unset( $prefs[$name] );
2997  }
2998  }
2999 
3000  // $value is ignored
3001  foreach ( $options as $key => $value ) {
3002  if ( isset( $prefs[$key] ) ) {
3003  $mapping[$key] = 'registered';
3004  } elseif ( isset( $multiselectOptions[$key] ) ) {
3005  $mapping[$key] = 'registered-multiselect';
3006  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3007  $mapping[$key] = 'registered-checkmatrix';
3008  } elseif ( isset( $specialOptions[$key] ) ) {
3009  $mapping[$key] = 'special';
3010  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3011  $mapping[$key] = 'userjs';
3012  } else {
3013  $mapping[$key] = 'unused';
3014  }
3015  }
3016 
3017  return $mapping;
3018  }
3019 
3034  public function resetOptions(
3035  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3036  IContextSource $context = null
3037  ) {
3038  $this->load();
3039  $defaultOptions = self::getDefaultOptions();
3040 
3041  if ( !is_array( $resetKinds ) ) {
3042  $resetKinds = [ $resetKinds ];
3043  }
3044 
3045  if ( in_array( 'all', $resetKinds ) ) {
3046  $newOptions = $defaultOptions;
3047  } else {
3048  if ( $context === null ) {
3050  }
3051 
3052  $optionKinds = $this->getOptionKinds( $context );
3053  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3054  $newOptions = [];
3055 
3056  // Use default values for the options that should be deleted, and
3057  // copy old values for the ones that shouldn't.
3058  foreach ( $this->mOptions as $key => $value ) {
3059  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3060  if ( array_key_exists( $key, $defaultOptions ) ) {
3061  $newOptions[$key] = $defaultOptions[$key];
3062  }
3063  } else {
3064  $newOptions[$key] = $value;
3065  }
3066  }
3067  }
3068 
3069  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3070 
3071  $this->mOptions = $newOptions;
3072  $this->mOptionsLoaded = true;
3073  }
3074 
3079  public function getDatePreference() {
3080  // Important migration for old data rows
3081  if ( is_null( $this->mDatePreference ) ) {
3082  global $wgLang;
3083  $value = $this->getOption( 'date' );
3084  $map = $wgLang->getDatePreferenceMigrationMap();
3085  if ( isset( $map[$value] ) ) {
3086  $value = $map[$value];
3087  }
3088  $this->mDatePreference = $value;
3089  }
3090  return $this->mDatePreference;
3091  }
3092 
3099  public function requiresHTTPS() {
3100  global $wgSecureLogin;
3101  if ( !$wgSecureLogin ) {
3102  return false;
3103  } else {
3104  $https = $this->getBoolOption( 'prefershttps' );
3105  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3106  if ( $https ) {
3107  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3108  }
3109  return $https;
3110  }
3111  }
3112 
3118  public function getStubThreshold() {
3119  global $wgMaxArticleSize; # Maximum article size, in Kb
3120  $threshold = $this->getIntOption( 'stubthreshold' );
3121  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3122  // If they have set an impossible value, disable the preference
3123  // so we can use the parser cache again.
3124  $threshold = 0;
3125  }
3126  return $threshold;
3127  }
3128 
3133  public function getRights() {
3134  if ( is_null( $this->mRights ) ) {
3135  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3136  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3137 
3138  // Deny any rights denied by the user's session, unless this
3139  // endpoint has no sessions.
3140  if ( !defined( 'MW_NO_SESSION' ) ) {
3141  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3142  if ( $allowedRights !== null ) {
3143  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3144  }
3145  }
3146 
3147  // Force reindexation of rights when a hook has unset one of them
3148  $this->mRights = array_values( array_unique( $this->mRights ) );
3149 
3150  // If block disables login, we should also remove any
3151  // extra rights blocked users might have, in case the
3152  // blocked user has a pre-existing session (T129738).
3153  // This is checked here for cases where people only call
3154  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3155  // to give a better error message in the common case.
3156  $config = RequestContext::getMain()->getConfig();
3157  if (
3158  $this->isLoggedIn() &&
3159  $config->get( 'BlockDisablesLogin' ) &&
3160  $this->isBlocked()
3161  ) {
3162  $anon = new User;
3163  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3164  }
3165  }
3166  return $this->mRights;
3167  }
3168 
3174  public function getGroups() {
3175  $this->load();
3176  $this->loadGroups();
3177  return $this->mGroups;
3178  }
3179 
3187  public function getEffectiveGroups( $recache = false ) {
3188  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3189  $this->mEffectiveGroups = array_unique( array_merge(
3190  $this->getGroups(), // explicit groups
3191  $this->getAutomaticGroups( $recache ) // implicit groups
3192  ) );
3193  // Avoid PHP 7.1 warning of passing $this by reference
3194  $user = $this;
3195  // Hook for additional groups
3196  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3197  // Force reindexation of groups when a hook has unset one of them
3198  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3199  }
3200  return $this->mEffectiveGroups;
3201  }
3202 
3210  public function getAutomaticGroups( $recache = false ) {
3211  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3212  $this->mImplicitGroups = [ '*' ];
3213  if ( $this->getId() ) {
3214  $this->mImplicitGroups[] = 'user';
3215 
3216  $this->mImplicitGroups = array_unique( array_merge(
3217  $this->mImplicitGroups,
3219  ) );
3220  }
3221  if ( $recache ) {
3222  // Assure data consistency with rights/groups,
3223  // as getEffectiveGroups() depends on this function
3224  $this->mEffectiveGroups = null;
3225  }
3226  }
3227  return $this->mImplicitGroups;
3228  }
3229 
3239  public function getFormerGroups() {
3240  $this->load();
3241 
3242  if ( is_null( $this->mFormerGroups ) ) {
3243  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3244  ? wfGetDB( DB_MASTER )
3245  : wfGetDB( DB_SLAVE );
3246  $res = $db->select( 'user_former_groups',
3247  [ 'ufg_group' ],
3248  [ 'ufg_user' => $this->mId ],
3249  __METHOD__ );
3250  $this->mFormerGroups = [];
3251  foreach ( $res as $row ) {
3252  $this->mFormerGroups[] = $row->ufg_group;
3253  }
3254  }
3255 
3256  return $this->mFormerGroups;
3257  }
3258 
3263  public function getEditCount() {
3264  if ( !$this->getId() ) {
3265  return null;
3266  }
3267 
3268  if ( $this->mEditCount === null ) {
3269  /* Populate the count, if it has not been populated yet */
3270  $dbr = wfGetDB( DB_SLAVE );
3271  // check if the user_editcount field has been initialized
3272  $count = $dbr->selectField(
3273  'user', 'user_editcount',
3274  [ 'user_id' => $this->mId ],
3275  __METHOD__
3276  );
3277 
3278  if ( $count === null ) {
3279  // it has not been initialized. do so.
3280  $count = $this->initEditCount();
3281  }
3282  $this->mEditCount = $count;
3283  }
3284  return (int)$this->mEditCount;
3285  }
3286 
3293  public function addGroup( $group ) {
3294  $this->load();
3295 
3296  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group ] ) ) {
3297  return false;
3298  }
3299 
3300  $dbw = wfGetDB( DB_MASTER );
3301  if ( $this->getId() ) {
3302  $dbw->insert( 'user_groups',
3303  [
3304  'ug_user' => $this->getId(),
3305  'ug_group' => $group,
3306  ],
3307  __METHOD__,
3308  [ 'IGNORE' ] );
3309  }
3310 
3311  $this->loadGroups();
3312  $this->mGroups[] = $group;
3313  // In case loadGroups was not called before, we now have the right twice.
3314  // Get rid of the duplicate.
3315  $this->mGroups = array_unique( $this->mGroups );
3316 
3317  // Refresh the groups caches, and clear the rights cache so it will be
3318  // refreshed on the next call to $this->getRights().
3319  $this->getEffectiveGroups( true );
3320  $this->mRights = null;
3321 
3322  $this->invalidateCache();
3323 
3324  return true;
3325  }
3326 
3333  public function removeGroup( $group ) {
3334  $this->load();
3335  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3336  return false;
3337  }
3338 
3339  $dbw = wfGetDB( DB_MASTER );
3340  $dbw->delete( 'user_groups',
3341  [
3342  'ug_user' => $this->getId(),
3343  'ug_group' => $group,
3344  ], __METHOD__
3345  );
3346  // Remember that the user was in this group
3347  $dbw->insert( 'user_former_groups',
3348  [
3349  'ufg_user' => $this->getId(),
3350  'ufg_group' => $group,
3351  ],
3352  __METHOD__,
3353  [ 'IGNORE' ]
3354  );
3355 
3356  $this->loadGroups();
3357  $this->mGroups = array_diff( $this->mGroups, [ $group ] );
3358 
3359  // Refresh the groups caches, and clear the rights cache so it will be
3360  // refreshed on the next call to $this->getRights().
3361  $this->getEffectiveGroups( true );
3362  $this->mRights = null;
3363 
3364  $this->invalidateCache();
3365 
3366  return true;
3367  }
3368 
3373  public function isLoggedIn() {
3374  return $this->getId() != 0;
3375  }
3376 
3381  public function isAnon() {
3382  return !$this->isLoggedIn();
3383  }
3384 
3391  public function isAllowedAny() {
3392  $permissions = func_get_args();
3393  foreach ( $permissions as $permission ) {
3394  if ( $this->isAllowed( $permission ) ) {
3395  return true;
3396  }
3397  }
3398  return false;
3399  }
3400 
3406  public function isAllowedAll() {
3407  $permissions = func_get_args();
3408  foreach ( $permissions as $permission ) {
3409  if ( !$this->isAllowed( $permission ) ) {
3410  return false;
3411  }
3412  }
3413  return true;
3414  }
3415 
3421  public function isAllowed( $action = '' ) {
3422  if ( $action === '' ) {
3423  return true; // In the spirit of DWIM
3424  }
3425  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3426  // by misconfiguration: 0 == 'foo'
3427  return in_array( $action, $this->getRights(), true );
3428  }
3429 
3434  public function useRCPatrol() {
3435  global $wgUseRCPatrol;
3436  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3437  }
3438 
3443  public function useNPPatrol() {
3444  global $wgUseRCPatrol, $wgUseNPPatrol;
3445  return (
3446  ( $wgUseRCPatrol || $wgUseNPPatrol )
3447  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3448  );
3449  }
3450 
3455  public function useFilePatrol() {
3456  global $wgUseRCPatrol, $wgUseFilePatrol;
3457  return (
3458  ( $wgUseRCPatrol || $wgUseFilePatrol )
3459  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3460  );
3461  }
3462 
3468  public function getRequest() {
3469  if ( $this->mRequest ) {
3470  return $this->mRequest;
3471  } else {
3473  return $wgRequest;
3474  }
3475  }
3476 
3485  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3486  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3487  return WatchedItemStore::getDefaultInstance()->isWatched( $this, $title );
3488  }
3489  return false;
3490  }
3491 
3499  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3500  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3501  WatchedItemStore::getDefaultInstance()->addWatchBatchForUser(
3502  $this,
3503  [ $title->getSubjectPage(), $title->getTalkPage() ]
3504  );
3505  }
3506  $this->invalidateCache();
3507  }
3508 
3516  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3517  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3518  WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getSubjectPage() );
3519  WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getTalkPage() );
3520  }
3521  $this->invalidateCache();
3522  }
3523 
3532  public function clearNotification( &$title, $oldid = 0 ) {
3533  global $wgUseEnotif, $wgShowUpdatedMarker;
3534 
3535  // Do nothing if the database is locked to writes
3536  if ( wfReadOnly() ) {
3537  return;
3538  }
3539 
3540  // Do nothing if not allowed to edit the watchlist
3541  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3542  return;
3543  }
3544 
3545  // If we're working on user's talk page, we should update the talk page message indicator
3546  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3547  // Avoid PHP 7.1 warning of passing $this by reference
3548  $user = $this;
3549  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3550  return;
3551  }
3552 
3553  // Try to update the DB post-send and only if needed...
3554  DeferredUpdates::addCallableUpdate( function() use ( $title, $oldid ) {
3555  if ( !$this->getNewtalk() ) {
3556  return; // no notifications to clear
3557  }
3558 
3559  // Delete the last notifications (they stack up)
3560  $this->setNewtalk( false );
3561 
3562  // If there is a new, unseen, revision, use its timestamp
3563  $nextid = $oldid
3564  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3565  : null;
3566  if ( $nextid ) {
3567  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3568  }
3569  } );
3570  }
3571 
3572  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3573  return;
3574  }
3575 
3576  if ( $this->isAnon() ) {
3577  // Nothing else to do...
3578  return;
3579  }
3580 
3581  // Only update the timestamp if the page is being watched.
3582  // The query to find out if it is watched is cached both in memcached and per-invocation,
3583  // and when it does have to be executed, it can be on a slave
3584  // If this is the user's newtalk page, we always update the timestamp
3585  $force = '';
3586  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3587  $force = 'force';
3588  }
3589 
3591  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3592  }
3593 
3600  public function clearAllNotifications() {
3601  if ( wfReadOnly() ) {
3602  return;
3603  }
3604 
3605  // Do nothing if not allowed to edit the watchlist
3606  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3607  return;
3608  }
3609 
3610  global $wgUseEnotif, $wgShowUpdatedMarker;
3611  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3612  $this->setNewtalk( false );
3613  return;
3614  }
3615  $id = $this->getId();
3616  if ( $id != 0 ) {
3617  $dbw = wfGetDB( DB_MASTER );
3618  $dbw->update( 'watchlist',
3619  [ /* SET */ 'wl_notificationtimestamp' => null ],
3620  [ /* WHERE */ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3621  __METHOD__
3622  );
3623  // We also need to clear here the "you have new message" notification for the own user_talk page;
3624  // it's cleared one page view later in WikiPage::doViewUpdates().
3625  }
3626  }
3627 
3644  protected function setCookie(
3645  $name, $value, $exp = 0, $secure = null, $params = [], $request = null
3646  ) {
3647  wfDeprecated( __METHOD__, '1.27' );
3648  if ( $request === null ) {
3649  $request = $this->getRequest();
3650  }
3651  $params['secure'] = $secure;
3652  $request->response()->setCookie( $name, $value, $exp, $params );
3653  }
3654 
3665  protected function clearCookie( $name, $secure = null, $params = [] ) {
3666  wfDeprecated( __METHOD__, '1.27' );
3667  $this->setCookie( $name, '', time() - 86400, $secure, $params );
3668  }
3669 
3685  protected function setExtendedLoginCookie( $name, $value, $secure ) {
3686  global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
3687 
3688  wfDeprecated( __METHOD__, '1.27' );
3689 
3690  $exp = time();
3691  $exp += $wgExtendedLoginCookieExpiration !== null
3692  ? $wgExtendedLoginCookieExpiration
3693  : $wgCookieExpiration;
3694 
3695  $this->setCookie( $name, $value, $exp, $secure );
3696  }
3697 
3706  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3707  $this->load();
3708  if ( 0 == $this->mId ) {
3709  return;
3710  }
3711 
3712  $session = $this->getRequest()->getSession();
3713  if ( $request && $session->getRequest() !== $request ) {
3714  $session = $session->sessionWithRequest( $request );
3715  }
3716  $delay = $session->delaySave();
3717 
3718  if ( !$session->getUser()->equals( $this ) ) {
3719  if ( !$session->canSetUser() ) {
3721  ->warning( __METHOD__ .
3722  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3723  );
3724  return;
3725  }
3726  $session->setUser( $this );
3727  }
3728 
3729  $session->setRememberUser( $rememberMe );
3730  if ( $secure !== null ) {
3731  $session->setForceHTTPS( $secure );
3732  }
3733 
3734  $session->persist();
3735 
3736  ScopedCallback::consume( $delay );
3737  }
3738 
3742  public function logout() {
3743  // Avoid PHP 7.1 warning of passing $this by reference
3744  $user = $this;
3745  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
3746  $this->doLogout();
3747  }
3748  }
3749 
3754  public function doLogout() {
3755  $session = $this->getRequest()->getSession();
3756  if ( !$session->canSetUser() ) {
3758  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3759  } elseif ( !$session->getUser()->equals( $this ) ) {
3761  ->warning( __METHOD__ .
3762  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3763  );
3764  // But we still may as well make this user object anon
3765  $this->clearInstanceCache( 'defaults' );
3766  } else {
3767  $this->clearInstanceCache( 'defaults' );
3768  $delay = $session->delaySave();
3769  $session->unpersist(); // Clear cookies (T127436)
3770  $session->setLoggedOutTimestamp( time() );
3771  $session->setUser( new User );
3772  $session->set( 'wsUserID', 0 ); // Other code expects this
3773  $session->resetAllTokens();
3774  ScopedCallback::consume( $delay );
3775  }
3776  }
3777 
3782  public function saveSettings() {
3783  if ( wfReadOnly() ) {
3784  // @TODO: caller should deal with this instead!
3785  // This should really just be an exception.
3787  null,
3788  "Could not update user with ID '{$this->mId}'; DB is read-only."
3789  ) );
3790  return;
3791  }
3792 
3793  $this->load();
3794  if ( 0 == $this->mId ) {
3795  return; // anon
3796  }
3797 
3798  // Get a new user_touched that is higher than the old one.
3799  // This will be used for a CAS check as a last-resort safety
3800  // check against race conditions and slave lag.
3801  $oldTouched = $this->mTouched;
3802  $newTouched = $this->newTouchedTimestamp();
3803 
3804  $dbw = wfGetDB( DB_MASTER );
3805  $dbw->update( 'user',
3806  [ /* SET */
3807  'user_name' => $this->mName,
3808  'user_real_name' => $this->mRealName,
3809  'user_email' => $this->mEmail,
3810  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3811  'user_touched' => $dbw->timestamp( $newTouched ),
3812  'user_token' => strval( $this->mToken ),
3813  'user_email_token' => $this->mEmailToken,
3814  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3815  ], [ /* WHERE */
3816  'user_id' => $this->mId,
3817  'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
3818  ], __METHOD__
3819  );
3820 
3821  if ( !$dbw->affectedRows() ) {
3822  // Maybe the problem was a missed cache update; clear it to be safe
3823  $this->clearSharedCache( 'refresh' );
3824  // User was changed in the meantime or loaded with stale data
3825  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'slave';
3826  throw new MWException(
3827  "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
3828  " the version of the user to be saved is older than the current version."
3829  );
3830  }
3831 
3832  $this->mTouched = $newTouched;
3833  $this->saveOptions();
3834 
3835  Hooks::run( 'UserSaveSettings', [ $this ] );
3836  $this->clearSharedCache();
3837  $this->getUserPage()->invalidateCache();
3838  }
3839 
3846  public function idForName( $flags = 0 ) {
3847  $s = trim( $this->getName() );
3848  if ( $s === '' ) {
3849  return 0;
3850  }
3851 
3852  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
3853  ? wfGetDB( DB_MASTER )
3854  : wfGetDB( DB_SLAVE );
3855 
3856  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
3857  ? [ 'LOCK IN SHARE MODE' ]
3858  : [];
3859 
3860  $id = $db->selectField( 'user',
3861  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
3862 
3863  return (int)$id;
3864  }
3865 
3881  public static function createNew( $name, $params = [] ) {
3882  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
3883  if ( isset( $params[$field] ) ) {
3884  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
3885  unset( $params[$field] );
3886  }
3887  }
3888 
3889  $user = new User;
3890  $user->load();
3891  $user->setToken(); // init token
3892  if ( isset( $params['options'] ) ) {
3893  $user->mOptions = $params['options'] + (array)$user->mOptions;
3894  unset( $params['options'] );
3895  }
3896  $dbw = wfGetDB( DB_MASTER );
3897  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
3898 
3899  $noPass = PasswordFactory::newInvalidPassword()->toString();
3900 
3901  $fields = [
3902  'user_id' => $seqVal,
3903  'user_name' => $name,
3904  'user_password' => $noPass,
3905  'user_newpassword' => $noPass,
3906  'user_email' => $user->mEmail,
3907  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3908  'user_real_name' => $user->mRealName,
3909  'user_token' => strval( $user->mToken ),
3910  'user_registration' => $dbw->timestamp( $user->mRegistration ),
3911  'user_editcount' => 0,
3912  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3913  ];
3914  foreach ( $params as $name => $value ) {
3915  $fields["user_$name"] = $value;
3916  }
3917  $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
3918  if ( $dbw->affectedRows() ) {
3919  $newUser = User::newFromId( $dbw->insertId() );
3920  } else {
3921  $newUser = null;
3922  }
3923  return $newUser;
3924  }
3925 
3952  public function addToDatabase() {
3953  $this->load();
3954  if ( !$this->mToken ) {
3955  $this->setToken(); // init token
3956  }
3957 
3958  $this->mTouched = $this->newTouchedTimestamp();
3959 
3960  $noPass = PasswordFactory::newInvalidPassword()->toString();
3961 
3962  $dbw = wfGetDB( DB_MASTER );
3963  $inWrite = $dbw->writesOrCallbacksPending();
3964  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
3965  $dbw->insert( 'user',
3966  [
3967  'user_id' => $seqVal,
3968  'user_name' => $this->mName,
3969  'user_password' => $noPass,
3970  'user_newpassword' => $noPass,
3971  'user_email' => $this->mEmail,
3972  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3973  'user_real_name' => $this->mRealName,
3974  'user_token' => strval( $this->mToken ),
3975  'user_registration' => $dbw->timestamp( $this->mRegistration ),
3976  'user_editcount' => 0,
3977  'user_touched' => $dbw->timestamp( $this->mTouched ),
3978  ], __METHOD__,
3979  [ 'IGNORE' ]
3980  );
3981  if ( !$dbw->affectedRows() ) {
3982  // The queries below cannot happen in the same REPEATABLE-READ snapshot.
3983  // Handle this by COMMIT, if possible, or by LOCK IN SHARE MODE otherwise.
3984  if ( $inWrite ) {
3985  // Can't commit due to pending writes that may need atomicity.
3986  // This may cause some lock contention unlike the case below.
3987  $options = [ 'LOCK IN SHARE MODE' ];
3988  $flags = self::READ_LOCKING;
3989  } else {
3990  // Often, this case happens early in views before any writes when
3991  // using CentralAuth. It's should be OK to commit and break the snapshot.
3992  $dbw->commit( __METHOD__, 'flush' );
3993  $options = [];
3994  $flags = self::READ_LATEST;
3995  }
3996  $this->mId = $dbw->selectField( 'user', 'user_id',
3997  [ 'user_name' => $this->mName ], __METHOD__, $options );
3998  $loaded = false;
3999  if ( $this->mId ) {
4000  if ( $this->loadFromDatabase( $flags ) ) {
4001  $loaded = true;
4002  }
4003  }
4004  if ( !$loaded ) {
4005  throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
4006  "to insert user '{$this->mName}' row, but it was not present in select!" );
4007  }
4008  return Status::newFatal( 'userexists' );
4009  }
4010  $this->mId = $dbw->insertId();
4011  self::$idCacheByName[$this->mName] = $this->mId;
4012 
4013  // Clear instance cache other than user table data, which is already accurate
4014  $this->clearInstanceCache();
4015 
4016  $this->saveOptions();
4017  return Status::newGood();
4018  }
4019 
4025  public function spreadAnyEditBlock() {
4026  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4027  return $this->spreadBlock();
4028  }
4029 
4030  return false;
4031  }
4032 
4038  protected function spreadBlock() {
4039  wfDebug( __METHOD__ . "()\n" );
4040  $this->load();
4041  if ( $this->mId == 0 ) {
4042  return false;
4043  }
4044 
4045  $userblock = Block::newFromTarget( $this->getName() );
4046  if ( !$userblock ) {
4047  return false;
4048  }
4049 
4050  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4051  }
4052 
4057  public function isBlockedFromCreateAccount() {
4058  $this->getBlockedStatus();
4059  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4060  return $this->mBlock;
4061  }
4062 
4063  # bug 13611: if the IP address the user is trying to create an account from is
4064  # blocked with createaccount disabled, prevent new account creation there even
4065  # when the user is logged in
4066  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4067  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4068  }
4069  return $this->mBlockedFromCreateAccount instanceof Block
4070  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4071  ? $this->mBlockedFromCreateAccount
4072  : false;
4073  }
4074 
4079  public function isBlockedFromEmailuser() {
4080  $this->getBlockedStatus();
4081  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4082  }
4083 
4088  public function isAllowedToCreateAccount() {
4089  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4090  }
4091 
4097  public function getUserPage() {
4098  return Title::makeTitle( NS_USER, $this->getName() );
4099  }
4100 
4106  public function getTalkPage() {
4107  $title = $this->getUserPage();
4108  return $title->getTalkPage();
4109  }
4110 
4116  public function isNewbie() {
4117  return !$this->isAllowed( 'autoconfirmed' );
4118  }
4119 
4126  public function checkPassword( $password ) {
4127  $manager = AuthManager::singleton();
4128  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4129  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4130  [
4131  'username' => $this->getName(),
4132  'password' => $password,
4133  ]
4134  );
4135  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4136  switch ( $res->status ) {
4137  case AuthenticationResponse::PASS:
4138  return true;
4139  case AuthenticationResponse::FAIL:
4140  // Hope it's not a PreAuthenticationProvider that failed...
4142  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4143  return false;
4144  default:
4145  throw new BadMethodCallException(
4146  'AuthManager returned a response unsupported by ' . __METHOD__
4147  );
4148  }
4149  }
4150 
4159  public function checkTemporaryPassword( $plaintext ) {
4160  // Can't check the temporary password individually.
4161  return $this->checkPassword( $plaintext );
4162  }
4163 
4175  public function getEditTokenObject( $salt = '', $request = null ) {
4176  if ( $this->isAnon() ) {
4177  return new LoggedOutEditToken();
4178  }
4179 
4180  if ( !$request ) {
4181  $request = $this->getRequest();
4182  }
4183  return $request->getSession()->getToken( $salt );
4184  }
4185 
4197  public function getEditToken( $salt = '', $request = null ) {
4198  return $this->getEditTokenObject( $salt, $request )->toString();
4199  }
4200 
4207  public static function getEditTokenTimestamp( $val ) {
4208  wfDeprecated( __METHOD__, '1.27' );
4210  }
4211 
4224  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4225  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4226  }
4227 
4238  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4239  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4240  return $this->matchEditToken( $val, $salt, $request, $maxage );
4241  }
4242 
4250  public function sendConfirmationMail( $type = 'created' ) {
4251  global $wgLang;
4252  $expiration = null; // gets passed-by-ref and defined in next line.
4253  $token = $this->confirmationToken( $expiration );
4254  $url = $this->confirmationTokenUrl( $token );
4255  $invalidateURL = $this->invalidationTokenUrl( $token );
4256  $this->saveSettings();
4257 
4258  if ( $type == 'created' || $type === false ) {
4259  $message = 'confirmemail_body';
4260  } elseif ( $type === true ) {
4261  $message = 'confirmemail_body_changed';
4262  } else {
4263  // Messages: confirmemail_body_changed, confirmemail_body_set
4264  $message = 'confirmemail_body_' . $type;
4265  }
4266 
4267  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4268  wfMessage( $message,
4269  $this->getRequest()->getIP(),
4270  $this->getName(),
4271  $url,
4272  $wgLang->userTimeAndDate( $expiration, $this ),
4273  $invalidateURL,
4274  $wgLang->userDate( $expiration, $this ),
4275  $wgLang->userTime( $expiration, $this ) )->text() );
4276  }
4277 
4289  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4291 
4292  if ( $from instanceof User ) {
4293  $sender = MailAddress::newFromUser( $from );
4294  } else {
4295  $sender = new MailAddress( $wgPasswordSender,
4296  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4297  }
4298  $to = MailAddress::newFromUser( $this );
4299 
4300  return UserMailer::send( $to, $sender, $subject, $body, [
4301  'replyTo' => $replyto,
4302  ] );
4303  }
4304 
4315  protected function confirmationToken( &$expiration ) {
4317  $now = time();
4318  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4319  $expiration = wfTimestamp( TS_MW, $expires );
4320  $this->load();
4321  $token = MWCryptRand::generateHex( 32 );
4322  $hash = md5( $token );
4323  $this->mEmailToken = $hash;
4324  $this->mEmailTokenExpires = $expiration;
4325  return $token;
4326  }
4327 
4333  protected function confirmationTokenUrl( $token ) {
4334  return $this->getTokenUrl( 'ConfirmEmail', $token );
4335  }
4336 
4342  protected function invalidationTokenUrl( $token ) {
4343  return $this->getTokenUrl( 'InvalidateEmail', $token );
4344  }
4345 
4360  protected function getTokenUrl( $page, $token ) {
4361  // Hack to bypass localization of 'Special:'
4362  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4363  return $title->getCanonicalURL();
4364  }
4365 
4373  public function confirmEmail() {
4374  // Check if it's already confirmed, so we don't touch the database
4375  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4376  if ( !$this->isEmailConfirmed() ) {
4378  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4379  }
4380  return true;
4381  }
4382 
4390  public function invalidateEmail() {
4391  $this->load();
4392  $this->mEmailToken = null;
4393  $this->mEmailTokenExpires = null;
4394  $this->setEmailAuthenticationTimestamp( null );
4395  $this->mEmail = '';
4396  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4397  return true;
4398  }
4399 
4405  $this->load();
4406  $this->mEmailAuthenticated = $timestamp;
4407  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4408  }
4409 
4415  public function canSendEmail() {
4417  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4418  return false;
4419  }
4420  $canSend = $this->isEmailConfirmed();
4421  // Avoid PHP 7.1 warning of passing $this by reference
4422  $user = $this;
4423  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4424  return $canSend;
4425  }
4426 
4432  public function canReceiveEmail() {
4433  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4434  }
4435 
4446  public function isEmailConfirmed() {
4448  $this->load();
4449  // Avoid PHP 7.1 warning of passing $this by reference
4450  $user = $this;
4451  $confirmed = true;
4452  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4453  if ( $this->isAnon() ) {
4454  return false;
4455  }
4456  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4457  return false;
4458  }
4459  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4460  return false;
4461  }
4462  return true;
4463  } else {
4464  return $confirmed;
4465  }
4466  }
4467 
4472  public function isEmailConfirmationPending() {
4474  return $wgEmailAuthentication &&
4475  !$this->isEmailConfirmed() &&
4476  $this->mEmailToken &&
4477  $this->mEmailTokenExpires > wfTimestamp();
4478  }
4479 
4487  public function getRegistration() {
4488  if ( $this->isAnon() ) {
4489  return false;
4490  }
4491  $this->load();
4492  return $this->mRegistration;
4493  }
4494 
4501  public function getFirstEditTimestamp() {
4502  if ( $this->getId() == 0 ) {
4503  return false; // anons
4504  }
4505  $dbr = wfGetDB( DB_SLAVE );
4506  $time = $dbr->selectField( 'revision', 'rev_timestamp',
4507  [ 'rev_user' => $this->getId() ],
4508  __METHOD__,
4509  [ 'ORDER BY' => 'rev_timestamp ASC' ]
4510  );
4511  if ( !$time ) {
4512  return false; // no edits
4513  }
4514  return wfTimestamp( TS_MW, $time );
4515  }
4516 
4523  public static function getGroupPermissions( $groups ) {
4524  global $wgGroupPermissions, $wgRevokePermissions;
4525  $rights = [];
4526  // grant every granted permission first
4527  foreach ( $groups as $group ) {
4528  if ( isset( $wgGroupPermissions[$group] ) ) {
4529  $rights = array_merge( $rights,
4530  // array_filter removes empty items
4531  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4532  }
4533  }
4534  // now revoke the revoked permissions
4535  foreach ( $groups as $group ) {
4536  if ( isset( $wgRevokePermissions[$group] ) ) {
4537  $rights = array_diff( $rights,
4538  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4539  }
4540  }
4541  return array_unique( $rights );
4542  }
4543 
4550  public static function getGroupsWithPermission( $role ) {
4551  global $wgGroupPermissions;
4552  $allowedGroups = [];
4553  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4554  if ( self::groupHasPermission( $group, $role ) ) {
4555  $allowedGroups[] = $group;
4556  }
4557  }
4558  return $allowedGroups;
4559  }
4560 
4573  public static function groupHasPermission( $group, $role ) {
4574  global $wgGroupPermissions, $wgRevokePermissions;
4575  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
4576  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
4577  }
4578 
4593  public static function isEveryoneAllowed( $right ) {
4594  global $wgGroupPermissions, $wgRevokePermissions;
4595  static $cache = [];
4596 
4597  // Use the cached results, except in unit tests which rely on
4598  // being able change the permission mid-request
4599  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
4600  return $cache[$right];
4601  }
4602 
4603  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
4604  $cache[$right] = false;
4605  return false;
4606  }
4607 
4608  // If it's revoked anywhere, then everyone doesn't have it
4609  foreach ( $wgRevokePermissions as $rights ) {
4610  if ( isset( $rights[$right] ) && $rights[$right] ) {
4611  $cache[$right] = false;
4612  return false;
4613  }
4614  }
4615 
4616  // Remove any rights that aren't allowed to the global-session user,
4617  // unless there are no sessions for this endpoint.
4618  if ( !defined( 'MW_NO_SESSION' ) ) {
4619  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
4620  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
4621  $cache[$right] = false;
4622  return false;
4623  }
4624  }
4625 
4626  // Allow extensions to say false
4627  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
4628  $cache[$right] = false;
4629  return false;
4630  }
4631 
4632  $cache[$right] = true;
4633  return true;
4634  }
4635 
4642  public static function getGroupName( $group ) {
4643  $msg = wfMessage( "group-$group" );
4644  return $msg->isBlank() ? $group : $msg->text();
4645  }
4646 
4654  public static function getGroupMember( $group, $username = '#' ) {
4655  $msg = wfMessage( "group-$group-member", $username );
4656  return $msg->isBlank() ? $group : $msg->text();
4657  }
4658 
4665  public static function getAllGroups() {
4666  global $wgGroupPermissions, $wgRevokePermissions;
4667  return array_diff(
4668  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4669  self::getImplicitGroups()
4670  );
4671  }
4672 
4677  public static function getAllRights() {
4678  if ( self::$mAllRights === false ) {
4679  global $wgAvailableRights;
4680  if ( count( $wgAvailableRights ) ) {
4681  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
4682  } else {
4683  self::$mAllRights = self::$mCoreRights;
4684  }
4685  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
4686  }
4687  return self::$mAllRights;
4688  }
4689 
4694  public static function getImplicitGroups() {
4695  global $wgImplicitGroups;
4696 
4697  $groups = $wgImplicitGroups;
4698  # Deprecated, use $wgImplicitGroups instead
4699  Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
4700 
4701  return $groups;
4702  }
4703 
4710  public static function getGroupPage( $group ) {
4711  $msg = wfMessage( 'grouppage-' . $group )->inContentLanguage();
4712  if ( $msg->exists() ) {
4713  $title = Title::newFromText( $msg->text() );
4714  if ( is_object( $title ) ) {
4715  return $title;
4716  }
4717  }
4718  return false;
4719  }
4720 
4729  public static function makeGroupLinkHTML( $group, $text = '' ) {
4730  if ( $text == '' ) {
4731  $text = self::getGroupName( $group );
4732  }
4733  $title = self::getGroupPage( $group );
4734  if ( $title ) {
4735  return Linker::link( $title, htmlspecialchars( $text ) );
4736  } else {
4737  return htmlspecialchars( $text );
4738  }
4739  }
4740 
4749  public static function makeGroupLinkWiki( $group, $text = '' ) {
4750  if ( $text == '' ) {
4751  $text = self::getGroupName( $group );
4752  }
4753  $title = self::getGroupPage( $group );
4754  if ( $title ) {
4755  $page = $title->getFullText();
4756  return "[[$page|$text]]";
4757  } else {
4758  return $text;
4759  }
4760  }
4761 
4771  public static function changeableByGroup( $group ) {
4772  global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
4773 
4774  $groups = [
4775  'add' => [],
4776  'remove' => [],
4777  'add-self' => [],
4778  'remove-self' => []
4779  ];
4780 
4781  if ( empty( $wgAddGroups[$group] ) ) {
4782  // Don't add anything to $groups
4783  } elseif ( $wgAddGroups[$group] === true ) {
4784  // You get everything
4785  $groups['add'] = self::getAllGroups();
4786  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4787  $groups['add'] = $wgAddGroups[$group];
4788  }
4789 
4790  // Same thing for remove
4791  if ( empty( $wgRemoveGroups[$group] ) ) {
4792  // Do nothing
4793  } elseif ( $wgRemoveGroups[$group] === true ) {
4794  $groups['remove'] = self::getAllGroups();
4795  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4796  $groups['remove'] = $wgRemoveGroups[$group];
4797  }
4798 
4799  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4800  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4801  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4802  if ( is_int( $key ) ) {
4803  $wgGroupsAddToSelf['user'][] = $value;
4804  }
4805  }
4806  }
4807 
4808  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4809  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4810  if ( is_int( $key ) ) {
4811  $wgGroupsRemoveFromSelf['user'][] = $value;
4812  }
4813  }
4814  }
4815 
4816  // Now figure out what groups the user can add to him/herself
4817  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4818  // Do nothing
4819  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4820  // No idea WHY this would be used, but it's there
4821  $groups['add-self'] = User::getAllGroups();
4822  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4823  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4824  }
4825 
4826  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4827  // Do nothing
4828  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4829  $groups['remove-self'] = User::getAllGroups();
4830  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4831  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4832  }
4833 
4834  return $groups;
4835  }
4836 
4844  public function changeableGroups() {
4845  if ( $this->isAllowed( 'userrights' ) ) {
4846  // This group gives the right to modify everything (reverse-
4847  // compatibility with old "userrights lets you change
4848  // everything")
4849  // Using array_merge to make the groups reindexed
4850  $all = array_merge( User::getAllGroups() );
4851  return [
4852  'add' => $all,
4853  'remove' => $all,
4854  'add-self' => [],
4855  'remove-self' => []
4856  ];
4857  }
4858 
4859  // Okay, it's not so simple, we will have to go through the arrays
4860  $groups = [
4861  'add' => [],
4862  'remove' => [],
4863  'add-self' => [],
4864  'remove-self' => []
4865  ];
4866  $addergroups = $this->getEffectiveGroups();
4867 
4868  foreach ( $addergroups as $addergroup ) {
4869  $groups = array_merge_recursive(
4870  $groups, $this->changeableByGroup( $addergroup )
4871  );
4872  $groups['add'] = array_unique( $groups['add'] );
4873  $groups['remove'] = array_unique( $groups['remove'] );
4874  $groups['add-self'] = array_unique( $groups['add-self'] );
4875  $groups['remove-self'] = array_unique( $groups['remove-self'] );
4876  }
4877  return $groups;
4878  }
4879 
4883  public function incEditCount() {
4884  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() {
4885  $this->incEditCountImmediate();
4886  } );
4887  }
4888 
4894  public function incEditCountImmediate() {
4895  if ( $this->isAnon() ) {
4896  return;
4897  }
4898 
4899  $dbw = wfGetDB( DB_MASTER );
4900  // No rows will be "affected" if user_editcount is NULL
4901  $dbw->update(
4902  'user',
4903  [ 'user_editcount=user_editcount+1' ],
4904  [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
4905  __METHOD__
4906  );
4907  // Lazy initialization check...
4908  if ( $dbw->affectedRows() == 0 ) {
4909  // Now here's a goddamn hack...
4910  $dbr = wfGetDB( DB_SLAVE );
4911  if ( $dbr !== $dbw ) {
4912  // If we actually have a slave server, the count is
4913  // at least one behind because the current transaction
4914  // has not been committed and replicated.
4915  $this->initEditCount( 1 );
4916  } else {
4917  // But if DB_SLAVE is selecting the master, then the
4918  // count we just read includes the revision that was
4919  // just added in the working transaction.
4920  $this->initEditCount();
4921  }
4922  }
4923  // Edit count in user cache too
4924  $this->invalidateCache();
4925  }
4926 
4933  protected function initEditCount( $add = 0 ) {
4934  // Pull from a slave to be less cruel to servers
4935  // Accuracy isn't the point anyway here
4936  $dbr = wfGetDB( DB_SLAVE );
4937  $count = (int)$dbr->selectField(
4938  'revision',
4939  'COUNT(rev_user)',
4940  [ 'rev_user' => $this->getId() ],
4941  __METHOD__
4942  );
4943  $count = $count + $add;
4944 
4945  $dbw = wfGetDB( DB_MASTER );
4946  $dbw->update(
4947  'user',
4948  [ 'user_editcount' => $count ],
4949  [ 'user_id' => $this->getId() ],
4950  __METHOD__
4951  );
4952 
4953  return $count;
4954  }
4955 
4962  public static function getRightDescription( $right ) {
4963  $key = "right-$right";
4964  $msg = wfMessage( $key );
4965  return $msg->isBlank() ? $right : $msg->text();
4966  }
4967 
4977  public static function crypt( $password, $salt = false ) {
4978  wfDeprecated( __METHOD__, '1.24' );
4979  $passwordFactory = new PasswordFactory();
4980  $passwordFactory->init( RequestContext::getMain()->getConfig() );
4981  $hash = $passwordFactory->newFromPlaintext( $password );
4982  return $hash->toString();
4983  }
4984 
4996  public static function comparePasswords( $hash, $password, $userId = false ) {
4997  wfDeprecated( __METHOD__, '1.24' );
4998 
4999  // Check for *really* old password hashes that don't even have a type
5000  // The old hash format was just an md5 hex hash, with no type information
5001  if ( preg_match( '/^[0-9a-f]{32}$/', $hash ) ) {
5002  global $wgPasswordSalt;
5003  if ( $wgPasswordSalt ) {
5004  $password = ":B:{$userId}:{$hash}";
5005  } else {
5006  $password = ":A:{$hash}";
5007  }
5008  }
5009 
5010  $passwordFactory = new PasswordFactory();
5011  $passwordFactory->init( RequestContext::getMain()->getConfig() );
5012  $hash = $passwordFactory->newFromCiphertext( $hash );
5013  return $hash->equals( $password );
5014  }
5015 
5036  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5037  return true; // disabled
5038  }
5039 
5048  public function addNewUserLogEntryAutoCreate() {
5049  $this->addNewUserLogEntry( 'autocreate' );
5050 
5051  return true;
5052  }
5053 
5059  protected function loadOptions( $data = null ) {
5061 
5062  $this->load();
5063 
5064  if ( $this->mOptionsLoaded ) {
5065  return;
5066  }
5067 
5068  $this->mOptions = self::getDefaultOptions();
5069 
5070  if ( !$this->getId() ) {
5071  // For unlogged-in users, load language/variant options from request.
5072  // There's no need to do it for logged-in users: they can set preferences,
5073  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5074  // so don't override user's choice (especially when the user chooses site default).
5075  $variant = $wgContLang->getDefaultVariant();
5076  $this->mOptions['variant'] = $variant;
5077  $this->mOptions['language'] = $variant;
5078  $this->mOptionsLoaded = true;
5079  return;
5080  }
5081 
5082  // Maybe load from the object
5083  if ( !is_null( $this->mOptionOverrides ) ) {
5084  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5085  foreach ( $this->mOptionOverrides as $key => $value ) {
5086  $this->mOptions[$key] = $value;
5087  }
5088  } else {
5089  if ( !is_array( $data ) ) {
5090  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5091  // Load from database
5092  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5093  ? wfGetDB( DB_MASTER )
5094  : wfGetDB( DB_SLAVE );
5095 
5096  $res = $dbr->select(
5097  'user_properties',
5098  [ 'up_property', 'up_value' ],
5099  [ 'up_user' => $this->getId() ],
5100  __METHOD__
5101  );
5102 
5103  $this->mOptionOverrides = [];
5104  $data = [];
5105  foreach ( $res as $row ) {
5106  $data[$row->up_property] = $row->up_value;
5107  }
5108  }
5109  foreach ( $data as $property => $value ) {
5110  $this->mOptionOverrides[$property] = $value;
5111  $this->mOptions[$property] = $value;
5112  }
5113  }
5114 
5115  $this->mOptionsLoaded = true;
5116 
5117  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5118  }
5119 
5125  protected function saveOptions() {
5126  $this->loadOptions();
5127 
5128  // Not using getOptions(), to keep hidden preferences in database
5129  $saveOptions = $this->mOptions;
5130 
5131  // Allow hooks to abort, for instance to save to a global profile.
5132  // Reset options to default state before saving.
5133  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5134  return;
5135  }
5136 
5137  $userId = $this->getId();
5138 
5139  $insert_rows = []; // all the new preference rows
5140  foreach ( $saveOptions as $key => $value ) {
5141  // Don't bother storing default values
5142  $defaultOption = self::getDefaultOption( $key );
5143  if ( ( $defaultOption === null && $value !== false && $value !== null )
5144  || $value != $defaultOption
5145  ) {
5146  $insert_rows[] = [
5147  'up_user' => $userId,
5148  'up_property' => $key,
5149  'up_value' => $value,
5150  ];
5151  }
5152  }
5153 
5154  $dbw = wfGetDB( DB_MASTER );
5155 
5156  $res = $dbw->select( 'user_properties',
5157  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5158 
5159  // Find prior rows that need to be removed or updated. These rows will
5160  // all be deleted (the later so that INSERT IGNORE applies the new values).
5161  $keysDelete = [];
5162  foreach ( $res as $row ) {
5163  if ( !isset( $saveOptions[$row->up_property] )
5164  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5165  ) {
5166  $keysDelete[] = $row->up_property;
5167  }
5168  }
5169 
5170  if ( count( $keysDelete ) ) {
5171  // Do the DELETE by PRIMARY KEY for prior rows.
5172  // In the past a very large portion of calls to this function are for setting
5173  // 'rememberpassword' for new accounts (a preference that has since been removed).
5174  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5175  // caused gap locks on [max user ID,+infinity) which caused high contention since
5176  // updates would pile up on each other as they are for higher (newer) user IDs.
5177  // It might not be necessary these days, but it shouldn't hurt either.
5178  $dbw->delete( 'user_properties',
5179  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5180  }
5181  // Insert the new preference rows
5182  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5183  }
5184 
5191  public static function getPasswordFactory() {
5192  wfDeprecated( __METHOD__, '1.27' );
5193  $ret = new PasswordFactory();
5194  $ret->init( RequestContext::getMain()->getConfig() );
5195  return $ret;
5196  }
5197 
5222  public static function passwordChangeInputAttribs() {
5223  global $wgMinimalPasswordLength;
5224 
5225  if ( $wgMinimalPasswordLength == 0 ) {
5226  return [];
5227  }
5228 
5229  # Note that the pattern requirement will always be satisfied if the
5230  # input is empty, so we need required in all cases.
5231 
5232  # @todo FIXME: Bug 23769: This needs to not claim the password is required
5233  # if e-mail confirmation is being used. Since HTML5 input validation
5234  # is b0rked anyway in some browsers, just return nothing. When it's
5235  # re-enabled, fix this code to not output required for e-mail
5236  # registration.
5237  # $ret = array( 'required' );
5238  $ret = [];
5239 
5240  # We can't actually do this right now, because Opera 9.6 will print out
5241  # the entered password visibly in its error message! When other
5242  # browsers add support for this attribute, or Opera fixes its support,
5243  # we can add support with a version check to avoid doing this on Opera
5244  # versions where it will be a problem. Reported to Opera as
5245  # DSK-262266, but they don't have a public bug tracker for us to follow.
5246  /*
5247  if ( $wgMinimalPasswordLength > 1 ) {
5248  $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}';
5249  $ret['title'] = wfMessage( 'passwordtooshort' )
5250  ->numParams( $wgMinimalPasswordLength )->text();
5251  }
5252  */
5253 
5254  return $ret;
5255  }
5256 
5262  public static function selectFields() {
5263  return [
5264  'user_id',
5265  'user_name',
5266  'user_real_name',
5267  'user_email',
5268  'user_touched',
5269  'user_token',
5270  'user_email_authenticated',
5271  'user_email_token',
5272  'user_email_token_expires',
5273  'user_registration',
5274  'user_editcount',
5275  ];
5276  }
5277 
5285  static function newFatalPermissionDeniedStatus( $permission ) {
5286  global $wgLang;
5287 
5288  $groups = array_map(
5289  [ 'User', 'makeGroupLinkWiki' ],
5290  User::getGroupsWithPermission( $permission )
5291  );
5292 
5293  if ( $groups ) {
5294  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5295  } else {
5296  return Status::newFatal( 'badaccess-group0' );
5297  }
5298  }
5299 
5309  public function getInstanceForUpdate() {
5310  if ( !$this->getId() ) {
5311  return null; // anon
5312  }
5313 
5314  $user = self::newFromId( $this->getId() );
5315  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5316  return null;
5317  }
5318 
5319  return $user;
5320  }
5321 
5329  public function equals( User $user ) {
5330  return $this->getName() === $user->getName();
5331  }
5332 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:568
addAutopromoteOnceGroups($event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1396
getEmail()
Get the user's e-mail address.
Definition: User.php:2638
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:2051
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:1412
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:2630
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:2816
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2177
isAllowedAll()
Definition: User.php:3406
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:4116
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:4238
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:2325
$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:4207
isBlockedFrom($title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition: User.php:1937
$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:4126
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1199
clearInstanceCache($reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1486
loadFromSession()
Load user data from the session.
Definition: User.php:1193
const NS_MAIN
Definition: Defines.php:70
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4088
$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:3742
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3532
isDnsBlacklisted($ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1654
static getImplicitGroups()
Get a list of implicit groups.
Definition: User.php:4694
saveSettings()
Save this user's settings into the database.
Definition: User.php:3782
static isLocallyBlockedProxy($ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1722
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:4501
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:3391
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4079
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:1802
setCookie($name, $value, $exp=0, $secure=null, $params=[], $request=null)
Set a cookie on the user's client.
Definition: User.php:3644
getAutomaticGroups($recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3210
pingLimiter($action= 'edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1777
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:2342
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:2385
deleteNewtalk($field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2269
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:2244
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:1989
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:1958
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:1422
getRealName()
Get the user's real name.
Definition: User.php:2730
checkPasswordValidity($password, $purpose= 'login')
Check if this is a valid password for this user.
Definition: User.php:1010
$value
Check if a user's password complies with any password policies that apply to that user...
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1446
static newFromId($id)
Static factory method for creation from a given user ID.
Definition: User.php:591
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4844
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3600
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3373
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:2552
saveToCache()
Save user data to the shared cache.
Definition: User.php:525
getTemporaryPassword()
Definition: User.php:2453
checkNewtalk($field, $id)
Internal uncached check for new messages.
Definition: User.php:2229
setPassword($str)
Set the password and reset the random token.
Definition: User.php:2473
static createNew($name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:3881
invalidationTokenUrl($token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4342
addNewUserLogEntry($action=false, $reason= '')
Add a newuser log entry for this user.
Definition: User.php:5036
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:810
setName($str)
Set the user name.
Definition: User.php:2122
array $mFormerGroups
Definition: User.php:281
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2433
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:2785
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:4224
setToken($token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2600
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:4729
static makeGroupLinkWiki($group, $text= '')
Create a link to the group in Wikitext, if available; else return the group name. ...
Definition: User.php:4749
setOption($oname, $val)
Set the given option for a user.
Definition: User.php:2844
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2095
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1616
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5262
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:2675
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:2535
static isEveryoneAllowed($right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4593
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:1800
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2131
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4665
resetTokenFromOption($oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:2893
loadOptions($data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5059
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:2927
getIntOption($oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2828
sendMail($subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4289
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:4677
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:4446
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:1802
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:4057
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:2342
setNewpassword($str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:2620
static crypt($password, $salt=false)
Make a new-style password hash.
Definition: User.php:4977
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:1925
sendConfirmationMail($type= 'created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4250
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:4404
static comparePasswords($hash, $password, $userId=false)
Compare a password hash with a plain-text password.
Definition: User.php:4996
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4487
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:2289
static groupHasPermission($group, $role)
Check, if the given group has the given permission.
Definition: User.php:4573
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:5048
getPassword()
Definition: User.php:2444
static changeableByGroup($group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4771
isAllowed($action= '')
Internal mechanics of testing a permission.
Definition: User.php:3421
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:3381
inDnsBlacklist($ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1675
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:3034
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:1079
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:1008
validateCache($timestamp)
Validate the cache for this account.
Definition: User.php:2399
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:3754
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:4472
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:49
incEditCount()
Deferred version of incEditCountImmediate()
Definition: User.php:4883
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2321
setTarget($target)
Set the target for this block, and update $this->type accordingly.
Definition: Block.php:1396
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:2950
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:3665
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4415
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:47
getBlockedStatus($bFromSlave=true)
Get blocking information.
Definition: User.php:1560
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:35
static getGroupPage($group)
Get the title of a page describing a particular group.
Definition: User.php:4710
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:916
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3099
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:4038
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:1588
getTokenUrl($page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4360
setInternalPassword($str)
Set the password and reset the random token unconditionally.
Definition: User.php:2485
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:2907
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:916
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:766
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:3485
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:246
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:3434
$wgPasswordSender
Sender email address for e-mail notifications.
isBlocked($bFromSlave=true)
Check if user is blocked.
Definition: User.php:1915
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:2757
static getGroupName($group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:4642
$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:4390
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1366
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5125
idForName($flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:3846
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:5222
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:2086
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3118
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3468
$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:3685
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition: Block.php:1119
this hook is for auditing only $req
Definition: hooks.txt:969
getNewtalk()
Check if the user has new messages.
Definition: User.php:2139
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:766
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:3174
__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:4654
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:4333
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:2422
removeGroup($group)
Remove the user from the given group.
Definition: User.php:3333
prevents($action, $x=null)
Get/set whether the Block prevents a given action.
Definition: Block.php:979
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:4025
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3455
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:3187
getId()
Get the user's ID.
Definition: User.php:2070
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2648
addToDatabase()
Add this existing user object to the database.
Definition: User.php:3952
bool $mAllowUsertalk
Definition: User.php:300
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2368
getTokenFromOption($oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:2865
removeWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3516
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4373
getGlobalBlock($ip= '')
Check if user is blocked on all wikis.
Definition: User.php:2003
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:2658
initEditCount($add=0)
Initialize user_editcount from data out of the revision table.
Definition: User.php:4933
bool $mHideName
Definition: User.php:287
static getPasswordFactory()
Lazily instantiate and return a factory object for making passwords.
Definition: User.php:5191
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:3263
getUserPage()
Get this user's personal page title.
Definition: User.php:4097
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5309
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:3443
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:1008
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:3706
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3079
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:1976
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:2202
$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:1008
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:4197
static newFatalPermissionDeniedStatus($permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5285
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:4175
isLocked()
Check if user account is locked.
Definition: User.php:2034
$messages
wfMemcKey()
Make a cache key for the local wiki.
string $mHash
Definition: User.php:271
const DB_MASTER
Definition: Defines.php:48
static validateEmail($addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1904
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4432
equals(User $user)
Checks if two user objects point to the same user.
Definition: User.php:5329
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:2742
addWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3499
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:4159
static logException($e)
Log an exception to the exception log (if enabled).
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1511
const INVALID_TOKEN
string An invalid value for user_token
Definition: User.php:56
setItemLoaded($item)
Set that an item has been loaded.
Definition: User.php:1182
wfTimestampOrNull($outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
static getDefaultOption($opt)
Get a given default option value.
Definition: User.php:1545
string $mRealName
Cache variables.
Definition: User.php:213
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:345
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
static getSubnet($ip)
Returns the subnet of a given IP.
Definition: IP.php:777
getCacheKey(WANObjectCache $cache)
Definition: User.php:471
static getGroupPermissions($groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4523
const NS_USER_TALK
Definition: Defines.php:73
static array $languagesWithVariants
languages supporting variants
getTalkPage()
Get this user's talk page title.
Definition: User.php:4106
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:2562
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:1752
static getRightDescription($right)
Get the description of a given right.
Definition: User.php:4962
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:2342
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:2342
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:4550
static newGood($value=null)
Factory function for good results.
Definition: Status.php:101
getRights()
Get the permissions this user has.
Definition: User.php:3133
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3239
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:1967
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:2342
setPasswordInternal($str)
Actually set the password and such.
Definition: User.php:2497
incEditCountImmediate()
Increment the user's edit-count field.
Definition: User.php:4894
int $mId
Cache variables.
Definition: User.php:209
addGroup($group)
Add the user to the given group.
Definition: User.php:3293
$wgUser
Definition: Setup.php:794
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4315
getTouched()
Get the user touched timestamp.
Definition: User.php:2411
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:314