MediaWiki  master
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  'deletechangetags',
134  'deletedhistory',
135  'deletedtext',
136  'deletelogentry',
137  'deleterevision',
138  'edit',
139  'editcontentmodel',
140  'editinterface',
141  'editprotected',
142  'editmyoptions',
143  'editmyprivateinfo',
144  'editmyusercss',
145  'editmyuserjs',
146  'editmywatchlist',
147  'editsemiprotected',
148  'editusercssjs', # deprecated
149  'editusercss',
150  'edituserjs',
151  'hideuser',
152  'import',
153  'importupload',
154  'ipblock-exempt',
155  'managechangetags',
156  'markbotedits',
157  'mergehistory',
158  'minoredit',
159  'move',
160  'movefile',
161  'move-categorypages',
162  'move-rootuserpages',
163  'move-subpages',
164  'nominornewtalk',
165  'noratelimit',
166  'override-export-depth',
167  'pagelang',
168  'passwordreset',
169  'patrol',
170  'patrolmarks',
171  'protect',
172  'purge',
173  'read',
174  'reupload',
175  'reupload-own',
176  'reupload-shared',
177  'rollback',
178  'sendemail',
179  'siteadmin',
180  'suppressionlog',
181  'suppressredirect',
182  'suppressrevision',
183  'unblockself',
184  'undelete',
185  'unwatchedpages',
186  'upload',
187  'upload_by_url',
188  'userrights',
189  'userrights-interwiki',
190  'viewmyprivateinfo',
191  'viewmywatchlist',
192  'viewsuppressed',
193  'writeapi',
194  ];
195 
199  protected static $mAllRights = false;
200 
202  // @{
204  public $mId;
206  public $mName;
208  public $mRealName;
209 
211  public $mEmail;
213  public $mTouched;
215  protected $mQuickTouched;
217  protected $mToken;
221  protected $mEmailToken;
225  protected $mRegistration;
227  protected $mEditCount;
229  public $mGroups;
231  protected $mOptionOverrides;
232  // @}
233 
237  // @{
239 
243  protected $mLoadedItems = [];
244  // @}
245 
255  public $mFrom;
256 
260  protected $mNewtalk;
262  protected $mDatePreference;
264  public $mBlockedby;
266  protected $mHash;
268  public $mRights;
270  protected $mBlockreason;
272  protected $mEffectiveGroups;
274  protected $mImplicitGroups;
276  protected $mFormerGroups;
278  protected $mGlobalBlock;
280  protected $mLocked;
282  public $mHideName;
284  public $mOptions;
285 
289  private $mRequest;
290 
292  public $mBlock;
293 
295  protected $mAllowUsertalk;
296 
298  private $mBlockedFromCreateAccount = false;
299 
301  protected $queryFlagsUsed = self::READ_NORMAL;
302 
303  public static $idCacheByName = [];
304 
315  public function __construct() {
316  $this->clearInstanceCache( 'defaults' );
317  }
318 
322  public function __toString() {
323  return $this->getName();
324  }
325 
340  public function isSafeToLoad() {
342 
343  // The user is safe to load if:
344  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
345  // * mLoadedItems === true (already loaded)
346  // * mFrom !== 'session' (sessions not involved at all)
347 
348  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
349  $this->mLoadedItems === true || $this->mFrom !== 'session';
350  }
351 
357  public function load( $flags = self::READ_NORMAL ) {
359 
360  if ( $this->mLoadedItems === true ) {
361  return;
362  }
363 
364  // Set it now to avoid infinite recursion in accessors
365  $oldLoadedItems = $this->mLoadedItems;
366  $this->mLoadedItems = true;
367  $this->queryFlagsUsed = $flags;
368 
369  // If this is called too early, things are likely to break.
370  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
372  ->warning( 'User::loadFromSession called before the end of Setup.php', [
373  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
374  ] );
375  $this->loadDefaults();
376  $this->mLoadedItems = $oldLoadedItems;
377  return;
378  }
379 
380  switch ( $this->mFrom ) {
381  case 'defaults':
382  $this->loadDefaults();
383  break;
384  case 'name':
385  // Make sure this thread sees its own changes
386  if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
387  $flags |= self::READ_LATEST;
388  $this->queryFlagsUsed = $flags;
389  }
390 
391  $this->mId = self::idFromName( $this->mName, $flags );
392  if ( !$this->mId ) {
393  // Nonexistent user placeholder object
394  $this->loadDefaults( $this->mName );
395  } else {
396  $this->loadFromId( $flags );
397  }
398  break;
399  case 'id':
400  $this->loadFromId( $flags );
401  break;
402  case 'session':
403  if ( !$this->loadFromSession() ) {
404  // Loading from session failed. Load defaults.
405  $this->loadDefaults();
406  }
407  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
408  break;
409  default:
410  throw new UnexpectedValueException(
411  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
412  }
413  }
414 
420  public function loadFromId( $flags = self::READ_NORMAL ) {
421  if ( $this->mId == 0 ) {
422  // Anonymous users are not in the database (don't need cache)
423  $this->loadDefaults();
424  return false;
425  }
426 
427  // Try cache (unless this needs data from the master DB).
428  // NOTE: if this thread called saveSettings(), the cache was cleared.
429  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
430  if ( $latest ) {
431  if ( !$this->loadFromDatabase( $flags ) ) {
432  // Can't load from ID
433  return false;
434  }
435  } else {
436  $this->loadFromCache();
437  }
438 
439  $this->mLoadedItems = true;
440  $this->queryFlagsUsed = $flags;
441 
442  return true;
443  }
444 
450  public static function purge( $wikiId, $userId ) {
452  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
453  $cache->delete( $key );
454  }
455 
461  protected function getCacheKey( WANObjectCache $cache ) {
462  return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId );
463  }
464 
471  protected function loadFromCache() {
473  $data = $cache->getWithSetCallback(
474  $this->getCacheKey( $cache ),
475  $cache::TTL_HOUR,
476  function ( $oldValue, &$ttl, array &$setOpts ) {
478  wfDebug( "User: cache miss for user {$this->mId}\n" );
479 
480  $this->loadFromDatabase( self::READ_NORMAL );
481  $this->loadGroups();
482  $this->loadOptions();
483 
484  $data = [];
485  foreach ( self::$mCacheVars as $name ) {
486  $data[$name] = $this->$name;
487  }
488 
489  return $data;
490 
491  },
492  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
493  );
494 
495  // Restore from cache
496  foreach ( self::$mCacheVars as $name ) {
497  $this->$name = $data[$name];
498  }
499 
500  return true;
501  }
502 
504  // @{
505 
522  public static function newFromName( $name, $validate = 'valid' ) {
523  if ( $validate === true ) {
524  $validate = 'valid';
525  }
526  $name = self::getCanonicalName( $name, $validate );
527  if ( $name === false ) {
528  return false;
529  } else {
530  // Create unloaded user object
531  $u = new User;
532  $u->mName = $name;
533  $u->mFrom = 'name';
534  $u->setItemLoaded( 'name' );
535  return $u;
536  }
537  }
538 
545  public static function newFromId( $id ) {
546  $u = new User;
547  $u->mId = $id;
548  $u->mFrom = 'id';
549  $u->setItemLoaded( 'id' );
550  return $u;
551  }
552 
564  public static function newFromConfirmationCode( $code, $flags = 0 ) {
565  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
566  ? wfGetDB( DB_MASTER )
567  : wfGetDB( DB_SLAVE );
568 
569  $id = $db->selectField(
570  'user',
571  'user_id',
572  [
573  'user_email_token' => md5( $code ),
574  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
575  ]
576  );
577 
578  return $id ? User::newFromId( $id ) : null;
579  }
580 
588  public static function newFromSession( WebRequest $request = null ) {
589  $user = new User;
590  $user->mFrom = 'session';
591  $user->mRequest = $request;
592  return $user;
593  }
594 
609  public static function newFromRow( $row, $data = null ) {
610  $user = new User;
611  $user->loadFromRow( $row, $data );
612  return $user;
613  }
614 
650  public static function newSystemUser( $name, $options = [] ) {
652 
653  $options += [
654  'validate' => 'valid',
655  'create' => true,
656  'steal' => false,
657  ];
658 
659  $name = self::getCanonicalName( $name, $options['validate'] );
660  if ( $name === false ) {
661  return null;
662  }
663 
664  $fields = self::selectFields();
665  if ( $wgDisableAuthManager ) {
666  $fields = array_merge( $fields, [ 'user_password', 'user_newpassword' ] );
667  }
668 
669  $dbw = wfGetDB( DB_MASTER );
670  $row = $dbw->selectRow(
671  'user',
672  $fields,
673  [ 'user_name' => $name ],
674  __METHOD__
675  );
676  if ( !$row ) {
677  // No user. Create it?
678  return $options['create'] ? self::createNew( $name ) : null;
679  }
680  $user = self::newFromRow( $row );
681 
682  // A user is considered to exist as a non-system user if it can
683  // authenticate, or has an email set, or has a non-invalid token.
684  if ( !$user->mEmail && $user->mToken === self::INVALID_TOKEN ) {
685  if ( $wgDisableAuthManager ) {
686  $passwordFactory = new PasswordFactory();
687  $passwordFactory->init( RequestContext::getMain()->getConfig() );
688  try {
689  $password = $passwordFactory->newFromCiphertext( $row->user_password );
690  } catch ( PasswordError $e ) {
691  wfDebug( 'Invalid password hash found in database.' );
693  }
694  try {
695  $newpassword = $passwordFactory->newFromCiphertext( $row->user_newpassword );
696  } catch ( PasswordError $e ) {
697  wfDebug( 'Invalid password hash found in database.' );
698  $newpassword = PasswordFactory::newInvalidPassword();
699  }
700  $canAuthenticate = !$password instanceof InvalidPassword ||
701  !$newpassword instanceof InvalidPassword;
702  } else {
703  $canAuthenticate = AuthManager::singleton()->userCanAuthenticate( $name );
704  }
705  }
706  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN || $canAuthenticate ) {
707  // User exists. Steal it?
708  if ( !$options['steal'] ) {
709  return null;
710  }
711 
712  if ( $wgDisableAuthManager ) {
713  $nopass = PasswordFactory::newInvalidPassword()->toString();
714  $dbw->update(
715  'user',
716  [
717  'user_password' => $nopass,
718  'user_newpassword' => $nopass,
719  'user_newpass_time' => null,
720  ],
721  [ 'user_id' => $user->getId() ],
722  __METHOD__
723  );
724  } else {
725  AuthManager::singleton()->revokeAccessForUser( $name );
726  }
727 
728  $user->invalidateEmail();
729  $user->mToken = self::INVALID_TOKEN;
730  $user->saveSettings();
731  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
732  }
733 
734  return $user;
735  }
736 
737  // @}
738 
744  public static function whoIs( $id ) {
745  return UserCache::singleton()->getProp( $id, 'name' );
746  }
747 
754  public static function whoIsReal( $id ) {
755  return UserCache::singleton()->getProp( $id, 'real_name' );
756  }
757 
764  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
766  if ( is_null( $nt ) ) {
767  // Illegal name
768  return null;
769  }
770 
771  if ( !( $flags & self::READ_LATEST ) && isset( self::$idCacheByName[$name] ) ) {
772  return self::$idCacheByName[$name];
773  }
774 
776  $db = wfGetDB( $index );
777 
778  $s = $db->selectRow(
779  'user',
780  [ 'user_id' ],
781  [ 'user_name' => $nt->getText() ],
782  __METHOD__,
783  $options
784  );
785 
786  if ( $s === false ) {
787  $result = null;
788  } else {
789  $result = $s->user_id;
790  }
791 
792  self::$idCacheByName[$name] = $result;
793 
794  if ( count( self::$idCacheByName ) > 1000 ) {
795  self::$idCacheByName = [];
796  }
797 
798  return $result;
799  }
800 
804  public static function resetIdByNameCache() {
805  self::$idCacheByName = [];
806  }
807 
824  public static function isIP( $name ) {
825  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
826  || IP::isIPv6( $name );
827  }
828 
840  public static function isValidUserName( $name ) {
842 
843  if ( $name == ''
844  || User::isIP( $name )
845  || strpos( $name, '/' ) !== false
846  || strlen( $name ) > $wgMaxNameChars
847  || $name != $wgContLang->ucfirst( $name )
848  ) {
849  return false;
850  }
851 
852  // Ensure that the name can't be misresolved as a different title,
853  // such as with extra namespace keys at the start.
854  $parsed = Title::newFromText( $name );
855  if ( is_null( $parsed )
856  || $parsed->getNamespace()
857  || strcmp( $name, $parsed->getPrefixedText() ) ) {
858  return false;
859  }
860 
861  // Check an additional blacklist of troublemaker characters.
862  // Should these be merged into the title char list?
863  $unicodeBlacklist = '/[' .
864  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
865  '\x{00a0}' . # non-breaking space
866  '\x{2000}-\x{200f}' . # various whitespace
867  '\x{2028}-\x{202f}' . # breaks and control chars
868  '\x{3000}' . # ideographic space
869  '\x{e000}-\x{f8ff}' . # private use
870  ']/u';
871  if ( preg_match( $unicodeBlacklist, $name ) ) {
872  return false;
873  }
874 
875  return true;
876  }
877 
889  public static function isUsableName( $name ) {
891  // Must be a valid username, obviously ;)
892  if ( !self::isValidUserName( $name ) ) {
893  return false;
894  }
895 
896  static $reservedUsernames = false;
897  if ( !$reservedUsernames ) {
898  $reservedUsernames = $wgReservedUsernames;
899  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
900  }
901 
902  // Certain names may be reserved for batch processes.
903  foreach ( $reservedUsernames as $reserved ) {
904  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
905  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
906  }
907  if ( $reserved == $name ) {
908  return false;
909  }
910  }
911  return true;
912  }
913 
924  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
925  if ( $groups === [] ) {
926  return UserArrayFromResult::newFromIDs( [] );
927  }
928 
929  $groups = array_unique( (array)$groups );
930  $limit = min( 5000, $limit );
931 
932  $conds = [ 'ug_group' => $groups ];
933  if ( $after !== null ) {
934  $conds[] = 'ug_user > ' . (int)$after;
935  }
936 
937  $dbr = wfGetDB( DB_SLAVE );
938  $ids = $dbr->selectFieldValues(
939  'user_groups',
940  'ug_user',
941  $conds,
942  __METHOD__,
943  [
944  'DISTINCT' => true,
945  'ORDER BY' => 'ug_user',
946  'LIMIT' => $limit,
947  ]
948  ) ?: [];
949  return UserArray::newFromIDs( $ids );
950  }
951 
964  public static function isCreatableName( $name ) {
966 
967  // Ensure that the username isn't longer than 235 bytes, so that
968  // (at least for the builtin skins) user javascript and css files
969  // will work. (bug 23080)
970  if ( strlen( $name ) > 235 ) {
971  wfDebugLog( 'username', __METHOD__ .
972  ": '$name' invalid due to length" );
973  return false;
974  }
975 
976  // Preg yells if you try to give it an empty string
977  if ( $wgInvalidUsernameCharacters !== '' ) {
978  if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
979  wfDebugLog( 'username', __METHOD__ .
980  ": '$name' invalid due to wgInvalidUsernameCharacters" );
981  return false;
982  }
983  }
984 
985  return self::isUsableName( $name );
986  }
987 
994  public function isValidPassword( $password ) {
995  // simple boolean wrapper for getPasswordValidity
996  return $this->getPasswordValidity( $password ) === true;
997  }
998 
1005  public function getPasswordValidity( $password ) {
1006  $result = $this->checkPasswordValidity( $password );
1007  if ( $result->isGood() ) {
1008  return true;
1009  } else {
1010  $messages = [];
1011  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1012  $messages[] = $error['message'];
1013  }
1014  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1015  $messages[] = $warning['message'];
1016  }
1017  if ( count( $messages ) === 1 ) {
1018  return $messages[0];
1019  }
1020  return $messages;
1021  }
1022  }
1023 
1042  public function checkPasswordValidity( $password, $purpose = 'login' ) {
1044 
1045  $upp = new UserPasswordPolicy(
1046  $wgPasswordPolicy['policies'],
1047  $wgPasswordPolicy['checks']
1048  );
1049 
1051  $result = false; // init $result to false for the internal checks
1052 
1053  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1054  $status->error( $result );
1055  return $status;
1056  }
1057 
1058  if ( $result === false ) {
1059  $status->merge( $upp->checkUserPassword( $this, $password, $purpose ) );
1060  return $status;
1061  } elseif ( $result === true ) {
1062  return $status;
1063  } else {
1064  $status->error( $result );
1065  return $status; // the isValidPassword hook set a string $result and returned true
1066  }
1067  }
1068 
1082  public static function getCanonicalName( $name, $validate = 'valid' ) {
1083  // Force usernames to capital
1085  $name = $wgContLang->ucfirst( $name );
1086 
1087  # Reject names containing '#'; these will be cleaned up
1088  # with title normalisation, but then it's too late to
1089  # check elsewhere
1090  if ( strpos( $name, '#' ) !== false ) {
1091  return false;
1092  }
1093 
1094  // Clean up name according to title rules,
1095  // but only when validation is requested (bug 12654)
1096  $t = ( $validate !== false ) ?
1098  // Check for invalid titles
1099  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1100  return false;
1101  }
1102 
1103  // Reject various classes of invalid names
1104  $name = AuthManager::callLegacyAuthPlugin(
1105  'getCanonicalName', [ $t->getText() ], $t->getText()
1106  );
1107 
1108  switch ( $validate ) {
1109  case false:
1110  break;
1111  case 'valid':
1112  if ( !User::isValidUserName( $name ) ) {
1113  $name = false;
1114  }
1115  break;
1116  case 'usable':
1117  if ( !User::isUsableName( $name ) ) {
1118  $name = false;
1119  }
1120  break;
1121  case 'creatable':
1122  if ( !User::isCreatableName( $name ) ) {
1123  $name = false;
1124  }
1125  break;
1126  default:
1127  throw new InvalidArgumentException(
1128  'Invalid parameter value for $validate in ' . __METHOD__ );
1129  }
1130  return $name;
1131  }
1132 
1141  public static function edits( $uid ) {
1142  wfDeprecated( __METHOD__, '1.21' );
1143  $user = self::newFromId( $uid );
1144  return $user->getEditCount();
1145  }
1146 
1153  public static function randomPassword() {
1155  return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1156  }
1157 
1166  public function loadDefaults( $name = false ) {
1167  $this->mId = 0;
1168  $this->mName = $name;
1169  $this->mRealName = '';
1170  $this->mEmail = '';
1171  $this->mOptionOverrides = null;
1172  $this->mOptionsLoaded = false;
1173 
1174  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1175  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1176  if ( $loggedOut !== 0 ) {
1177  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1178  } else {
1179  $this->mTouched = '1'; # Allow any pages to be cached
1180  }
1181 
1182  $this->mToken = null; // Don't run cryptographic functions till we need a token
1183  $this->mEmailAuthenticated = null;
1184  $this->mEmailToken = '';
1185  $this->mEmailTokenExpires = null;
1186  $this->mRegistration = wfTimestamp( TS_MW );
1187  $this->mGroups = [];
1188 
1189  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1190  }
1191 
1204  public function isItemLoaded( $item, $all = 'all' ) {
1205  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1206  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1207  }
1208 
1214  protected function setItemLoaded( $item ) {
1215  if ( is_array( $this->mLoadedItems ) ) {
1216  $this->mLoadedItems[$item] = true;
1217  }
1218  }
1219 
1225  private function loadFromSession() {
1226  // Deprecated hook
1227  $result = null;
1228  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1229  if ( $result !== null ) {
1230  return $result;
1231  }
1232 
1233  // MediaWiki\Session\Session already did the necessary authentication of the user
1234  // returned here, so just use it if applicable.
1235  $session = $this->getRequest()->getSession();
1236  $user = $session->getUser();
1237  if ( $user->isLoggedIn() ) {
1238  $this->loadFromUserObject( $user );
1239  // Other code expects these to be set in the session, so set them.
1240  $session->set( 'wsUserID', $this->getId() );
1241  $session->set( 'wsUserName', $this->getName() );
1242  $session->set( 'wsToken', $this->getToken() );
1243  return true;
1244  }
1245 
1246  return false;
1247  }
1248 
1256  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1257  // Paranoia
1258  $this->mId = intval( $this->mId );
1259 
1260  if ( !$this->mId ) {
1261  // Anonymous users are not in the database
1262  $this->loadDefaults();
1263  return false;
1264  }
1265 
1267  $db = wfGetDB( $index );
1268 
1269  $s = $db->selectRow(
1270  'user',
1271  self::selectFields(),
1272  [ 'user_id' => $this->mId ],
1273  __METHOD__,
1274  $options
1275  );
1276 
1277  $this->queryFlagsUsed = $flags;
1278  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1279 
1280  if ( $s !== false ) {
1281  // Initialise user table data
1282  $this->loadFromRow( $s );
1283  $this->mGroups = null; // deferred
1284  $this->getEditCount(); // revalidation for nulls
1285  return true;
1286  } else {
1287  // Invalid user_id
1288  $this->mId = 0;
1289  $this->loadDefaults();
1290  return false;
1291  }
1292  }
1293 
1303  protected function loadFromRow( $row, $data = null ) {
1304  $all = true;
1305 
1306  $this->mGroups = null; // deferred
1307 
1308  if ( isset( $row->user_name ) ) {
1309  $this->mName = $row->user_name;
1310  $this->mFrom = 'name';
1311  $this->setItemLoaded( 'name' );
1312  } else {
1313  $all = false;
1314  }
1315 
1316  if ( isset( $row->user_real_name ) ) {
1317  $this->mRealName = $row->user_real_name;
1318  $this->setItemLoaded( 'realname' );
1319  } else {
1320  $all = false;
1321  }
1322 
1323  if ( isset( $row->user_id ) ) {
1324  $this->mId = intval( $row->user_id );
1325  $this->mFrom = 'id';
1326  $this->setItemLoaded( 'id' );
1327  } else {
1328  $all = false;
1329  }
1330 
1331  if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
1332  self::$idCacheByName[$row->user_name] = $row->user_id;
1333  }
1334 
1335  if ( isset( $row->user_editcount ) ) {
1336  $this->mEditCount = $row->user_editcount;
1337  } else {
1338  $all = false;
1339  }
1340 
1341  if ( isset( $row->user_touched ) ) {
1342  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1343  } else {
1344  $all = false;
1345  }
1346 
1347  if ( isset( $row->user_token ) ) {
1348  // The definition for the column is binary(32), so trim the NULs
1349  // that appends. The previous definition was char(32), so trim
1350  // spaces too.
1351  $this->mToken = rtrim( $row->user_token, " \0" );
1352  if ( $this->mToken === '' ) {
1353  $this->mToken = null;
1354  }
1355  } else {
1356  $all = false;
1357  }
1358 
1359  if ( isset( $row->user_email ) ) {
1360  $this->mEmail = $row->user_email;
1361  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1362  $this->mEmailToken = $row->user_email_token;
1363  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1364  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1365  } else {
1366  $all = false;
1367  }
1368 
1369  if ( $all ) {
1370  $this->mLoadedItems = true;
1371  }
1372 
1373  if ( is_array( $data ) ) {
1374  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1375  $this->mGroups = $data['user_groups'];
1376  }
1377  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1378  $this->loadOptions( $data['user_properties'] );
1379  }
1380  }
1381  }
1382 
1388  protected function loadFromUserObject( $user ) {
1389  $user->load();
1390  $user->loadGroups();
1391  $user->loadOptions();
1392  foreach ( self::$mCacheVars as $var ) {
1393  $this->$var = $user->$var;
1394  }
1395  }
1396 
1400  private function loadGroups() {
1401  if ( is_null( $this->mGroups ) ) {
1402  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1403  ? wfGetDB( DB_MASTER )
1404  : wfGetDB( DB_SLAVE );
1405  $res = $db->select( 'user_groups',
1406  [ 'ug_group' ],
1407  [ 'ug_user' => $this->mId ],
1408  __METHOD__ );
1409  $this->mGroups = [];
1410  foreach ( $res as $row ) {
1411  $this->mGroups[] = $row->ug_group;
1412  }
1413  }
1414  }
1415 
1430  public function addAutopromoteOnceGroups( $event ) {
1432 
1433  if ( wfReadOnly() || !$this->getId() ) {
1434  return [];
1435  }
1436 
1437  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1438  if ( !count( $toPromote ) ) {
1439  return [];
1440  }
1441 
1442  if ( !$this->checkAndSetTouched() ) {
1443  return []; // raced out (bug T48834)
1444  }
1445 
1446  $oldGroups = $this->getGroups(); // previous groups
1447  foreach ( $toPromote as $group ) {
1448  $this->addGroup( $group );
1449  }
1450  // update groups in external authentication database
1451  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false ] );
1452  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1453 
1454  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1455 
1456  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1457  $logEntry->setPerformer( $this );
1458  $logEntry->setTarget( $this->getUserPage() );
1459  $logEntry->setParameters( [
1460  '4::oldgroups' => $oldGroups,
1461  '5::newgroups' => $newGroups,
1462  ] );
1463  $logid = $logEntry->insert();
1464  if ( $wgAutopromoteOnceLogInRC ) {
1465  $logEntry->publish( $logid );
1466  }
1467 
1468  return $toPromote;
1469  }
1470 
1480  protected function makeUpdateConditions( DatabaseBase $db, array $conditions ) {
1481  if ( $this->mTouched ) {
1482  // CAS check: only update if the row wasn't changed sicne it was loaded.
1483  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1484  }
1485 
1486  return $conditions;
1487  }
1488 
1498  protected function checkAndSetTouched() {
1499  $this->load();
1500 
1501  if ( !$this->mId ) {
1502  return false; // anon
1503  }
1504 
1505  // Get a new user_touched that is higher than the old one
1506  $newTouched = $this->newTouchedTimestamp();
1507 
1508  $dbw = wfGetDB( DB_MASTER );
1509  $dbw->update( 'user',
1510  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1511  $this->makeUpdateConditions( $dbw, [
1512  'user_id' => $this->mId,
1513  ] ),
1514  __METHOD__
1515  );
1516  $success = ( $dbw->affectedRows() > 0 );
1517 
1518  if ( $success ) {
1519  $this->mTouched = $newTouched;
1520  $this->clearSharedCache();
1521  } else {
1522  // Clears on failure too since that is desired if the cache is stale
1523  $this->clearSharedCache( 'refresh' );
1524  }
1525 
1526  return $success;
1527  }
1528 
1536  public function clearInstanceCache( $reloadFrom = false ) {
1537  $this->mNewtalk = -1;
1538  $this->mDatePreference = null;
1539  $this->mBlockedby = -1; # Unset
1540  $this->mHash = false;
1541  $this->mRights = null;
1542  $this->mEffectiveGroups = null;
1543  $this->mImplicitGroups = null;
1544  $this->mGroups = null;
1545  $this->mOptions = null;
1546  $this->mOptionsLoaded = false;
1547  $this->mEditCount = null;
1548 
1549  if ( $reloadFrom ) {
1550  $this->mLoadedItems = [];
1551  $this->mFrom = $reloadFrom;
1552  }
1553  }
1554 
1561  public static function getDefaultOptions() {
1563 
1564  static $defOpt = null;
1565  static $defOptLang = null;
1566 
1567  if ( $defOpt !== null && $defOptLang === $wgContLang->getCode() ) {
1568  // $wgContLang does not change (and should not change) mid-request,
1569  // but the unit tests change it anyway, and expect this method to
1570  // return values relevant to the current $wgContLang.
1571  return $defOpt;
1572  }
1573 
1574  $defOpt = $wgDefaultUserOptions;
1575  // Default language setting
1576  $defOptLang = $wgContLang->getCode();
1577  $defOpt['language'] = $defOptLang;
1578  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1579  $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
1580  }
1581  $namespaces = MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
1582  foreach ( $namespaces as $nsnum => $nsname ) {
1583  $defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
1584  }
1585  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1586 
1587  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1588 
1589  return $defOpt;
1590  }
1591 
1598  public static function getDefaultOption( $opt ) {
1599  $defOpts = self::getDefaultOptions();
1600  if ( isset( $defOpts[$opt] ) ) {
1601  return $defOpts[$opt];
1602  } else {
1603  return null;
1604  }
1605  }
1606 
1613  private function getBlockedStatus( $bFromSlave = true ) {
1615 
1616  if ( -1 != $this->mBlockedby ) {
1617  return;
1618  }
1619 
1620  wfDebug( __METHOD__ . ": checking...\n" );
1621 
1622  // Initialize data...
1623  // Otherwise something ends up stomping on $this->mBlockedby when
1624  // things get lazy-loaded later, causing false positive block hits
1625  // due to -1 !== 0. Probably session-related... Nothing should be
1626  // overwriting mBlockedby, surely?
1627  $this->load();
1628 
1629  # We only need to worry about passing the IP address to the Block generator if the
1630  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1631  # know which IP address they're actually coming from
1632  $ip = null;
1633  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1634  // $wgUser->getName() only works after the end of Setup.php. Until
1635  // then, assume it's a logged-out user.
1636  $globalUserName = $wgUser->isSafeToLoad()
1637  ? $wgUser->getName()
1638  : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1639  if ( $this->getName() === $globalUserName ) {
1640  $ip = $this->getRequest()->getIP();
1641  }
1642  }
1643 
1644  // User/IP blocking
1645  $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1646 
1647  // Proxy blocking
1648  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1649  // Local list
1650  if ( self::isLocallyBlockedProxy( $ip ) ) {
1651  $block = new Block;
1652  $block->setBlocker( wfMessage( 'proxyblocker' )->text() );
1653  $block->mReason = wfMessage( 'proxyblockreason' )->text();
1654  $block->setTarget( $ip );
1655  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1656  $block = new Block;
1657  $block->setBlocker( wfMessage( 'sorbs' )->text() );
1658  $block->mReason = wfMessage( 'sorbsreason' )->text();
1659  $block->setTarget( $ip );
1660  }
1661  }
1662 
1663  // (bug 23343) Apply IP blocks to the contents of XFF headers, if enabled
1664  if ( !$block instanceof Block
1665  && $wgApplyIpBlocksToXff
1666  && $ip !== null
1667  && !in_array( $ip, $wgProxyWhitelist )
1668  ) {
1669  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1670  $xff = array_map( 'trim', explode( ',', $xff ) );
1671  $xff = array_diff( $xff, [ $ip ] );
1672  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1673  $block = Block::chooseBlock( $xffblocks, $xff );
1674  if ( $block instanceof Block ) {
1675  # Mangle the reason to alert the user that the block
1676  # originated from matching the X-Forwarded-For header.
1677  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
1678  }
1679  }
1680 
1681  if ( $block instanceof Block ) {
1682  wfDebug( __METHOD__ . ": Found block.\n" );
1683  $this->mBlock = $block;
1684  $this->mBlockedby = $block->getByName();
1685  $this->mBlockreason = $block->mReason;
1686  $this->mHideName = $block->mHideName;
1687  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1688  } else {
1689  $this->mBlockedby = '';
1690  $this->mHideName = 0;
1691  $this->mAllowUsertalk = false;
1692  }
1693 
1694  // Extensions
1695  Hooks::run( 'GetBlockedStatus', [ &$this ] );
1696 
1697  }
1698 
1706  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1708 
1709  if ( !$wgEnableDnsBlacklist ) {
1710  return false;
1711  }
1712 
1713  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
1714  return false;
1715  }
1716 
1717  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
1718  }
1719 
1727  public function inDnsBlacklist( $ip, $bases ) {
1728 
1729  $found = false;
1730  // @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
1731  if ( IP::isIPv4( $ip ) ) {
1732  // Reverse IP, bug 21255
1733  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1734 
1735  foreach ( (array)$bases as $base ) {
1736  // Make hostname
1737  // If we have an access key, use that too (ProjectHoneypot, etc.)
1738  $basename = $base;
1739  if ( is_array( $base ) ) {
1740  if ( count( $base ) >= 2 ) {
1741  // Access key is 1, base URL is 0
1742  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1743  } else {
1744  $host = "$ipReversed.{$base[0]}";
1745  }
1746  $basename = $base[0];
1747  } else {
1748  $host = "$ipReversed.$base";
1749  }
1750 
1751  // Send query
1752  $ipList = gethostbynamel( $host );
1753 
1754  if ( $ipList ) {
1755  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1756  $found = true;
1757  break;
1758  } else {
1759  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1760  }
1761  }
1762  }
1763 
1764  return $found;
1765  }
1766 
1774  public static function isLocallyBlockedProxy( $ip ) {
1776 
1777  if ( !$wgProxyList ) {
1778  return false;
1779  }
1780 
1781  if ( !is_array( $wgProxyList ) ) {
1782  // Load from the specified file
1783  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1784  }
1785 
1786  if ( !is_array( $wgProxyList ) ) {
1787  $ret = false;
1788  } elseif ( array_search( $ip, $wgProxyList ) !== false ) {
1789  $ret = true;
1790  } elseif ( array_key_exists( $ip, $wgProxyList ) ) {
1791  // Old-style flipped proxy list
1792  $ret = true;
1793  } else {
1794  $ret = false;
1795  }
1796  return $ret;
1797  }
1798 
1804  public function isPingLimitable() {
1806  if ( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1807  // No other good way currently to disable rate limits
1808  // for specific IPs. :P
1809  // But this is a crappy hack and should die.
1810  return false;
1811  }
1812  return !$this->isAllowed( 'noratelimit' );
1813  }
1814 
1829  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1830  // Call the 'PingLimiter' hook
1831  $result = false;
1832  if ( !Hooks::run( 'PingLimiter', [ &$this, $action, &$result, $incrBy ] ) ) {
1833  return $result;
1834  }
1835 
1837  if ( !isset( $wgRateLimits[$action] ) ) {
1838  return false;
1839  }
1840 
1841  // Some groups shouldn't trigger the ping limiter, ever
1842  if ( !$this->isPingLimitable() ) {
1843  return false;
1844  }
1845 
1846  $limits = $wgRateLimits[$action];
1847  $keys = [];
1848  $id = $this->getId();
1849  $userLimit = false;
1850  $isNewbie = $this->isNewbie();
1851 
1852  if ( $id == 0 ) {
1853  // limits for anons
1854  if ( isset( $limits['anon'] ) ) {
1855  $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1856  }
1857  } else {
1858  // limits for logged-in users
1859  if ( isset( $limits['user'] ) ) {
1860  $userLimit = $limits['user'];
1861  }
1862  // limits for newbie logged-in users
1863  if ( $isNewbie && isset( $limits['newbie'] ) ) {
1864  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
1865  }
1866  }
1867 
1868  // limits for anons and for newbie logged-in users
1869  if ( $isNewbie ) {
1870  // ip-based limits
1871  if ( isset( $limits['ip'] ) ) {
1872  $ip = $this->getRequest()->getIP();
1873  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1874  }
1875  // subnet-based limits
1876  if ( isset( $limits['subnet'] ) ) {
1877  $ip = $this->getRequest()->getIP();
1878  $subnet = IP::getSubnet( $ip );
1879  if ( $subnet !== false ) {
1880  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1881  }
1882  }
1883  }
1884 
1885  // Check for group-specific permissions
1886  // If more than one group applies, use the group with the highest limit ratio (max/period)
1887  foreach ( $this->getGroups() as $group ) {
1888  if ( isset( $limits[$group] ) ) {
1889  if ( $userLimit === false
1890  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1891  ) {
1892  $userLimit = $limits[$group];
1893  }
1894  }
1895  }
1896 
1897  // Set the user limit key
1898  if ( $userLimit !== false ) {
1899  list( $max, $period ) = $userLimit;
1900  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
1901  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit;
1902  }
1903 
1904  // ip-based limits for all ping-limitable users
1905  if ( isset( $limits['ip-all'] ) ) {
1906  $ip = $this->getRequest()->getIP();
1907  // ignore if user limit is more permissive
1908  if ( $isNewbie || $userLimit === false
1909  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1910  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
1911  }
1912  }
1913 
1914  // subnet-based limits for all ping-limitable users
1915  if ( isset( $limits['subnet-all'] ) ) {
1916  $ip = $this->getRequest()->getIP();
1917  $subnet = IP::getSubnet( $ip );
1918  if ( $subnet !== false ) {
1919  // ignore if user limit is more permissive
1920  if ( $isNewbie || $userLimit === false
1921  || $limits['ip-all'][0] / $limits['ip-all'][1]
1922  > $userLimit[0] / $userLimit[1] ) {
1923  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
1924  }
1925  }
1926  }
1927 
1929 
1930  $triggered = false;
1931  foreach ( $keys as $key => $limit ) {
1932  list( $max, $period ) = $limit;
1933  $summary = "(limit $max in {$period}s)";
1934  $count = $cache->get( $key );
1935  // Already pinged?
1936  if ( $count ) {
1937  if ( $count >= $max ) {
1938  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
1939  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
1940  $triggered = true;
1941  } else {
1942  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
1943  }
1944  } else {
1945  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
1946  if ( $incrBy > 0 ) {
1947  $cache->add( $key, 0, intval( $period ) ); // first ping
1948  }
1949  }
1950  if ( $incrBy > 0 ) {
1951  $cache->incr( $key, $incrBy );
1952  }
1953  }
1954 
1955  return $triggered;
1956  }
1957 
1965  public function isBlocked( $bFromSlave = true ) {
1966  return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
1967  }
1968 
1975  public function getBlock( $bFromSlave = true ) {
1976  $this->getBlockedStatus( $bFromSlave );
1977  return $this->mBlock instanceof Block ? $this->mBlock : null;
1978  }
1979 
1987  public function isBlockedFrom( $title, $bFromSlave = false ) {
1989 
1990  $blocked = $this->isBlocked( $bFromSlave );
1991  $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
1992  // If a user's name is suppressed, they cannot make edits anywhere
1993  if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
1994  && $title->getNamespace() == NS_USER_TALK ) {
1995  $blocked = false;
1996  wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
1997  }
1998 
1999  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
2000 
2001  return $blocked;
2002  }
2003 
2008  public function blockedBy() {
2009  $this->getBlockedStatus();
2010  return $this->mBlockedby;
2011  }
2012 
2017  public function blockedFor() {
2018  $this->getBlockedStatus();
2019  return $this->mBlockreason;
2020  }
2021 
2026  public function getBlockId() {
2027  $this->getBlockedStatus();
2028  return ( $this->mBlock ? $this->mBlock->getId() : false );
2029  }
2030 
2039  public function isBlockedGlobally( $ip = '' ) {
2040  return $this->getGlobalBlock( $ip ) instanceof Block;
2041  }
2042 
2053  public function getGlobalBlock( $ip = '' ) {
2054  if ( $this->mGlobalBlock !== null ) {
2055  return $this->mGlobalBlock ?: null;
2056  }
2057  // User is already an IP?
2058  if ( IP::isIPAddress( $this->getName() ) ) {
2059  $ip = $this->getName();
2060  } elseif ( !$ip ) {
2061  $ip = $this->getRequest()->getIP();
2062  }
2063  $blocked = false;
2064  $block = null;
2065  Hooks::run( 'UserIsBlockedGlobally', [ &$this, $ip, &$blocked, &$block ] );
2066 
2067  if ( $blocked && $block === null ) {
2068  // back-compat: UserIsBlockedGlobally didn't have $block param first
2069  $block = new Block;
2070  $block->setTarget( $ip );
2071  }
2072 
2073  $this->mGlobalBlock = $blocked ? $block : false;
2074  return $this->mGlobalBlock ?: null;
2075  }
2076 
2082  public function isLocked() {
2083  if ( $this->mLocked !== null ) {
2084  return $this->mLocked;
2085  }
2086  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2087  $this->mLocked = $authUser && $authUser->isLocked();
2088  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2089  return $this->mLocked;
2090  }
2091 
2097  public function isHidden() {
2098  if ( $this->mHideName !== null ) {
2099  return $this->mHideName;
2100  }
2101  $this->getBlockedStatus();
2102  if ( !$this->mHideName ) {
2103  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2104  $this->mHideName = $authUser && $authUser->isHidden();
2105  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2106  }
2107  return $this->mHideName;
2108  }
2109 
2114  public function getId() {
2115  if ( $this->mId === null && $this->mName !== null && User::isIP( $this->mName ) ) {
2116  // Special case, we know the user is anonymous
2117  return 0;
2118  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2119  // Don't load if this was initialized from an ID
2120  $this->load();
2121  }
2122 
2123  return (int)$this->mId;
2124  }
2125 
2130  public function setId( $v ) {
2131  $this->mId = $v;
2132  $this->clearInstanceCache( 'id' );
2133  }
2134 
2139  public function getName() {
2140  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2141  // Special case optimisation
2142  return $this->mName;
2143  } else {
2144  $this->load();
2145  if ( $this->mName === false ) {
2146  // Clean up IPs
2147  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2148  }
2149  return $this->mName;
2150  }
2151  }
2152 
2166  public function setName( $str ) {
2167  $this->load();
2168  $this->mName = $str;
2169  }
2170 
2175  public function getTitleKey() {
2176  return str_replace( ' ', '_', $this->getName() );
2177  }
2178 
2183  public function getNewtalk() {
2184  $this->load();
2185 
2186  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2187  if ( $this->mNewtalk === -1 ) {
2188  $this->mNewtalk = false; # reset talk page status
2189 
2190  // Check memcached separately for anons, who have no
2191  // entire User object stored in there.
2192  if ( !$this->mId ) {
2194  if ( $wgDisableAnonTalk ) {
2195  // Anon newtalk disabled by configuration.
2196  $this->mNewtalk = false;
2197  } else {
2198  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2199  }
2200  } else {
2201  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2202  }
2203  }
2204 
2205  return (bool)$this->mNewtalk;
2206  }
2207 
2221  public function getNewMessageLinks() {
2222  $talks = [];
2223  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$this, &$talks ] ) ) {
2224  return $talks;
2225  } elseif ( !$this->getNewtalk() ) {
2226  return [];
2227  }
2228  $utp = $this->getTalkPage();
2229  $dbr = wfGetDB( DB_SLAVE );
2230  // Get the "last viewed rev" timestamp from the oldest message notification
2231  $timestamp = $dbr->selectField( 'user_newtalk',
2232  'MIN(user_last_timestamp)',
2233  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2234  __METHOD__ );
2236  return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2237  }
2238 
2244  public function getNewMessageRevisionId() {
2245  $newMessageRevisionId = null;
2246  $newMessageLinks = $this->getNewMessageLinks();
2247  if ( $newMessageLinks ) {
2248  // Note: getNewMessageLinks() never returns more than a single link
2249  // and it is always for the same wiki, but we double-check here in
2250  // case that changes some time in the future.
2251  if ( count( $newMessageLinks ) === 1
2252  && $newMessageLinks[0]['wiki'] === wfWikiID()
2253  && $newMessageLinks[0]['rev']
2254  ) {
2256  $newMessageRevision = $newMessageLinks[0]['rev'];
2257  $newMessageRevisionId = $newMessageRevision->getId();
2258  }
2259  }
2260  return $newMessageRevisionId;
2261  }
2262 
2271  protected function checkNewtalk( $field, $id ) {
2272  $dbr = wfGetDB( DB_SLAVE );
2273 
2274  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2275 
2276  return $ok !== false;
2277  }
2278 
2286  protected function updateNewtalk( $field, $id, $curRev = null ) {
2287  // Get timestamp of the talk page revision prior to the current one
2288  $prevRev = $curRev ? $curRev->getPrevious() : false;
2289  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2290  // Mark the user as having new messages since this revision
2291  $dbw = wfGetDB( DB_MASTER );
2292  $dbw->insert( 'user_newtalk',
2293  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2294  __METHOD__,
2295  'IGNORE' );
2296  if ( $dbw->affectedRows() ) {
2297  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2298  return true;
2299  } else {
2300  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2301  return false;
2302  }
2303  }
2304 
2311  protected function deleteNewtalk( $field, $id ) {
2312  $dbw = wfGetDB( DB_MASTER );
2313  $dbw->delete( 'user_newtalk',
2314  [ $field => $id ],
2315  __METHOD__ );
2316  if ( $dbw->affectedRows() ) {
2317  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2318  return true;
2319  } else {
2320  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2321  return false;
2322  }
2323  }
2324 
2331  public function setNewtalk( $val, $curRev = null ) {
2332  if ( wfReadOnly() ) {
2333  return;
2334  }
2335 
2336  $this->load();
2337  $this->mNewtalk = $val;
2338 
2339  if ( $this->isAnon() ) {
2340  $field = 'user_ip';
2341  $id = $this->getName();
2342  } else {
2343  $field = 'user_id';
2344  $id = $this->getId();
2345  }
2346 
2347  if ( $val ) {
2348  $changed = $this->updateNewtalk( $field, $id, $curRev );
2349  } else {
2350  $changed = $this->deleteNewtalk( $field, $id );
2351  }
2352 
2353  if ( $changed ) {
2354  $this->invalidateCache();
2355  }
2356  }
2357 
2363  private function newTouchedTimestamp() {
2365 
2366  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2367  if ( $this->mTouched && $time <= $this->mTouched ) {
2368  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2369  }
2370 
2371  return $time;
2372  }
2373 
2384  public function clearSharedCache( $mode = 'changed' ) {
2385  if ( !$this->getId() ) {
2386  return;
2387  }
2388 
2390  $key = $this->getCacheKey( $cache );
2391  if ( $mode === 'refresh' ) {
2392  $cache->delete( $key, 1 );
2393  } else {
2394  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
2395  function() use ( $cache, $key ) {
2396  $cache->delete( $key );
2397  }
2398  );
2399  }
2400  }
2401 
2407  public function invalidateCache() {
2408  $this->touch();
2409  $this->clearSharedCache();
2410  }
2411 
2424  public function touch() {
2425  $id = $this->getId();
2426  if ( $id ) {
2427  $key = wfMemcKey( 'user-quicktouched', 'id', $id );
2428  ObjectCache::getMainWANInstance()->touchCheckKey( $key );
2429  $this->mQuickTouched = null;
2430  }
2431  }
2432 
2438  public function validateCache( $timestamp ) {
2439  return ( $timestamp >= $this->getTouched() );
2440  }
2441 
2450  public function getTouched() {
2451  $this->load();
2452 
2453  if ( $this->mId ) {
2454  if ( $this->mQuickTouched === null ) {
2455  $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
2457 
2458  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2459  }
2460 
2461  return max( $this->mTouched, $this->mQuickTouched );
2462  }
2463 
2464  return $this->mTouched;
2465  }
2466 
2472  public function getDBTouched() {
2473  $this->load();
2474 
2475  return $this->mTouched;
2476  }
2477 
2483  public function getPassword() {
2484  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2485  }
2486 
2492  public function getTemporaryPassword() {
2493  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2494  }
2495 
2512  public function setPassword( $str ) {
2514 
2515  if ( !$wgDisableAuthManager ) {
2516  return $this->setPasswordInternal( $str );
2517  }
2518 
2519  if ( $str !== null ) {
2520  if ( !$wgAuth->allowPasswordChange() ) {
2521  throw new PasswordError( wfMessage( 'password-change-forbidden' )->text() );
2522  }
2523 
2524  $status = $this->checkPasswordValidity( $str );
2525  if ( !$status->isGood() ) {
2526  throw new PasswordError( $status->getMessage()->text() );
2527  }
2528  }
2529 
2530  if ( !$wgAuth->setPassword( $this, $str ) ) {
2531  throw new PasswordError( wfMessage( 'externaldberror' )->text() );
2532  }
2533 
2534  $this->setOption( 'watchlisttoken', false );
2535  $this->setPasswordInternal( $str );
2536 
2537  return true;
2538  }
2539 
2548  public function setInternalPassword( $str ) {
2550 
2551  if ( !$wgDisableAuthManager ) {
2552  $this->setPasswordInternal( $str );
2553  }
2554 
2555  if ( $wgAuth->allowSetLocalPassword() ) {
2556  $this->setOption( 'watchlisttoken', false );
2557  $this->setPasswordInternal( $str );
2558  }
2559  }
2560 
2569  private function setPasswordInternal( $str ) {
2571 
2572  if ( $wgDisableAuthManager ) {
2573  $id = self::idFromName( $this->getName(), self::READ_LATEST );
2574  if ( $id == 0 ) {
2575  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2576  }
2577 
2578  $passwordFactory = new PasswordFactory();
2579  $passwordFactory->init( RequestContext::getMain()->getConfig() );
2580  $dbw = wfGetDB( DB_MASTER );
2581  $dbw->update(
2582  'user',
2583  [
2584  'user_password' => $passwordFactory->newFromPlaintext( $str )->toString(),
2585  'user_newpassword' => PasswordFactory::newInvalidPassword()->toString(),
2586  'user_newpass_time' => $dbw->timestampOrNull( null ),
2587  ],
2588  [
2589  'user_id' => $id,
2590  ],
2591  __METHOD__
2592  );
2593 
2594  // When the main password is changed, invalidate all bot passwords too
2596  } else {
2597  $manager = AuthManager::singleton();
2598 
2599  // If the user doesn't exist yet, fail
2600  if ( !$manager->userExists( $this->getName() ) ) {
2601  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2602  }
2603 
2604  $status = $this->changeAuthenticationData( [
2605  'username' => $this->getName(),
2606  'password' => $str,
2607  'retype' => $str,
2608  ] );
2609  if ( !$status->isGood() ) {
2611  ->info( __METHOD__ . ': Password change rejected: '
2612  . $status->getWikiText( null, null, 'en' ) );
2613  return false;
2614  }
2615 
2616  $this->setOption( 'watchlisttoken', false );
2617  }
2618 
2619  SessionManager::singleton()->invalidateSessionsForUser( $this );
2620 
2621  return true;
2622  }
2623 
2636  public function changeAuthenticationData( array $data ) {
2638  if ( $wgDisableAuthManager ) {
2639  throw new LogicException( __METHOD__ . ' cannot be called when $wgDisableAuthManager '
2640  . 'is true' );
2641  }
2642 
2643  $manager = AuthManager::singleton();
2644  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2645  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2646 
2647  $status = Status::newGood( 'ignored' );
2648  foreach ( $reqs as $req ) {
2649  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2650  }
2651  if ( $status->getValue() === 'ignored' ) {
2652  $status->warning( 'authenticationdatachange-ignored' );
2653  }
2654 
2655  if ( $status->isGood() ) {
2656  foreach ( $reqs as $req ) {
2657  $manager->changeAuthenticationData( $req );
2658  }
2659  }
2660  return $status;
2661  }
2662 
2669  public function getToken( $forceCreation = true ) {
2671 
2672  $this->load();
2673  if ( !$this->mToken && $forceCreation ) {
2674  $this->setToken();
2675  }
2676 
2677  if ( !$this->mToken ) {
2678  // The user doesn't have a token, return null to indicate that.
2679  return null;
2680  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2681  // We return a random value here so existing token checks are very
2682  // likely to fail.
2683  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2684  } elseif ( $wgAuthenticationTokenVersion === null ) {
2685  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2686  return $this->mToken;
2687  } else {
2688  // $wgAuthenticationTokenVersion in use, so hmac it.
2689  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2690 
2691  // The raw hash can be overly long. Shorten it up.
2692  $len = max( 32, self::TOKEN_LENGTH );
2693  if ( strlen( $ret ) < $len ) {
2694  // Should never happen, even md5 is 128 bits
2695  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2696  }
2697  return substr( $ret, -$len );
2698  }
2699  }
2700 
2707  public function setToken( $token = false ) {
2708  $this->load();
2709  if ( $this->mToken === self::INVALID_TOKEN ) {
2711  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2712  } elseif ( !$token ) {
2713  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2714  } else {
2715  $this->mToken = $token;
2716  }
2717  }
2718 
2727  public function setNewpassword( $str, $throttle = true ) {
2729 
2730  if ( $wgDisableAuthManager ) {
2731  $id = $this->getId();
2732  if ( $id == 0 ) {
2733  throw new LogicException( 'Cannot set new password for a user that is not in the database.' );
2734  }
2735 
2736  $dbw = wfGetDB( DB_MASTER );
2737 
2738  $passwordFactory = new PasswordFactory();
2739  $passwordFactory->init( RequestContext::getMain()->getConfig() );
2740  $update = [
2741  'user_newpassword' => $passwordFactory->newFromPlaintext( $str )->toString(),
2742  ];
2743 
2744  if ( $str === null ) {
2745  $update['user_newpass_time'] = null;
2746  } elseif ( $throttle ) {
2747  $update['user_newpass_time'] = $dbw->timestamp();
2748  }
2749 
2750  $dbw->update( 'user', $update, [ 'user_id' => $id ], __METHOD__ );
2751  } else {
2752  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2753  }
2754  }
2755 
2762  public function isPasswordReminderThrottled() {
2764 
2765  if ( $wgDisableAuthManager ) {
2766  if ( !$wgPasswordReminderResendTime ) {
2767  return false;
2768  }
2769 
2770  $this->load();
2771 
2772  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
2773  ? wfGetDB( DB_MASTER )
2774  : wfGetDB( DB_SLAVE );
2775  $newpassTime = $db->selectField(
2776  'user',
2777  'user_newpass_time',
2778  [ 'user_id' => $this->getId() ],
2779  __METHOD__
2780  );
2781 
2782  if ( $newpassTime === null ) {
2783  return false;
2784  }
2785  $expiry = wfTimestamp( TS_UNIX, $newpassTime ) + $wgPasswordReminderResendTime * 3600;
2786  return time() < $expiry;
2787  } else {
2788  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2789  }
2790  }
2791 
2796  public function getEmail() {
2797  $this->load();
2798  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2799  return $this->mEmail;
2800  }
2801 
2807  $this->load();
2808  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2810  }
2811 
2816  public function setEmail( $str ) {
2817  $this->load();
2818  if ( $str == $this->mEmail ) {
2819  return;
2820  }
2821  $this->invalidateEmail();
2822  $this->mEmail = $str;
2823  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2824  }
2825 
2833  public function setEmailWithConfirmation( $str ) {
2835 
2836  if ( !$wgEnableEmail ) {
2837  return Status::newFatal( 'emaildisabled' );
2838  }
2839 
2840  $oldaddr = $this->getEmail();
2841  if ( $str === $oldaddr ) {
2842  return Status::newGood( true );
2843  }
2844 
2845  $type = $oldaddr != '' ? 'changed' : 'set';
2846  $notificationResult = null;
2847 
2848  if ( $wgEmailAuthentication ) {
2849  // Send the user an email notifying the user of the change in registered
2850  // email address on their previous email address
2851  if ( $type == 'changed' ) {
2852  $change = $str != '' ? 'changed' : 'removed';
2853  $notificationResult = $this->sendMail(
2854  wfMessage( 'notificationemail_subject_' . $change )->text(),
2855  wfMessage( 'notificationemail_body_' . $change,
2856  $this->getRequest()->getIP(),
2857  $this->getName(),
2858  $str )->text()
2859  );
2860  }
2861  }
2862 
2863  $this->setEmail( $str );
2864 
2865  if ( $str !== '' && $wgEmailAuthentication ) {
2866  // Send a confirmation request to the new address if needed
2867  $result = $this->sendConfirmationMail( $type );
2868 
2869  if ( $notificationResult !== null ) {
2870  $result->merge( $notificationResult );
2871  }
2872 
2873  if ( $result->isGood() ) {
2874  // Say to the caller that a confirmation and notification mail has been sent
2875  $result->value = 'eauth';
2876  }
2877  } else {
2878  $result = Status::newGood( true );
2879  }
2880 
2881  return $result;
2882  }
2883 
2888  public function getRealName() {
2889  if ( !$this->isItemLoaded( 'realname' ) ) {
2890  $this->load();
2891  }
2892 
2893  return $this->mRealName;
2894  }
2895 
2900  public function setRealName( $str ) {
2901  $this->load();
2902  $this->mRealName = $str;
2903  }
2904 
2915  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2917  $this->loadOptions();
2918 
2919  # We want 'disabled' preferences to always behave as the default value for
2920  # users, even if they have set the option explicitly in their settings (ie they
2921  # set it, and then it was disabled removing their ability to change it). But
2922  # we don't want to erase the preferences in the database in case the preference
2923  # is re-enabled again. So don't touch $mOptions, just override the returned value
2924  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2925  return self::getDefaultOption( $oname );
2926  }
2927 
2928  if ( array_key_exists( $oname, $this->mOptions ) ) {
2929  return $this->mOptions[$oname];
2930  } else {
2931  return $defaultOverride;
2932  }
2933  }
2934 
2943  public function getOptions( $flags = 0 ) {
2945  $this->loadOptions();
2947 
2948  # We want 'disabled' preferences to always behave as the default value for
2949  # users, even if they have set the option explicitly in their settings (ie they
2950  # set it, and then it was disabled removing their ability to change it). But
2951  # we don't want to erase the preferences in the database in case the preference
2952  # is re-enabled again. So don't touch $mOptions, just override the returned value
2953  foreach ( $wgHiddenPrefs as $pref ) {
2954  $default = self::getDefaultOption( $pref );
2955  if ( $default !== null ) {
2956  $options[$pref] = $default;
2957  }
2958  }
2959 
2960  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2961  $options = array_diff_assoc( $options, self::getDefaultOptions() );
2962  }
2963 
2964  return $options;
2965  }
2966 
2974  public function getBoolOption( $oname ) {
2975  return (bool)$this->getOption( $oname );
2976  }
2977 
2986  public function getIntOption( $oname, $defaultOverride = 0 ) {
2987  $val = $this->getOption( $oname );
2988  if ( $val == '' ) {
2989  $val = $defaultOverride;
2990  }
2991  return intval( $val );
2992  }
2993 
3002  public function setOption( $oname, $val ) {
3003  $this->loadOptions();
3004 
3005  // Explicitly NULL values should refer to defaults
3006  if ( is_null( $val ) ) {
3007  $val = self::getDefaultOption( $oname );
3008  }
3009 
3010  $this->mOptions[$oname] = $val;
3011  }
3012 
3023  public function getTokenFromOption( $oname ) {
3025 
3026  $id = $this->getId();
3027  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3028  return false;
3029  }
3030 
3031  $token = $this->getOption( $oname );
3032  if ( !$token ) {
3033  // Default to a value based on the user token to avoid space
3034  // wasted on storing tokens for all users. When this option
3035  // is set manually by the user, only then is it stored.
3036  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3037  }
3038 
3039  return $token;
3040  }
3041 
3051  public function resetTokenFromOption( $oname ) {
3053  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3054  return false;
3055  }
3056 
3057  $token = MWCryptRand::generateHex( 40 );
3058  $this->setOption( $oname, $token );
3059  return $token;
3060  }
3061 
3085  public static function listOptionKinds() {
3086  return [
3087  'registered',
3088  'registered-multiselect',
3089  'registered-checkmatrix',
3090  'userjs',
3091  'special',
3092  'unused'
3093  ];
3094  }
3095 
3108  public function getOptionKinds( IContextSource $context, $options = null ) {
3109  $this->loadOptions();
3110  if ( $options === null ) {
3112  }
3113 
3114  $prefs = Preferences::getPreferences( $this, $context );
3115  $mapping = [];
3116 
3117  // Pull out the "special" options, so they don't get converted as
3118  // multiselect or checkmatrix.
3119  $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
3120  foreach ( $specialOptions as $name => $value ) {
3121  unset( $prefs[$name] );
3122  }
3123 
3124  // Multiselect and checkmatrix options are stored in the database with
3125  // one key per option, each having a boolean value. Extract those keys.
3126  $multiselectOptions = [];
3127  foreach ( $prefs as $name => $info ) {
3128  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3129  ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
3130  $opts = HTMLFormField::flattenOptions( $info['options'] );
3131  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
3132 
3133  foreach ( $opts as $value ) {
3134  $multiselectOptions["$prefix$value"] = true;
3135  }
3136 
3137  unset( $prefs[$name] );
3138  }
3139  }
3140  $checkmatrixOptions = [];
3141  foreach ( $prefs as $name => $info ) {
3142  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3143  ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
3144  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3145  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3146  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
3147 
3148  foreach ( $columns as $column ) {
3149  foreach ( $rows as $row ) {
3150  $checkmatrixOptions["$prefix$column-$row"] = true;
3151  }
3152  }
3153 
3154  unset( $prefs[$name] );
3155  }
3156  }
3157 
3158  // $value is ignored
3159  foreach ( $options as $key => $value ) {
3160  if ( isset( $prefs[$key] ) ) {
3161  $mapping[$key] = 'registered';
3162  } elseif ( isset( $multiselectOptions[$key] ) ) {
3163  $mapping[$key] = 'registered-multiselect';
3164  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3165  $mapping[$key] = 'registered-checkmatrix';
3166  } elseif ( isset( $specialOptions[$key] ) ) {
3167  $mapping[$key] = 'special';
3168  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3169  $mapping[$key] = 'userjs';
3170  } else {
3171  $mapping[$key] = 'unused';
3172  }
3173  }
3174 
3175  return $mapping;
3176  }
3177 
3192  public function resetOptions(
3193  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3194  IContextSource $context = null
3195  ) {
3196  $this->load();
3197  $defaultOptions = self::getDefaultOptions();
3198 
3199  if ( !is_array( $resetKinds ) ) {
3200  $resetKinds = [ $resetKinds ];
3201  }
3202 
3203  if ( in_array( 'all', $resetKinds ) ) {
3204  $newOptions = $defaultOptions;
3205  } else {
3206  if ( $context === null ) {
3208  }
3209 
3210  $optionKinds = $this->getOptionKinds( $context );
3211  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3212  $newOptions = [];
3213 
3214  // Use default values for the options that should be deleted, and
3215  // copy old values for the ones that shouldn't.
3216  foreach ( $this->mOptions as $key => $value ) {
3217  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3218  if ( array_key_exists( $key, $defaultOptions ) ) {
3219  $newOptions[$key] = $defaultOptions[$key];
3220  }
3221  } else {
3222  $newOptions[$key] = $value;
3223  }
3224  }
3225  }
3226 
3227  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3228 
3229  $this->mOptions = $newOptions;
3230  $this->mOptionsLoaded = true;
3231  }
3232 
3237  public function getDatePreference() {
3238  // Important migration for old data rows
3239  if ( is_null( $this->mDatePreference ) ) {
3240  global $wgLang;
3241  $value = $this->getOption( 'date' );
3242  $map = $wgLang->getDatePreferenceMigrationMap();
3243  if ( isset( $map[$value] ) ) {
3244  $value = $map[$value];
3245  }
3246  $this->mDatePreference = $value;
3247  }
3248  return $this->mDatePreference;
3249  }
3250 
3257  public function requiresHTTPS() {
3259  if ( !$wgSecureLogin ) {
3260  return false;
3261  } else {
3262  $https = $this->getBoolOption( 'prefershttps' );
3263  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3264  if ( $https ) {
3265  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3266  }
3267  return $https;
3268  }
3269  }
3270 
3276  public function getStubThreshold() {
3277  global $wgMaxArticleSize; # Maximum article size, in Kb
3278  $threshold = $this->getIntOption( 'stubthreshold' );
3279  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3280  // If they have set an impossible value, disable the preference
3281  // so we can use the parser cache again.
3282  $threshold = 0;
3283  }
3284  return $threshold;
3285  }
3286 
3291  public function getRights() {
3292  if ( is_null( $this->mRights ) ) {
3293  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3294 
3295  // Deny any rights denied by the user's session, unless this
3296  // endpoint has no sessions.
3297  if ( !defined( 'MW_NO_SESSION' ) ) {
3298  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3299  if ( $allowedRights !== null ) {
3300  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3301  }
3302  }
3303 
3304  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3305  // Force reindexation of rights when a hook has unset one of them
3306  $this->mRights = array_values( array_unique( $this->mRights ) );
3307  }
3308  return $this->mRights;
3309  }
3310 
3316  public function getGroups() {
3317  $this->load();
3318  $this->loadGroups();
3319  return $this->mGroups;
3320  }
3321 
3329  public function getEffectiveGroups( $recache = false ) {
3330  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3331  $this->mEffectiveGroups = array_unique( array_merge(
3332  $this->getGroups(), // explicit groups
3333  $this->getAutomaticGroups( $recache ) // implicit groups
3334  ) );
3335  // Hook for additional groups
3336  Hooks::run( 'UserEffectiveGroups', [ &$this, &$this->mEffectiveGroups ] );
3337  // Force reindexation of groups when a hook has unset one of them
3338  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3339  }
3340  return $this->mEffectiveGroups;
3341  }
3342 
3350  public function getAutomaticGroups( $recache = false ) {
3351  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3352  $this->mImplicitGroups = [ '*' ];
3353  if ( $this->getId() ) {
3354  $this->mImplicitGroups[] = 'user';
3355 
3356  $this->mImplicitGroups = array_unique( array_merge(
3357  $this->mImplicitGroups,
3359  ) );
3360  }
3361  if ( $recache ) {
3362  // Assure data consistency with rights/groups,
3363  // as getEffectiveGroups() depends on this function
3364  $this->mEffectiveGroups = null;
3365  }
3366  }
3367  return $this->mImplicitGroups;
3368  }
3369 
3379  public function getFormerGroups() {
3380  $this->load();
3381 
3382  if ( is_null( $this->mFormerGroups ) ) {
3383  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3384  ? wfGetDB( DB_MASTER )
3385  : wfGetDB( DB_SLAVE );
3386  $res = $db->select( 'user_former_groups',
3387  [ 'ufg_group' ],
3388  [ 'ufg_user' => $this->mId ],
3389  __METHOD__ );
3390  $this->mFormerGroups = [];
3391  foreach ( $res as $row ) {
3392  $this->mFormerGroups[] = $row->ufg_group;
3393  }
3394  }
3395 
3396  return $this->mFormerGroups;
3397  }
3398 
3403  public function getEditCount() {
3404  if ( !$this->getId() ) {
3405  return null;
3406  }
3407 
3408  if ( $this->mEditCount === null ) {
3409  /* Populate the count, if it has not been populated yet */
3410  $dbr = wfGetDB( DB_SLAVE );
3411  // check if the user_editcount field has been initialized
3412  $count = $dbr->selectField(
3413  'user', 'user_editcount',
3414  [ 'user_id' => $this->mId ],
3415  __METHOD__
3416  );
3417 
3418  if ( $count === null ) {
3419  // it has not been initialized. do so.
3420  $count = $this->initEditCount();
3421  }
3422  $this->mEditCount = $count;
3423  }
3424  return (int)$this->mEditCount;
3425  }
3426 
3433  public function addGroup( $group ) {
3434  $this->load();
3435 
3436  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group ] ) ) {
3437  return false;
3438  }
3439 
3440  $dbw = wfGetDB( DB_MASTER );
3441  if ( $this->getId() ) {
3442  $dbw->insert( 'user_groups',
3443  [
3444  'ug_user' => $this->getId(),
3445  'ug_group' => $group,
3446  ],
3447  __METHOD__,
3448  [ 'IGNORE' ] );
3449  }
3450 
3451  $this->loadGroups();
3452  $this->mGroups[] = $group;
3453  // In case loadGroups was not called before, we now have the right twice.
3454  // Get rid of the duplicate.
3455  $this->mGroups = array_unique( $this->mGroups );
3456 
3457  // Refresh the groups caches, and clear the rights cache so it will be
3458  // refreshed on the next call to $this->getRights().
3459  $this->getEffectiveGroups( true );
3460  $this->mRights = null;
3461 
3462  $this->invalidateCache();
3463 
3464  return true;
3465  }
3466 
3473  public function removeGroup( $group ) {
3474  $this->load();
3475  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3476  return false;
3477  }
3478 
3479  $dbw = wfGetDB( DB_MASTER );
3480  $dbw->delete( 'user_groups',
3481  [
3482  'ug_user' => $this->getId(),
3483  'ug_group' => $group,
3484  ], __METHOD__
3485  );
3486  // Remember that the user was in this group
3487  $dbw->insert( 'user_former_groups',
3488  [
3489  'ufg_user' => $this->getId(),
3490  'ufg_group' => $group,
3491  ],
3492  __METHOD__,
3493  [ 'IGNORE' ]
3494  );
3495 
3496  $this->loadGroups();
3497  $this->mGroups = array_diff( $this->mGroups, [ $group ] );
3498 
3499  // Refresh the groups caches, and clear the rights cache so it will be
3500  // refreshed on the next call to $this->getRights().
3501  $this->getEffectiveGroups( true );
3502  $this->mRights = null;
3503 
3504  $this->invalidateCache();
3505 
3506  return true;
3507  }
3508 
3513  public function isLoggedIn() {
3514  return $this->getId() != 0;
3515  }
3516 
3521  public function isAnon() {
3522  return !$this->isLoggedIn();
3523  }
3524 
3529  public function isBot() {
3530  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3531  return true;
3532  }
3533 
3534  $isBot = false;
3535  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3536 
3537  return $isBot;
3538  }
3539 
3546  public function isAllowedAny() {
3547  $permissions = func_get_args();
3548  foreach ( $permissions as $permission ) {
3549  if ( $this->isAllowed( $permission ) ) {
3550  return true;
3551  }
3552  }
3553  return false;
3554  }
3555 
3561  public function isAllowedAll() {
3562  $permissions = func_get_args();
3563  foreach ( $permissions as $permission ) {
3564  if ( !$this->isAllowed( $permission ) ) {
3565  return false;
3566  }
3567  }
3568  return true;
3569  }
3570 
3576  public function isAllowed( $action = '' ) {
3577  if ( $action === '' ) {
3578  return true; // In the spirit of DWIM
3579  }
3580  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3581  // by misconfiguration: 0 == 'foo'
3582  return in_array( $action, $this->getRights(), true );
3583  }
3584 
3589  public function useRCPatrol() {
3591  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3592  }
3593 
3598  public function useNPPatrol() {
3600  return (
3601  ( $wgUseRCPatrol || $wgUseNPPatrol )
3602  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3603  );
3604  }
3605 
3610  public function useFilePatrol() {
3612  return (
3613  ( $wgUseRCPatrol || $wgUseFilePatrol )
3614  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3615  );
3616  }
3617 
3623  public function getRequest() {
3624  if ( $this->mRequest ) {
3625  return $this->mRequest;
3626  } else {
3628  return $wgRequest;
3629  }
3630  }
3631 
3640  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3641  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3642  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3643  }
3644  return false;
3645  }
3646 
3654  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3655  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3656  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3657  $this,
3658  [ $title->getSubjectPage(), $title->getTalkPage() ]
3659  );
3660  }
3661  $this->invalidateCache();
3662  }
3663 
3671  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3672  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3673  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3674  $store->removeWatch( $this, $title->getSubjectPage() );
3675  $store->removeWatch( $this, $title->getTalkPage() );
3676  }
3677  $this->invalidateCache();
3678  }
3679 
3688  public function clearNotification( &$title, $oldid = 0 ) {
3690 
3691  // Do nothing if the database is locked to writes
3692  if ( wfReadOnly() ) {
3693  return;
3694  }
3695 
3696  // Do nothing if not allowed to edit the watchlist
3697  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3698  return;
3699  }
3700 
3701  // If we're working on user's talk page, we should update the talk page message indicator
3702  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3703  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$this, $oldid ] ) ) {
3704  return;
3705  }
3706 
3707  // Try to update the DB post-send and only if needed...
3708  DeferredUpdates::addCallableUpdate( function() use ( $title, $oldid ) {
3709  if ( !$this->getNewtalk() ) {
3710  return; // no notifications to clear
3711  }
3712 
3713  // Delete the last notifications (they stack up)
3714  $this->setNewtalk( false );
3715 
3716  // If there is a new, unseen, revision, use its timestamp
3717  $nextid = $oldid
3718  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3719  : null;
3720  if ( $nextid ) {
3721  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3722  }
3723  } );
3724  }
3725 
3726  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3727  return;
3728  }
3729 
3730  if ( $this->isAnon() ) {
3731  // Nothing else to do...
3732  return;
3733  }
3734 
3735  // Only update the timestamp if the page is being watched.
3736  // The query to find out if it is watched is cached both in memcached and per-invocation,
3737  // and when it does have to be executed, it can be on a slave
3738  // If this is the user's newtalk page, we always update the timestamp
3739  $force = '';
3740  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3741  $force = 'force';
3742  }
3743 
3744  MediaWikiServices::getInstance()->getWatchedItemStore()
3745  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3746  }
3747 
3754  public function clearAllNotifications() {
3755  if ( wfReadOnly() ) {
3756  return;
3757  }
3758 
3759  // Do nothing if not allowed to edit the watchlist
3760  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3761  return;
3762  }
3763 
3765  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3766  $this->setNewtalk( false );
3767  return;
3768  }
3769  $id = $this->getId();
3770  if ( $id != 0 ) {
3771  $dbw = wfGetDB( DB_MASTER );
3772  $dbw->update( 'watchlist',
3773  [ /* SET */ 'wl_notificationtimestamp' => null ],
3774  [ /* WHERE */ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3775  __METHOD__
3776  );
3777  // We also need to clear here the "you have new message" notification for the own user_talk page;
3778  // it's cleared one page view later in WikiPage::doViewUpdates().
3779  }
3780  }
3781 
3798  protected function setCookie(
3799  $name, $value, $exp = 0, $secure = null, $params = [], $request = null
3800  ) {
3801  wfDeprecated( __METHOD__, '1.27' );
3802  if ( $request === null ) {
3803  $request = $this->getRequest();
3804  }
3805  $params['secure'] = $secure;
3806  $request->response()->setCookie( $name, $value, $exp, $params );
3807  }
3808 
3819  protected function clearCookie( $name, $secure = null, $params = [] ) {
3820  wfDeprecated( __METHOD__, '1.27' );
3821  $this->setCookie( $name, '', time() - 86400, $secure, $params );
3822  }
3823 
3839  protected function setExtendedLoginCookie( $name, $value, $secure ) {
3841 
3842  wfDeprecated( __METHOD__, '1.27' );
3843 
3844  $exp = time();
3845  $exp += $wgExtendedLoginCookieExpiration !== null
3846  ? $wgExtendedLoginCookieExpiration
3848 
3849  $this->setCookie( $name, $value, $exp, $secure );
3850  }
3851 
3860  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3861  $this->load();
3862  if ( 0 == $this->mId ) {
3863  return;
3864  }
3865 
3866  $session = $this->getRequest()->getSession();
3867  if ( $request && $session->getRequest() !== $request ) {
3868  $session = $session->sessionWithRequest( $request );
3869  }
3870  $delay = $session->delaySave();
3871 
3872  if ( !$session->getUser()->equals( $this ) ) {
3873  if ( !$session->canSetUser() ) {
3875  ->warning( __METHOD__ .
3876  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3877  );
3878  return;
3879  }
3880  $session->setUser( $this );
3881  }
3882 
3883  $session->setRememberUser( $rememberMe );
3884  if ( $secure !== null ) {
3885  $session->setForceHTTPS( $secure );
3886  }
3887 
3888  $session->persist();
3889 
3890  ScopedCallback::consume( $delay );
3891  }
3892 
3896  public function logout() {
3897  if ( Hooks::run( 'UserLogout', [ &$this ] ) ) {
3898  $this->doLogout();
3899  }
3900  }
3901 
3906  public function doLogout() {
3907  $session = $this->getRequest()->getSession();
3908  if ( !$session->canSetUser() ) {
3910  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3911  $error = 'immutable';
3912  } elseif ( !$session->getUser()->equals( $this ) ) {
3914  ->warning( __METHOD__ .
3915  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3916  );
3917  // But we still may as well make this user object anon
3918  $this->clearInstanceCache( 'defaults' );
3919  $error = 'wronguser';
3920  } else {
3921  $this->clearInstanceCache( 'defaults' );
3922  $delay = $session->delaySave();
3923  $session->unpersist(); // Clear cookies (T127436)
3924  $session->setLoggedOutTimestamp( time() );
3925  $session->setUser( new User );
3926  $session->set( 'wsUserID', 0 ); // Other code expects this
3927  $session->resetAllTokens();
3928  ScopedCallback::consume( $delay );
3929  $error = false;
3930  }
3931  \MediaWiki\Logger\LoggerFactory::getInstance( 'authmanager' )->info( 'Logout', [
3932  'event' => 'logout',
3933  'successful' => $error === false,
3934  'status' => $error ?: 'success',
3935  ] );
3936  }
3937 
3942  public function saveSettings() {
3943  if ( wfReadOnly() ) {
3944  // @TODO: caller should deal with this instead!
3945  // This should really just be an exception.
3947  null,
3948  "Could not update user with ID '{$this->mId}'; DB is read-only."
3949  ) );
3950  return;
3951  }
3952 
3953  $this->load();
3954  if ( 0 == $this->mId ) {
3955  return; // anon
3956  }
3957 
3958  // Get a new user_touched that is higher than the old one.
3959  // This will be used for a CAS check as a last-resort safety
3960  // check against race conditions and slave lag.
3961  $newTouched = $this->newTouchedTimestamp();
3962 
3963  $dbw = wfGetDB( DB_MASTER );
3964  $dbw->update( 'user',
3965  [ /* SET */
3966  'user_name' => $this->mName,
3967  'user_real_name' => $this->mRealName,
3968  'user_email' => $this->mEmail,
3969  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3970  'user_touched' => $dbw->timestamp( $newTouched ),
3971  'user_token' => strval( $this->mToken ),
3972  'user_email_token' => $this->mEmailToken,
3973  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3974  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
3975  'user_id' => $this->mId,
3976  ] ), __METHOD__
3977  );
3978 
3979  if ( !$dbw->affectedRows() ) {
3980  // Maybe the problem was a missed cache update; clear it to be safe
3981  $this->clearSharedCache( 'refresh' );
3982  // User was changed in the meantime or loaded with stale data
3983  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'slave';
3984  throw new MWException(
3985  "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
3986  " the version of the user to be saved is older than the current version."
3987  );
3988  }
3989 
3990  $this->mTouched = $newTouched;
3991  $this->saveOptions();
3992 
3993  Hooks::run( 'UserSaveSettings', [ $this ] );
3994  $this->clearSharedCache();
3995  $this->getUserPage()->invalidateCache();
3996  }
3997 
4004  public function idForName( $flags = 0 ) {
4005  $s = trim( $this->getName() );
4006  if ( $s === '' ) {
4007  return 0;
4008  }
4009 
4010  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4011  ? wfGetDB( DB_MASTER )
4012  : wfGetDB( DB_SLAVE );
4013 
4014  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4015  ? [ 'LOCK IN SHARE MODE' ]
4016  : [];
4017 
4018  $id = $db->selectField( 'user',
4019  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4020 
4021  return (int)$id;
4022  }
4023 
4039  public static function createNew( $name, $params = [] ) {
4040  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4041  if ( isset( $params[$field] ) ) {
4042  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4043  unset( $params[$field] );
4044  }
4045  }
4046 
4047  $user = new User;
4048  $user->load();
4049  $user->setToken(); // init token
4050  if ( isset( $params['options'] ) ) {
4051  $user->mOptions = $params['options'] + (array)$user->mOptions;
4052  unset( $params['options'] );
4053  }
4054  $dbw = wfGetDB( DB_MASTER );
4055  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
4056 
4057  $noPass = PasswordFactory::newInvalidPassword()->toString();
4058 
4059  $fields = [
4060  'user_id' => $seqVal,
4061  'user_name' => $name,
4062  'user_password' => $noPass,
4063  'user_newpassword' => $noPass,
4064  'user_email' => $user->mEmail,
4065  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4066  'user_real_name' => $user->mRealName,
4067  'user_token' => strval( $user->mToken ),
4068  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4069  'user_editcount' => 0,
4070  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4071  ];
4072  foreach ( $params as $name => $value ) {
4073  $fields["user_$name"] = $value;
4074  }
4075  $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
4076  if ( $dbw->affectedRows() ) {
4077  $newUser = User::newFromId( $dbw->insertId() );
4078  } else {
4079  $newUser = null;
4080  }
4081  return $newUser;
4082  }
4083 
4110  public function addToDatabase() {
4111  $this->load();
4112  if ( !$this->mToken ) {
4113  $this->setToken(); // init token
4114  }
4115 
4116  $this->mTouched = $this->newTouchedTimestamp();
4117 
4118  $noPass = PasswordFactory::newInvalidPassword()->toString();
4119 
4120  $dbw = wfGetDB( DB_MASTER );
4121  $inWrite = $dbw->writesOrCallbacksPending();
4122  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
4123  $dbw->insert( 'user',
4124  [
4125  'user_id' => $seqVal,
4126  'user_name' => $this->mName,
4127  'user_password' => $noPass,
4128  'user_newpassword' => $noPass,
4129  'user_email' => $this->mEmail,
4130  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4131  'user_real_name' => $this->mRealName,
4132  'user_token' => strval( $this->mToken ),
4133  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4134  'user_editcount' => 0,
4135  'user_touched' => $dbw->timestamp( $this->mTouched ),
4136  ], __METHOD__,
4137  [ 'IGNORE' ]
4138  );
4139  if ( !$dbw->affectedRows() ) {
4140  // The queries below cannot happen in the same REPEATABLE-READ snapshot.
4141  // Handle this by COMMIT, if possible, or by LOCK IN SHARE MODE otherwise.
4142  if ( $inWrite ) {
4143  // Can't commit due to pending writes that may need atomicity.
4144  // This may cause some lock contention unlike the case below.
4145  $options = [ 'LOCK IN SHARE MODE' ];
4146  $flags = self::READ_LOCKING;
4147  } else {
4148  // Often, this case happens early in views before any writes when
4149  // using CentralAuth. It's should be OK to commit and break the snapshot.
4150  $dbw->commit( __METHOD__, 'flush' );
4151  $options = [];
4152  $flags = self::READ_LATEST;
4153  }
4154  $this->mId = $dbw->selectField( 'user', 'user_id',
4155  [ 'user_name' => $this->mName ], __METHOD__, $options );
4156  $loaded = false;
4157  if ( $this->mId ) {
4158  if ( $this->loadFromDatabase( $flags ) ) {
4159  $loaded = true;
4160  }
4161  }
4162  if ( !$loaded ) {
4163  throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
4164  "to insert user '{$this->mName}' row, but it was not present in select!" );
4165  }
4166  return Status::newFatal( 'userexists' );
4167  }
4168  $this->mId = $dbw->insertId();
4169  self::$idCacheByName[$this->mName] = $this->mId;
4170 
4171  // Clear instance cache other than user table data, which is already accurate
4172  $this->clearInstanceCache();
4173 
4174  $this->saveOptions();
4175  return Status::newGood();
4176  }
4177 
4183  public function spreadAnyEditBlock() {
4184  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4185  return $this->spreadBlock();
4186  }
4187 
4188  return false;
4189  }
4190 
4196  protected function spreadBlock() {
4197  wfDebug( __METHOD__ . "()\n" );
4198  $this->load();
4199  if ( $this->mId == 0 ) {
4200  return false;
4201  }
4202 
4203  $userblock = Block::newFromTarget( $this->getName() );
4204  if ( !$userblock ) {
4205  return false;
4206  }
4207 
4208  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4209  }
4210 
4215  public function isBlockedFromCreateAccount() {
4216  $this->getBlockedStatus();
4217  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4218  return $this->mBlock;
4219  }
4220 
4221  # bug 13611: if the IP address the user is trying to create an account from is
4222  # blocked with createaccount disabled, prevent new account creation there even
4223  # when the user is logged in
4224  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4225  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4226  }
4227  return $this->mBlockedFromCreateAccount instanceof Block
4228  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4229  ? $this->mBlockedFromCreateAccount
4230  : false;
4231  }
4232 
4237  public function isBlockedFromEmailuser() {
4238  $this->getBlockedStatus();
4239  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4240  }
4241 
4246  public function isAllowedToCreateAccount() {
4247  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4248  }
4249 
4255  public function getUserPage() {
4256  return Title::makeTitle( NS_USER, $this->getName() );
4257  }
4258 
4264  public function getTalkPage() {
4265  $title = $this->getUserPage();
4266  return $title->getTalkPage();
4267  }
4268 
4274  public function isNewbie() {
4275  return !$this->isAllowed( 'autoconfirmed' );
4276  }
4277 
4284  public function checkPassword( $password ) {
4286 
4287  if ( $wgDisableAuthManager ) {
4288  $this->load();
4289 
4290  // Some passwords will give a fatal Status, which means there is
4291  // some sort of technical or security reason for this password to
4292  // be completely invalid and should never be checked (e.g., T64685)
4293  if ( !$this->checkPasswordValidity( $password )->isOK() ) {
4294  return false;
4295  }
4296 
4297  // Certain authentication plugins do NOT want to save
4298  // domain passwords in a mysql database, so we should
4299  // check this (in case $wgAuth->strict() is false).
4300  if ( $wgAuth->authenticate( $this->getName(), $password ) ) {
4301  return true;
4302  } elseif ( $wgAuth->strict() ) {
4303  // Auth plugin doesn't allow local authentication
4304  return false;
4305  } elseif ( $wgAuth->strictUserAuth( $this->getName() ) ) {
4306  // Auth plugin doesn't allow local authentication for this user name
4307  return false;
4308  }
4309 
4310  $passwordFactory = new PasswordFactory();
4311  $passwordFactory->init( RequestContext::getMain()->getConfig() );
4312  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
4313  ? wfGetDB( DB_MASTER )
4314  : wfGetDB( DB_SLAVE );
4315 
4316  try {
4317  $mPassword = $passwordFactory->newFromCiphertext( $db->selectField(
4318  'user', 'user_password', [ 'user_id' => $this->getId() ], __METHOD__
4319  ) );
4320  } catch ( PasswordError $e ) {
4321  wfDebug( 'Invalid password hash found in database.' );
4322  $mPassword = PasswordFactory::newInvalidPassword();
4323  }
4324 
4325  if ( !$mPassword->equals( $password ) ) {
4326  if ( $wgLegacyEncoding ) {
4327  // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
4328  // Check for this with iconv
4329  $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password );
4330  if ( $cp1252Password === $password || !$mPassword->equals( $cp1252Password ) ) {
4331  return false;
4332  }
4333  } else {
4334  return false;
4335  }
4336  }
4337 
4338  if ( $passwordFactory->needsUpdate( $mPassword ) && !wfReadOnly() ) {
4339  $this->setPasswordInternal( $password );
4340  }
4341 
4342  return true;
4343  } else {
4344  $manager = AuthManager::singleton();
4345  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4346  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4347  [
4348  'username' => $this->getName(),
4349  'password' => $password,
4350  ]
4351  );
4352  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4353  switch ( $res->status ) {
4354  case AuthenticationResponse::PASS:
4355  return true;
4356  case AuthenticationResponse::FAIL:
4357  // Hope it's not a PreAuthenticationProvider that failed...
4359  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4360  return false;
4361  default:
4362  throw new BadMethodCallException(
4363  'AuthManager returned a response unsupported by ' . __METHOD__
4364  );
4365  }
4366  }
4367  }
4368 
4377  public function checkTemporaryPassword( $plaintext ) {
4379 
4380  if ( $wgDisableAuthManager ) {
4381  $this->load();
4382 
4383  $passwordFactory = new PasswordFactory();
4384  $passwordFactory->init( RequestContext::getMain()->getConfig() );
4385  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
4386  ? wfGetDB( DB_MASTER )
4387  : wfGetDB( DB_SLAVE );
4388 
4389  $row = $db->selectRow(
4390  'user',
4391  [ 'user_newpassword', 'user_newpass_time' ],
4392  [ 'user_id' => $this->getId() ],
4393  __METHOD__
4394  );
4395  try {
4396  $newPassword = $passwordFactory->newFromCiphertext( $row->user_newpassword );
4397  } catch ( PasswordError $e ) {
4398  wfDebug( 'Invalid password hash found in database.' );
4399  $newPassword = PasswordFactory::newInvalidPassword();
4400  }
4401 
4402  if ( $newPassword->equals( $plaintext ) ) {
4403  if ( is_null( $row->user_newpass_time ) ) {
4404  return true;
4405  }
4406  $expiry = wfTimestamp( TS_UNIX, $row->user_newpass_time ) + $wgNewPasswordExpiry;
4407  return ( time() < $expiry );
4408  } else {
4409  return false;
4410  }
4411  } else {
4412  // Can't check the temporary password individually.
4413  return $this->checkPassword( $plaintext );
4414  }
4415  }
4416 
4428  public function getEditTokenObject( $salt = '', $request = null ) {
4429  if ( $this->isAnon() ) {
4430  return new LoggedOutEditToken();
4431  }
4432 
4433  if ( !$request ) {
4434  $request = $this->getRequest();
4435  }
4436  return $request->getSession()->getToken( $salt );
4437  }
4438 
4450  public function getEditToken( $salt = '', $request = null ) {
4451  return $this->getEditTokenObject( $salt, $request )->toString();
4452  }
4453 
4460  public static function getEditTokenTimestamp( $val ) {
4461  wfDeprecated( __METHOD__, '1.27' );
4463  }
4464 
4477  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4478  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4479  }
4480 
4491  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4492  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4493  return $this->matchEditToken( $val, $salt, $request, $maxage );
4494  }
4495 
4503  public function sendConfirmationMail( $type = 'created' ) {
4504  global $wgLang;
4505  $expiration = null; // gets passed-by-ref and defined in next line.
4506  $token = $this->confirmationToken( $expiration );
4507  $url = $this->confirmationTokenUrl( $token );
4508  $invalidateURL = $this->invalidationTokenUrl( $token );
4509  $this->saveSettings();
4510 
4511  if ( $type == 'created' || $type === false ) {
4512  $message = 'confirmemail_body';
4513  } elseif ( $type === true ) {
4514  $message = 'confirmemail_body_changed';
4515  } else {
4516  // Messages: confirmemail_body_changed, confirmemail_body_set
4517  $message = 'confirmemail_body_' . $type;
4518  }
4519 
4520  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4521  wfMessage( $message,
4522  $this->getRequest()->getIP(),
4523  $this->getName(),
4524  $url,
4525  $wgLang->userTimeAndDate( $expiration, $this ),
4526  $invalidateURL,
4527  $wgLang->userDate( $expiration, $this ),
4528  $wgLang->userTime( $expiration, $this ) )->text() );
4529  }
4530 
4542  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4544 
4545  if ( $from instanceof User ) {
4546  $sender = MailAddress::newFromUser( $from );
4547  } else {
4548  $sender = new MailAddress( $wgPasswordSender,
4549  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4550  }
4551  $to = MailAddress::newFromUser( $this );
4552 
4553  return UserMailer::send( $to, $sender, $subject, $body, [
4554  'replyTo' => $replyto,
4555  ] );
4556  }
4557 
4568  protected function confirmationToken( &$expiration ) {
4570  $now = time();
4571  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4572  $expiration = wfTimestamp( TS_MW, $expires );
4573  $this->load();
4574  $token = MWCryptRand::generateHex( 32 );
4575  $hash = md5( $token );
4576  $this->mEmailToken = $hash;
4577  $this->mEmailTokenExpires = $expiration;
4578  return $token;
4579  }
4580 
4586  protected function confirmationTokenUrl( $token ) {
4587  return $this->getTokenUrl( 'ConfirmEmail', $token );
4588  }
4589 
4595  protected function invalidationTokenUrl( $token ) {
4596  return $this->getTokenUrl( 'InvalidateEmail', $token );
4597  }
4598 
4613  protected function getTokenUrl( $page, $token ) {
4614  // Hack to bypass localization of 'Special:'
4615  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4616  return $title->getCanonicalURL();
4617  }
4618 
4626  public function confirmEmail() {
4627  // Check if it's already confirmed, so we don't touch the database
4628  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4629  if ( !$this->isEmailConfirmed() ) {
4631  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4632  }
4633  return true;
4634  }
4635 
4643  public function invalidateEmail() {
4644  $this->load();
4645  $this->mEmailToken = null;
4646  $this->mEmailTokenExpires = null;
4647  $this->setEmailAuthenticationTimestamp( null );
4648  $this->mEmail = '';
4649  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4650  return true;
4651  }
4652 
4658  $this->load();
4659  $this->mEmailAuthenticated = $timestamp;
4660  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4661  }
4662 
4668  public function canSendEmail() {
4670  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4671  return false;
4672  }
4673  $canSend = $this->isEmailConfirmed();
4674  Hooks::run( 'UserCanSendEmail', [ &$this, &$canSend ] );
4675  return $canSend;
4676  }
4677 
4683  public function canReceiveEmail() {
4684  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4685  }
4686 
4697  public function isEmailConfirmed() {
4699  $this->load();
4700  $confirmed = true;
4701  if ( Hooks::run( 'EmailConfirmed', [ &$this, &$confirmed ] ) ) {
4702  if ( $this->isAnon() ) {
4703  return false;
4704  }
4705  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4706  return false;
4707  }
4708  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4709  return false;
4710  }
4711  return true;
4712  } else {
4713  return $confirmed;
4714  }
4715  }
4716 
4721  public function isEmailConfirmationPending() {
4723  return $wgEmailAuthentication &&
4724  !$this->isEmailConfirmed() &&
4725  $this->mEmailToken &&
4726  $this->mEmailTokenExpires > wfTimestamp();
4727  }
4728 
4736  public function getRegistration() {
4737  if ( $this->isAnon() ) {
4738  return false;
4739  }
4740  $this->load();
4741  return $this->mRegistration;
4742  }
4743 
4750  public function getFirstEditTimestamp() {
4751  if ( $this->getId() == 0 ) {
4752  return false; // anons
4753  }
4754  $dbr = wfGetDB( DB_SLAVE );
4755  $time = $dbr->selectField( 'revision', 'rev_timestamp',
4756  [ 'rev_user' => $this->getId() ],
4757  __METHOD__,
4758  [ 'ORDER BY' => 'rev_timestamp ASC' ]
4759  );
4760  if ( !$time ) {
4761  return false; // no edits
4762  }
4763  return wfTimestamp( TS_MW, $time );
4764  }
4765 
4772  public static function getGroupPermissions( $groups ) {
4774  $rights = [];
4775  // grant every granted permission first
4776  foreach ( $groups as $group ) {
4777  if ( isset( $wgGroupPermissions[$group] ) ) {
4778  $rights = array_merge( $rights,
4779  // array_filter removes empty items
4780  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4781  }
4782  }
4783  // now revoke the revoked permissions
4784  foreach ( $groups as $group ) {
4785  if ( isset( $wgRevokePermissions[$group] ) ) {
4786  $rights = array_diff( $rights,
4787  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4788  }
4789  }
4790  return array_unique( $rights );
4791  }
4792 
4799  public static function getGroupsWithPermission( $role ) {
4801  $allowedGroups = [];
4802  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4803  if ( self::groupHasPermission( $group, $role ) ) {
4804  $allowedGroups[] = $group;
4805  }
4806  }
4807  return $allowedGroups;
4808  }
4809 
4822  public static function groupHasPermission( $group, $role ) {
4824  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
4825  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
4826  }
4827 
4842  public static function isEveryoneAllowed( $right ) {
4844  static $cache = [];
4845 
4846  // Use the cached results, except in unit tests which rely on
4847  // being able change the permission mid-request
4848  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
4849  return $cache[$right];
4850  }
4851 
4852  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
4853  $cache[$right] = false;
4854  return false;
4855  }
4856 
4857  // If it's revoked anywhere, then everyone doesn't have it
4858  foreach ( $wgRevokePermissions as $rights ) {
4859  if ( isset( $rights[$right] ) && $rights[$right] ) {
4860  $cache[$right] = false;
4861  return false;
4862  }
4863  }
4864 
4865  // Remove any rights that aren't allowed to the global-session user,
4866  // unless there are no sessions for this endpoint.
4867  if ( !defined( 'MW_NO_SESSION' ) ) {
4868  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
4869  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
4870  $cache[$right] = false;
4871  return false;
4872  }
4873  }
4874 
4875  // Allow extensions to say false
4876  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
4877  $cache[$right] = false;
4878  return false;
4879  }
4880 
4881  $cache[$right] = true;
4882  return true;
4883  }
4884 
4891  public static function getGroupName( $group ) {
4892  $msg = wfMessage( "group-$group" );
4893  return $msg->isBlank() ? $group : $msg->text();
4894  }
4895 
4903  public static function getGroupMember( $group, $username = '#' ) {
4904  $msg = wfMessage( "group-$group-member", $username );
4905  return $msg->isBlank() ? $group : $msg->text();
4906  }
4907 
4914  public static function getAllGroups() {
4916  return array_diff(
4917  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4918  self::getImplicitGroups()
4919  );
4920  }
4921 
4926  public static function getAllRights() {
4927  if ( self::$mAllRights === false ) {
4929  if ( count( $wgAvailableRights ) ) {
4930  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
4931  } else {
4932  self::$mAllRights = self::$mCoreRights;
4933  }
4934  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
4935  }
4936  return self::$mAllRights;
4937  }
4938 
4943  public static function getImplicitGroups() {
4945 
4946  $groups = $wgImplicitGroups;
4947  # Deprecated, use $wgImplicitGroups instead
4948  Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
4949 
4950  return $groups;
4951  }
4952 
4959  public static function getGroupPage( $group ) {
4960  $msg = wfMessage( 'grouppage-' . $group )->inContentLanguage();
4961  if ( $msg->exists() ) {
4962  $title = Title::newFromText( $msg->text() );
4963  if ( is_object( $title ) ) {
4964  return $title;
4965  }
4966  }
4967  return false;
4968  }
4969 
4978  public static function makeGroupLinkHTML( $group, $text = '' ) {
4979  if ( $text == '' ) {
4980  $text = self::getGroupName( $group );
4981  }
4982  $title = self::getGroupPage( $group );
4983  if ( $title ) {
4984  return Linker::link( $title, htmlspecialchars( $text ) );
4985  } else {
4986  return htmlspecialchars( $text );
4987  }
4988  }
4989 
4998  public static function makeGroupLinkWiki( $group, $text = '' ) {
4999  if ( $text == '' ) {
5000  $text = self::getGroupName( $group );
5001  }
5002  $title = self::getGroupPage( $group );
5003  if ( $title ) {
5004  $page = $title->getFullText();
5005  return "[[$page|$text]]";
5006  } else {
5007  return $text;
5008  }
5009  }
5010 
5020  public static function changeableByGroup( $group ) {
5022 
5023  $groups = [
5024  'add' => [],
5025  'remove' => [],
5026  'add-self' => [],
5027  'remove-self' => []
5028  ];
5029 
5030  if ( empty( $wgAddGroups[$group] ) ) {
5031  // Don't add anything to $groups
5032  } elseif ( $wgAddGroups[$group] === true ) {
5033  // You get everything
5034  $groups['add'] = self::getAllGroups();
5035  } elseif ( is_array( $wgAddGroups[$group] ) ) {
5036  $groups['add'] = $wgAddGroups[$group];
5037  }
5038 
5039  // Same thing for remove
5040  if ( empty( $wgRemoveGroups[$group] ) ) {
5041  // Do nothing
5042  } elseif ( $wgRemoveGroups[$group] === true ) {
5043  $groups['remove'] = self::getAllGroups();
5044  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5045  $groups['remove'] = $wgRemoveGroups[$group];
5046  }
5047 
5048  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5049  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5050  foreach ( $wgGroupsAddToSelf as $key => $value ) {
5051  if ( is_int( $key ) ) {
5052  $wgGroupsAddToSelf['user'][] = $value;
5053  }
5054  }
5055  }
5056 
5057  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5058  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5059  if ( is_int( $key ) ) {
5060  $wgGroupsRemoveFromSelf['user'][] = $value;
5061  }
5062  }
5063  }
5064 
5065  // Now figure out what groups the user can add to him/herself
5066  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5067  // Do nothing
5068  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5069  // No idea WHY this would be used, but it's there
5070  $groups['add-self'] = User::getAllGroups();
5071  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5072  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5073  }
5074 
5075  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5076  // Do nothing
5077  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5078  $groups['remove-self'] = User::getAllGroups();
5079  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5080  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5081  }
5082 
5083  return $groups;
5084  }
5085 
5093  public function changeableGroups() {
5094  if ( $this->isAllowed( 'userrights' ) ) {
5095  // This group gives the right to modify everything (reverse-
5096  // compatibility with old "userrights lets you change
5097  // everything")
5098  // Using array_merge to make the groups reindexed
5099  $all = array_merge( User::getAllGroups() );
5100  return [
5101  'add' => $all,
5102  'remove' => $all,
5103  'add-self' => [],
5104  'remove-self' => []
5105  ];
5106  }
5107 
5108  // Okay, it's not so simple, we will have to go through the arrays
5109  $groups = [
5110  'add' => [],
5111  'remove' => [],
5112  'add-self' => [],
5113  'remove-self' => []
5114  ];
5115  $addergroups = $this->getEffectiveGroups();
5116 
5117  foreach ( $addergroups as $addergroup ) {
5118  $groups = array_merge_recursive(
5119  $groups, $this->changeableByGroup( $addergroup )
5120  );
5121  $groups['add'] = array_unique( $groups['add'] );
5122  $groups['remove'] = array_unique( $groups['remove'] );
5123  $groups['add-self'] = array_unique( $groups['add-self'] );
5124  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5125  }
5126  return $groups;
5127  }
5128 
5132  public function incEditCount() {
5133  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() {
5134  $this->incEditCountImmediate();
5135  } );
5136  }
5137 
5143  public function incEditCountImmediate() {
5144  if ( $this->isAnon() ) {
5145  return;
5146  }
5147 
5148  $dbw = wfGetDB( DB_MASTER );
5149  // No rows will be "affected" if user_editcount is NULL
5150  $dbw->update(
5151  'user',
5152  [ 'user_editcount=user_editcount+1' ],
5153  [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
5154  __METHOD__
5155  );
5156  // Lazy initialization check...
5157  if ( $dbw->affectedRows() == 0 ) {
5158  // Now here's a goddamn hack...
5159  $dbr = wfGetDB( DB_SLAVE );
5160  if ( $dbr !== $dbw ) {
5161  // If we actually have a slave server, the count is
5162  // at least one behind because the current transaction
5163  // has not been committed and replicated.
5164  $this->initEditCount( 1 );
5165  } else {
5166  // But if DB_SLAVE is selecting the master, then the
5167  // count we just read includes the revision that was
5168  // just added in the working transaction.
5169  $this->initEditCount();
5170  }
5171  }
5172  // Edit count in user cache too
5173  $this->invalidateCache();
5174  }
5175 
5182  protected function initEditCount( $add = 0 ) {
5183  // Pull from a slave to be less cruel to servers
5184  // Accuracy isn't the point anyway here
5185  $dbr = wfGetDB( DB_SLAVE );
5186  $count = (int)$dbr->selectField(
5187  'revision',
5188  'COUNT(rev_user)',
5189  [ 'rev_user' => $this->getId() ],
5190  __METHOD__
5191  );
5192  $count = $count + $add;
5193 
5194  $dbw = wfGetDB( DB_MASTER );
5195  $dbw->update(
5196  'user',
5197  [ 'user_editcount' => $count ],
5198  [ 'user_id' => $this->getId() ],
5199  __METHOD__
5200  );
5201 
5202  return $count;
5203  }
5204 
5211  public static function getRightDescription( $right ) {
5212  $key = "right-$right";
5213  $msg = wfMessage( $key );
5214  return $msg->isBlank() ? $right : $msg->text();
5215  }
5216 
5226  public static function crypt( $password, $salt = false ) {
5227  wfDeprecated( __METHOD__, '1.24' );
5228  $passwordFactory = new PasswordFactory();
5229  $passwordFactory->init( RequestContext::getMain()->getConfig() );
5230  $hash = $passwordFactory->newFromPlaintext( $password );
5231  return $hash->toString();
5232  }
5233 
5245  public static function comparePasswords( $hash, $password, $userId = false ) {
5246  wfDeprecated( __METHOD__, '1.24' );
5247 
5248  // Check for *really* old password hashes that don't even have a type
5249  // The old hash format was just an md5 hex hash, with no type information
5250  if ( preg_match( '/^[0-9a-f]{32}$/', $hash ) ) {
5252  if ( $wgPasswordSalt ) {
5253  $password = ":B:{$userId}:{$hash}";
5254  } else {
5255  $password = ":A:{$hash}";
5256  }
5257  }
5258 
5259  $passwordFactory = new PasswordFactory();
5260  $passwordFactory->init( RequestContext::getMain()->getConfig() );
5261  $hash = $passwordFactory->newFromCiphertext( $hash );
5262  return $hash->equals( $password );
5263  }
5264 
5286  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5288  if ( !$wgDisableAuthManager || empty( $wgNewUserLog ) ) {
5289  return true; // disabled
5290  }
5291 
5292  if ( $action === true ) {
5293  $action = 'byemail';
5294  } elseif ( $action === false ) {
5295  if ( $this->equals( $wgUser ) ) {
5296  $action = 'create';
5297  } else {
5298  $action = 'create2';
5299  }
5300  }
5301 
5302  if ( $action === 'create' || $action === 'autocreate' ) {
5303  $performer = $this;
5304  } else {
5305  $performer = $wgUser;
5306  }
5307 
5308  $logEntry = new ManualLogEntry( 'newusers', $action );
5309  $logEntry->setPerformer( $performer );
5310  $logEntry->setTarget( $this->getUserPage() );
5311  $logEntry->setComment( $reason );
5312  $logEntry->setParameters( [
5313  '4::userid' => $this->getId(),
5314  ] );
5315  $logid = $logEntry->insert();
5316 
5317  if ( $action !== 'autocreate' ) {
5318  $logEntry->publish( $logid );
5319  }
5320 
5321  return (int)$logid;
5322  }
5323 
5332  public function addNewUserLogEntryAutoCreate() {
5333  $this->addNewUserLogEntry( 'autocreate' );
5334 
5335  return true;
5336  }
5337 
5343  protected function loadOptions( $data = null ) {
5345 
5346  $this->load();
5347 
5348  if ( $this->mOptionsLoaded ) {
5349  return;
5350  }
5351 
5352  $this->mOptions = self::getDefaultOptions();
5353 
5354  if ( !$this->getId() ) {
5355  // For unlogged-in users, load language/variant options from request.
5356  // There's no need to do it for logged-in users: they can set preferences,
5357  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5358  // so don't override user's choice (especially when the user chooses site default).
5359  $variant = $wgContLang->getDefaultVariant();
5360  $this->mOptions['variant'] = $variant;
5361  $this->mOptions['language'] = $variant;
5362  $this->mOptionsLoaded = true;
5363  return;
5364  }
5365 
5366  // Maybe load from the object
5367  if ( !is_null( $this->mOptionOverrides ) ) {
5368  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5369  foreach ( $this->mOptionOverrides as $key => $value ) {
5370  $this->mOptions[$key] = $value;
5371  }
5372  } else {
5373  if ( !is_array( $data ) ) {
5374  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5375  // Load from database
5376  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5377  ? wfGetDB( DB_MASTER )
5378  : wfGetDB( DB_SLAVE );
5379 
5380  $res = $dbr->select(
5381  'user_properties',
5382  [ 'up_property', 'up_value' ],
5383  [ 'up_user' => $this->getId() ],
5384  __METHOD__
5385  );
5386 
5387  $this->mOptionOverrides = [];
5388  $data = [];
5389  foreach ( $res as $row ) {
5390  $data[$row->up_property] = $row->up_value;
5391  }
5392  }
5393  foreach ( $data as $property => $value ) {
5394  $this->mOptionOverrides[$property] = $value;
5395  $this->mOptions[$property] = $value;
5396  }
5397  }
5398 
5399  $this->mOptionsLoaded = true;
5400 
5401  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5402  }
5403 
5409  protected function saveOptions() {
5410  $this->loadOptions();
5411 
5412  // Not using getOptions(), to keep hidden preferences in database
5413  $saveOptions = $this->mOptions;
5414 
5415  // Allow hooks to abort, for instance to save to a global profile.
5416  // Reset options to default state before saving.
5417  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5418  return;
5419  }
5420 
5421  $userId = $this->getId();
5422 
5423  $insert_rows = []; // all the new preference rows
5424  foreach ( $saveOptions as $key => $value ) {
5425  // Don't bother storing default values
5426  $defaultOption = self::getDefaultOption( $key );
5427  if ( ( $defaultOption === null && $value !== false && $value !== null )
5428  || $value != $defaultOption
5429  ) {
5430  $insert_rows[] = [
5431  'up_user' => $userId,
5432  'up_property' => $key,
5433  'up_value' => $value,
5434  ];
5435  }
5436  }
5437 
5438  $dbw = wfGetDB( DB_MASTER );
5439 
5440  $res = $dbw->select( 'user_properties',
5441  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5442 
5443  // Find prior rows that need to be removed or updated. These rows will
5444  // all be deleted (the later so that INSERT IGNORE applies the new values).
5445  $keysDelete = [];
5446  foreach ( $res as $row ) {
5447  if ( !isset( $saveOptions[$row->up_property] )
5448  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5449  ) {
5450  $keysDelete[] = $row->up_property;
5451  }
5452  }
5453 
5454  if ( count( $keysDelete ) ) {
5455  // Do the DELETE by PRIMARY KEY for prior rows.
5456  // In the past a very large portion of calls to this function are for setting
5457  // 'rememberpassword' for new accounts (a preference that has since been removed).
5458  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5459  // caused gap locks on [max user ID,+infinity) which caused high contention since
5460  // updates would pile up on each other as they are for higher (newer) user IDs.
5461  // It might not be necessary these days, but it shouldn't hurt either.
5462  $dbw->delete( 'user_properties',
5463  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5464  }
5465  // Insert the new preference rows
5466  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5467  }
5468 
5475  public static function getPasswordFactory() {
5476  wfDeprecated( __METHOD__, '1.27' );
5477  $ret = new PasswordFactory();
5478  $ret->init( RequestContext::getMain()->getConfig() );
5479  return $ret;
5480  }
5481 
5506  public static function passwordChangeInputAttribs() {
5508 
5509  if ( $wgMinimalPasswordLength == 0 ) {
5510  return [];
5511  }
5512 
5513  # Note that the pattern requirement will always be satisfied if the
5514  # input is empty, so we need required in all cases.
5515 
5516  # @todo FIXME: Bug 23769: This needs to not claim the password is required
5517  # if e-mail confirmation is being used. Since HTML5 input validation
5518  # is b0rked anyway in some browsers, just return nothing. When it's
5519  # re-enabled, fix this code to not output required for e-mail
5520  # registration.
5521  # $ret = array( 'required' );
5522  $ret = [];
5523 
5524  # We can't actually do this right now, because Opera 9.6 will print out
5525  # the entered password visibly in its error message! When other
5526  # browsers add support for this attribute, or Opera fixes its support,
5527  # we can add support with a version check to avoid doing this on Opera
5528  # versions where it will be a problem. Reported to Opera as
5529  # DSK-262266, but they don't have a public bug tracker for us to follow.
5530  /*
5531  if ( $wgMinimalPasswordLength > 1 ) {
5532  $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}';
5533  $ret['title'] = wfMessage( 'passwordtooshort' )
5534  ->numParams( $wgMinimalPasswordLength )->text();
5535  }
5536  */
5537 
5538  return $ret;
5539  }
5540 
5546  public static function selectFields() {
5547  return [
5548  'user_id',
5549  'user_name',
5550  'user_real_name',
5551  'user_email',
5552  'user_touched',
5553  'user_token',
5554  'user_email_authenticated',
5555  'user_email_token',
5556  'user_email_token_expires',
5557  'user_registration',
5558  'user_editcount',
5559  ];
5560  }
5561 
5569  static function newFatalPermissionDeniedStatus( $permission ) {
5570  global $wgLang;
5571 
5572  $groups = array_map(
5573  [ 'User', 'makeGroupLinkWiki' ],
5574  User::getGroupsWithPermission( $permission )
5575  );
5576 
5577  if ( $groups ) {
5578  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5579  } else {
5580  return Status::newFatal( 'badaccess-group0' );
5581  }
5582  }
5583 
5593  public function getInstanceForUpdate() {
5594  if ( !$this->getId() ) {
5595  return null; // anon
5596  }
5597 
5598  $user = self::newFromId( $this->getId() );
5599  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5600  return null;
5601  }
5602 
5603  return $user;
5604  }
5605 
5613  public function equals( User $user ) {
5614  return $this->getName() === $user->getName();
5615  }
5616 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:522
addAutopromoteOnceGroups($event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1430
getEmail()
Get the user's e-mail address.
Definition: User.php:2796
static randomPassword()
Return a random password.
Definition: User.php:1153
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
isHidden()
Check if user account is hidden.
Definition: User.php:2097
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Factory class for creating and checking Password objects.
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:744
string $mBlockedby
Definition: User.php:264
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:1390
$wgNewUserLog
Maintain a log of newusers at Log/newusers?
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:2762
static newFromRow($row, $data=null)
Create a new user object from a user row.
Definition: User.php:609
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:2974
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2221
isAllowedAll()
Definition: User.php:3561
string $mDatePreference
Definition: User.php:262
the array() calling protocol came about after MediaWiki 1.4rc1.
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4274
static whoIsReal($id)
Get the real name of a user given their user ID.
Definition: User.php:754
wfCanIPUseHTTPS($ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS...
matchEditTokenNoSuffix($val, $salt= '', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4491
$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:4460
isBlockedFrom($title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition: User.php:1987
$context
Definition: load.php:43
checkPassword($password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4284
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1177
$wgMaxNameChars
Maximum number of bytes in username.
clearInstanceCache($reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1536
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
loadFromSession()
Load user data from the session.
Definition: User.php:1225
const NS_MAIN
Definition: Defines.php:69
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4246
$success
Block $mBlockedFromCreateAccount
Definition: User.php:298
isValidPassword($password)
Is the input a valid password for this user?
Definition: User.php:994
logout()
Log this user out.
Definition: User.php:3896
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3688
isDnsBlacklisted($ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1706
static getImplicitGroups()
Get a list of implicit groups.
Definition: User.php:4943
saveSettings()
Save this user's settings into the database.
Definition: User.php:3942
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
static isLocallyBlockedProxy($ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1774
load($flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:357
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4750
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:3546
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4237
static getCanonicalName($name, $validate= 'valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1082
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:1980
setCookie($name, $value, $exp=0, $secure=null, $params=[], $request=null)
Set a cookie on the user's client.
Definition: User.php:3798
getAutomaticGroups($recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3350
pingLimiter($action= 'edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1829
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:2384
loadFromUserObject($user)
Load the data for this user object from another user object.
Definition: User.php:1388
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2424
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:37
deleteNewtalk($field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2311
updateNewtalk($field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2286
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:2039
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2008
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:215
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
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:322
static isUsableName($name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:889
static $idCacheByName
Definition: User.php:303
null for the local wiki Added in
Definition: hooks.txt:1435
getRealName()
Get the user's real name.
Definition: User.php:2888
$wgSecureLogin
This is to let user authenticate using https when they come from http.
checkPasswordValidity($password, $purpose= 'login')
Check if this is a valid password for this user.
Definition: User.php:1042
static findUsersByGroup($groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:924
$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:1498
static newFromId($id)
Static factory method for creation from a given user ID.
Definition: User.php:545
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5093
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3754
Represents an invalid password hash.
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3513
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:2588
This is a value object to hold authentication response data.
getTemporaryPassword()
Definition: User.php:2492
checkNewtalk($field, $id)
Internal uncached check for new messages.
Definition: User.php:2271
setPassword($str)
Set the password and reset the random token.
Definition: User.php:2512
$wgGroupsRemoveFromSelf
static createNew($name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4039
invalidationTokenUrl($token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4595
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
addNewUserLogEntry($action=false, $reason= '')
Add a newuser log entry for this user.
Definition: User.php:5286
$wgAuth $wgAuth
Authentication plugin.
$wgHiddenPrefs
An array of preferences to not show for the user.
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:804
setName($str)
Set the user name.
Definition: User.php:2166
array $mFormerGroups
Definition: User.php:276
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2472
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:256
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
$wgRevokePermissions
Permission keys revoked from users in each group.
static getPreferences($user, IContextSource $context)
Definition: Preferences.php:83
getOptions($flags=0)
Get all user's options.
Definition: User.php:2943
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:4477
setToken($token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2707
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.
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
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:4978
static makeGroupLinkWiki($group, $text= '')
Create a link to the group in Wikitext, if available; else return the group name. ...
Definition: User.php:4998
setOption($oname, $val)
Set the given option for a user.
Definition: User.php:3002
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2139
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1629
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5546
string $mName
Cache variables.
Definition: User.php:206
string $mRegistration
Cache variables.
Definition: User.php:225
$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:2833
$wgDnsBlacklistUrls
List of DNS blacklists to use, if $wgEnableDnsBlacklist is true.
int $mEditCount
Cache variables.
Definition: User.php:227
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:2636
static isEveryoneAllowed($right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4842
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':DEPRECATED!Use HtmlPageLinkRendererBegin instead.Used when generating internal and interwiki links in Linker::link(), before processing starts.Return false to skip default processing and return $ret.See documentation for Linker::link() for details on the expected meanings of parameters.$skin:the Skin object $target:the Title that the link is pointing to &$html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1814
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2175
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4914
resetTokenFromOption($oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3051
loadOptions($data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5343
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3085
$wgGroupPermissions
Permission keys given to users in each group.
getIntOption($oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2986
sendMail($subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4542
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
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
string $mEmailTokenExpires
Cache variables.
Definition: User.php:223
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4926
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4697
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:450
static isValidUserName($name)
Is the input a valid username?
Definition: User.php:840
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:1816
static edits($uid)
Count the number of edits of a user.
Definition: User.php:1141
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4215
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:2376
setNewpassword($str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:2727
timestamp($ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:2855
static crypt($password, $salt=false)
Make a new-style password hash.
Definition: User.php:5226
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:1166
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:238
getBlock($bFromSlave=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1975
sendConfirmationMail($type= 'created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4503
string $mEmailToken
Cache variables.
Definition: User.php:221
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
$wgExtendedLoginCookieExpiration
Default login cookie lifetime, in seconds.
setEmailAuthenticationTimestamp($timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4657
static comparePasswords($hash, $password, $userId=false)
Compare a password hash with a plain-text password.
Definition: User.php:5245
$wgPasswordPolicy
Password policy for local wiki users.
Value object representing a CSRF token.
Definition: Token.php:32
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4736
wfGetLB($wiki=false)
Get a load balancer object.
$wgRateLimitsExcludedIPs
Array of IPs which should be excluded from rate limits.
wfReadOnly()
Check whether the wiki is in read-only mode.
static getMain()
Static methods.
$wgLegacyEncoding
Set this to eg 'ISO-8859-1' to perform character set conversion when loading old revisions not marked...
setNewtalk($val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2331
static groupHasPermission($group, $role)
Check, if the given group has the given permission.
Definition: User.php:4822
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:5332
getPassword()
Definition: User.php:2483
static changeableByGroup($group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:5020
isAllowed($action= '')
Internal mechanics of testing a permission.
Definition: User.php:3576
isItemLoaded($item, $all= 'all')
Return whether an item has been loaded.
Definition: User.php:1204
Block $mBlock
Definition: User.php:292
string $mBlockreason
Definition: User.php:270
isAnon()
Get whether the user is anonymous.
Definition: User.php:3521
inDnsBlacklist($ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1727
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:3192
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:1057
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
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:1020
validateCache($timestamp)
Validate the cache for this account.
Definition: User.php:2438
static invalidateAllPasswordsForUser($username)
Invalidate all passwords for a user, by name.
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:879
$summary
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
static getSaveBlacklist()
Definition: Preferences.php:73
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3906
integer $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:301
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:471
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4721
MediaWiki exception.
Definition: MWException.php:26
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:51
incEditCount()
Deferred version of incEditCountImmediate()
Definition: User.php:5132
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2363
setTarget($target)
Set the target for this block, and update $this->type accordingly.
Definition: Block.php:1374
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
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:3108
$wgProxyList
Big list of banned IP addresses.
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:650
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:213
clearCookie($name, $secure=null, $params=[])
Clear a cookie on the user's client.
Definition: User.php:3819
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4668
array $mEffectiveGroups
Definition: User.php:272
$cache
Definition: mcc.php:33
$wgAvailableRights
A list of available rights, in addition to the ones defined by the core.
const IGNORE_USER_RIGHTS
Definition: User.php:84
$params
$wgProxyWhitelist
Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other methods mi...
string $mEmailAuthenticated
Cache variables.
Definition: User.php:219
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:289
static makeTitleSafe($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:527
static isIP($name)
Does the string match an anonymous IP address?
Definition: User.php:824
$wgRemoveGroups
const DB_SLAVE
Definition: Defines.php:46
getBlockedStatus($bFromSlave=true)
Get blocking information.
Definition: User.php:1613
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:4959
$wgNewPasswordExpiry
The time, in seconds, when an emailed temporary password expires.
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 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 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
getPasswordValidity($password)
Given unvalidated password input, return error message on failure.
Definition: User.php:1005
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead.&$feedLinks conditions will AND in the final query as a Content object as a Content object $title
Definition: hooks.txt:312
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3257
$wgRateLimits
Simple rate limiter options to brake edit floods.
array $mImplicitGroups
Definition: User.php:274
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:199
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
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:4196
This serves as the entry point to the authentication system.
Definition: AuthManager.php:43
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...
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
isBot()
Definition: User.php:3529
$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.
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
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:1601
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:1816
getTokenUrl($page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4613
setInternalPassword($str)
Set the password and reset the random token unconditionally.
Definition: User.php:2548
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history...
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:2950
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:927
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:776
loadFromDatabase($flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1256
string $mToken
Cache variables.
Definition: User.php:217
isWatched($title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3640
static newInvalidPassword()
Create an InvalidPassword.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
static link($target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:203
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:3589
$wgPasswordSender
Sender email address for e-mail notifications.
isBlocked($bFromSlave=true)
Check if user is blocked.
Definition: User.php:1965
array $mOptions
Definition: User.php:284
getOption($oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2915
makeUpdateConditions(DatabaseBase $db, array $conditions)
Builds update conditions.
Definition: User.php:1480
static getGroupName($group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:4891
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:260
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4643
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1400
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5409
idForName($flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:4004
$wgCookieExpiration
Default cookie lifetime, in seconds.
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:5506
Database abstraction object.
Definition: Database.php:32
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:99
$wgApplyIpBlocksToXff
Whether to look at the X-Forwarded-For header's list of (potentially spoofed) IPs and apply IP blocks...
setId($v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2130
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3276
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3623
$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:211
setExtendedLoginCookie($name, $value, $secure)
Set an extended login cookie on the user's client.
Definition: User.php:3839
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition: Block.php:1097
this hook is for auditing only $req
Definition: hooks.txt:981
getNewtalk()
Check if the user has new messages.
Definition: User.php:2183
bool $mLocked
Definition: User.php:280
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:776
static newFromConfirmationCode($code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:564
loadFromRow($row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1303
$wgUseEnotif
Definition: Setup.php:343
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3316
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:315
static getGroupMember($group, $username= '#')
Get the localized descriptive name for a member of a group, if it exists.
Definition: User.php:4903
array $mGroups
Cache variables.
Definition: User.php:229
confirmationTokenUrl($token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4586
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:2458
removeGroup($group)
Remove the user from the given group.
Definition: User.php:3473
prevents($action, $x=null)
Get/set whether the Block prevents a given action.
Definition: Block.php:968
static hasFlags($bitfield, $flags)
The ContentHandler facility adds support for arbitrary content types on wiki pages
$wgMinimalPasswordLength
Specifies the minimal length of a user password.
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from...
Definition: User.php:4183
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3610
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:3329
getId()
Get the user's ID.
Definition: User.php:2114
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2806
Show an error when any operation involving passwords fails to run.
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4110
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
bool $mAllowUsertalk
Definition: User.php:295
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2407
getTokenFromOption($oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:3023
removeWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3671
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4626
getGlobalBlock($ip= '')
Check if user is blocked on all wikis.
Definition: User.php:2053
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:2816
initEditCount($add=0)
Initialize user_editcount from data out of the revision table.
Definition: User.php:5182
$wgDisableAuthManager
Disable AuthManager.
bool $mHideName
Definition: User.php:282
static getPasswordFactory()
Lazily instantiate and return a factory object for making passwords.
Definition: User.php:5475
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:964
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:588
getEditCount()
Get the user's edit count.
Definition: User.php:3403
getUserPage()
Get this user's personal page title.
Definition: User.php:4255
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5593
static idFromName($name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:764
$wgPasswordSalt
For compatibility with old installations set to false.
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3598
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
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:1020
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:3860
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3237
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2026
array $mRights
Definition: User.php:268
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2244
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:255
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:1020
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:4450
static newFatalPermissionDeniedStatus($permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5569
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:4428
isLocked()
Check if user account is locked.
Definition: User.php:2082
$messages
wfMemcKey()
Make a cache key for the local wiki.
string $mHash
Definition: User.php:266
const DB_MASTER
Definition: Defines.php:47
static validateEmail($addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1941
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4683
Some quick notes on the file repository architecture Functionality as driven by data model *The repository object stores configuration information about a file storage method *The file object is a process local cache of information about a particular file Thus the file object is the primary public entry point for obtaining information about since access via the file object can be cached
Definition: README:3
equals(User $user)
Checks if two user objects point to the same user.
Definition: User.php:5613
$wgBlockAllowsUTEdit
Set this to true to allow blocked users to edit their own user talk page.
loadFromId($flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:420
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
This serves as the entry point to the MediaWiki session handling system.
setRealName($str)
Set the user's real name.
Definition: User.php:2900
addWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3654
array $mOptionOverrides
Cache variables.
Definition: User.php:231
checkTemporaryPassword($plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4377
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:1561
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:1214
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:1598
string $mRealName
Cache variables.
Definition: User.php:208
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:340
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
static newFromIDs($ids)
Definition: UserArray.php:43
static getSubnet($ip)
Returns the subnet of a given IP.
Definition: IP.php:777
getCacheKey(WANObjectCache $cache)
Definition: User.php:461
static getGroupPermissions($groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4772
const NS_USER_TALK
Definition: Defines.php:72
static array $languagesWithVariants
languages supporting variants
getTalkPage()
Get this user's talk page title.
Definition: User.php:4264
static flattenOptions($options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
$wgPasswordReminderResendTime
Minimum time, in hours, which must elapse between password reminder emails for a given account...
getToken($forceCreation=true)
Get the user's current token.
Definition: User.php:2669
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:1804
static getRightDescription($right)
Get the description of a given right.
Definition: User.php:5211
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:2376
if(is_null($wgLocalTZoffset)) if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:663
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:2376
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:503
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 getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4799
static newGood($value=null)
Factory function for good results.
Definition: Status.php:101
getRights()
Get the permissions this user has.
Definition: User.php:3291
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3379
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2017
This is a value object for authentication requests.
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:2376
$wgEnableDnsBlacklist
Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies.
setPasswordInternal($str)
Actually set the password and such.
Definition: User.php:2569
incEditCountImmediate()
Increment the user's edit-count field.
Definition: User.php:5143
int $mId
Cache variables.
Definition: User.php:204
addGroup($group)
Add the user to the given group.
Definition: User.php:3433
$wgUser
Definition: Setup.php:801
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4568
getTouched()
Get the user touched timestamp.
Definition: User.php:2450
Block $mGlobalBlock
Definition: User.php:278
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:243
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310