MediaWiki  1.28.3
User.php
Go to the documentation of this file.
1 <?php
30 
36 define( 'EDIT_TOKEN_SUFFIX', Token::SUFFIX );
37 
48 class User implements IDBAccessObject {
52  const TOKEN_LENGTH = 32;
53 
57  const INVALID_TOKEN = '*** INVALID ***';
58 
65 
69  const VERSION = 10;
70 
76 
80  const CHECK_USER_RIGHTS = true;
81 
85  const IGNORE_USER_RIGHTS = false;
86 
93  protected static $mCacheVars = [
94  // user table
95  'mId',
96  'mName',
97  'mRealName',
98  'mEmail',
99  'mTouched',
100  'mToken',
101  'mEmailAuthenticated',
102  'mEmailToken',
103  'mEmailTokenExpires',
104  'mRegistration',
105  'mEditCount',
106  // user_groups table
107  'mGroups',
108  // user_properties table
109  'mOptionOverrides',
110  ];
111 
118  protected static $mCoreRights = [
119  'apihighlimits',
120  'applychangetags',
121  'autoconfirmed',
122  'autocreateaccount',
123  'autopatrol',
124  'bigdelete',
125  'block',
126  'blockemail',
127  'bot',
128  'browsearchive',
129  'changetags',
130  'createaccount',
131  'createpage',
132  'createtalk',
133  'delete',
134  'deletechangetags',
135  'deletedhistory',
136  'deletedtext',
137  'deletelogentry',
138  'deleterevision',
139  'edit',
140  'editcontentmodel',
141  'editinterface',
142  'editprotected',
143  'editmyoptions',
144  'editmyprivateinfo',
145  'editmyusercss',
146  'editmyuserjs',
147  'editmywatchlist',
148  'editsemiprotected',
149  'editusercssjs', # deprecated
150  'editusercss',
151  'edituserjs',
152  'hideuser',
153  'import',
154  'importupload',
155  'ipblock-exempt',
156  'managechangetags',
157  'markbotedits',
158  'mergehistory',
159  'minoredit',
160  'move',
161  'movefile',
162  'move-categorypages',
163  'move-rootuserpages',
164  'move-subpages',
165  'nominornewtalk',
166  'noratelimit',
167  'override-export-depth',
168  'pagelang',
169  'passwordreset',
170  'patrol',
171  'patrolmarks',
172  'protect',
173  'purge',
174  'read',
175  'reupload',
176  'reupload-own',
177  'reupload-shared',
178  'rollback',
179  'sendemail',
180  'siteadmin',
181  'suppressionlog',
182  'suppressredirect',
183  'suppressrevision',
184  'unblockself',
185  'undelete',
186  'unwatchedpages',
187  'upload',
188  'upload_by_url',
189  'userrights',
190  'userrights-interwiki',
191  'viewmyprivateinfo',
192  'viewmywatchlist',
193  'viewsuppressed',
194  'writeapi',
195  ];
196 
200  protected static $mAllRights = false;
201 
203  // @{
205  public $mId;
207  public $mName;
209  public $mRealName;
210 
212  public $mEmail;
214  public $mTouched;
216  protected $mQuickTouched;
218  protected $mToken;
222  protected $mEmailToken;
226  protected $mRegistration;
228  protected $mEditCount;
230  public $mGroups;
232  protected $mOptionOverrides;
233  // @}
234 
238  // @{
240 
244  protected $mLoadedItems = [];
245  // @}
246 
256  public $mFrom;
257 
261  protected $mNewtalk;
263  protected $mDatePreference;
265  public $mBlockedby;
267  protected $mHash;
269  public $mRights;
271  protected $mBlockreason;
273  protected $mEffectiveGroups;
275  protected $mImplicitGroups;
277  protected $mFormerGroups;
279  protected $mGlobalBlock;
281  protected $mLocked;
283  public $mHideName;
285  public $mOptions;
286 
290  private $mRequest;
291 
293  public $mBlock;
294 
296  protected $mAllowUsertalk;
297 
299  private $mBlockedFromCreateAccount = false;
300 
302  protected $queryFlagsUsed = self::READ_NORMAL;
303 
304  public static $idCacheByName = [];
305 
316  public function __construct() {
317  $this->clearInstanceCache( 'defaults' );
318  }
319 
323  public function __toString() {
324  return (string)$this->getName();
325  }
326 
341  public function isSafeToLoad() {
343 
344  // The user is safe to load if:
345  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
346  // * mLoadedItems === true (already loaded)
347  // * mFrom !== 'session' (sessions not involved at all)
348 
349  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
350  $this->mLoadedItems === true || $this->mFrom !== 'session';
351  }
352 
358  public function load( $flags = self::READ_NORMAL ) {
360 
361  if ( $this->mLoadedItems === true ) {
362  return;
363  }
364 
365  // Set it now to avoid infinite recursion in accessors
366  $oldLoadedItems = $this->mLoadedItems;
367  $this->mLoadedItems = true;
368  $this->queryFlagsUsed = $flags;
369 
370  // If this is called too early, things are likely to break.
371  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
373  ->warning( 'User::loadFromSession called before the end of Setup.php', [
374  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
375  ] );
376  $this->loadDefaults();
377  $this->mLoadedItems = $oldLoadedItems;
378  return;
379  }
380 
381  switch ( $this->mFrom ) {
382  case 'defaults':
383  $this->loadDefaults();
384  break;
385  case 'name':
386  // Make sure this thread sees its own changes
387  if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
388  $flags |= self::READ_LATEST;
389  $this->queryFlagsUsed = $flags;
390  }
391 
392  $this->mId = self::idFromName( $this->mName, $flags );
393  if ( !$this->mId ) {
394  // Nonexistent user placeholder object
395  $this->loadDefaults( $this->mName );
396  } else {
397  $this->loadFromId( $flags );
398  }
399  break;
400  case 'id':
401  $this->loadFromId( $flags );
402  break;
403  case 'session':
404  if ( !$this->loadFromSession() ) {
405  // Loading from session failed. Load defaults.
406  $this->loadDefaults();
407  }
408  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
409  break;
410  default:
411  throw new UnexpectedValueException(
412  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
413  }
414  }
415 
421  public function loadFromId( $flags = self::READ_NORMAL ) {
422  if ( $this->mId == 0 ) {
423  // Anonymous users are not in the database (don't need cache)
424  $this->loadDefaults();
425  return false;
426  }
427 
428  // Try cache (unless this needs data from the master DB).
429  // NOTE: if this thread called saveSettings(), the cache was cleared.
430  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
431  if ( $latest ) {
432  if ( !$this->loadFromDatabase( $flags ) ) {
433  // Can't load from ID
434  return false;
435  }
436  } else {
437  $this->loadFromCache();
438  }
439 
440  $this->mLoadedItems = true;
441  $this->queryFlagsUsed = $flags;
442 
443  return true;
444  }
445 
451  public static function purge( $wikiId, $userId ) {
453  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
454  $cache->delete( $key );
455  }
456 
462  protected function getCacheKey( WANObjectCache $cache ) {
463  return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId );
464  }
465 
472  protected function loadFromCache() {
474  $data = $cache->getWithSetCallback(
475  $this->getCacheKey( $cache ),
476  $cache::TTL_HOUR,
477  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
479  wfDebug( "User: cache miss for user {$this->mId}\n" );
480 
481  $this->loadFromDatabase( self::READ_NORMAL );
482  $this->loadGroups();
483  $this->loadOptions();
484 
485  $data = [];
486  foreach ( self::$mCacheVars as $name ) {
487  $data[$name] = $this->$name;
488  }
489 
490  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
491 
492  return $data;
493 
494  },
495  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
496  );
497 
498  // Restore from cache
499  foreach ( self::$mCacheVars as $name ) {
500  $this->$name = $data[$name];
501  }
502 
503  return true;
504  }
505 
507  // @{
508 
525  public static function newFromName( $name, $validate = 'valid' ) {
526  if ( $validate === true ) {
527  $validate = 'valid';
528  }
529  $name = self::getCanonicalName( $name, $validate );
530  if ( $name === false ) {
531  return false;
532  } else {
533  // Create unloaded user object
534  $u = new User;
535  $u->mName = $name;
536  $u->mFrom = 'name';
537  $u->setItemLoaded( 'name' );
538  return $u;
539  }
540  }
541 
548  public static function newFromId( $id ) {
549  $u = new User;
550  $u->mId = $id;
551  $u->mFrom = 'id';
552  $u->setItemLoaded( 'id' );
553  return $u;
554  }
555 
567  public static function newFromConfirmationCode( $code, $flags = 0 ) {
568  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
569  ? wfGetDB( DB_MASTER )
570  : wfGetDB( DB_REPLICA );
571 
572  $id = $db->selectField(
573  'user',
574  'user_id',
575  [
576  'user_email_token' => md5( $code ),
577  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
578  ]
579  );
580 
581  return $id ? User::newFromId( $id ) : null;
582  }
583 
591  public static function newFromSession( WebRequest $request = null ) {
592  $user = new User;
593  $user->mFrom = 'session';
594  $user->mRequest = $request;
595  return $user;
596  }
597 
612  public static function newFromRow( $row, $data = null ) {
613  $user = new User;
614  $user->loadFromRow( $row, $data );
615  return $user;
616  }
617 
653  public static function newSystemUser( $name, $options = [] ) {
654  $options += [
655  'validate' => 'valid',
656  'create' => true,
657  'steal' => false,
658  ];
659 
660  $name = self::getCanonicalName( $name, $options['validate'] );
661  if ( $name === false ) {
662  return null;
663  }
664 
665  $fields = self::selectFields();
666 
667  $dbw = wfGetDB( DB_MASTER );
668  $row = $dbw->selectRow(
669  'user',
670  $fields,
671  [ 'user_name' => $name ],
672  __METHOD__
673  );
674  if ( !$row ) {
675  // No user. Create it?
676  return $options['create'] ? self::createNew( $name ) : null;
677  }
678  $user = self::newFromRow( $row );
679 
680  // A user is considered to exist as a non-system user if it can
681  // authenticate, or has an email set, or has a non-invalid token.
682  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
683  AuthManager::singleton()->userCanAuthenticate( $name )
684  ) {
685  // User exists. Steal it?
686  if ( !$options['steal'] ) {
687  return null;
688  }
689 
690  AuthManager::singleton()->revokeAccessForUser( $name );
691 
692  $user->invalidateEmail();
693  $user->mToken = self::INVALID_TOKEN;
694  $user->saveSettings();
695  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
696  }
697 
698  return $user;
699  }
700 
701  // @}
702 
708  public static function whoIs( $id ) {
709  return UserCache::singleton()->getProp( $id, 'name' );
710  }
711 
718  public static function whoIsReal( $id ) {
719  return UserCache::singleton()->getProp( $id, 'real_name' );
720  }
721 
728  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
730  if ( is_null( $nt ) ) {
731  // Illegal name
732  return null;
733  }
734 
735  if ( !( $flags & self::READ_LATEST ) && isset( self::$idCacheByName[$name] ) ) {
736  return self::$idCacheByName[$name];
737  }
738 
740  $db = wfGetDB( $index );
741 
742  $s = $db->selectRow(
743  'user',
744  [ 'user_id' ],
745  [ 'user_name' => $nt->getText() ],
746  __METHOD__,
747  $options
748  );
749 
750  if ( $s === false ) {
751  $result = null;
752  } else {
753  $result = $s->user_id;
754  }
755 
756  self::$idCacheByName[$name] = $result;
757 
758  if ( count( self::$idCacheByName ) > 1000 ) {
759  self::$idCacheByName = [];
760  }
761 
762  return $result;
763  }
764 
768  public static function resetIdByNameCache() {
769  self::$idCacheByName = [];
770  }
771 
788  public static function isIP( $name ) {
789  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
790  || IP::isIPv6( $name );
791  }
792 
804  public static function isValidUserName( $name ) {
805  global $wgContLang, $wgMaxNameChars;
806 
807  if ( $name == ''
808  || User::isIP( $name )
809  || strpos( $name, '/' ) !== false
810  || strlen( $name ) > $wgMaxNameChars
811  || $name != $wgContLang->ucfirst( $name )
812  ) {
813  return false;
814  }
815 
816  // Ensure that the name can't be misresolved as a different title,
817  // such as with extra namespace keys at the start.
818  $parsed = Title::newFromText( $name );
819  if ( is_null( $parsed )
820  || $parsed->getNamespace()
821  || strcmp( $name, $parsed->getPrefixedText() ) ) {
822  return false;
823  }
824 
825  // Check an additional blacklist of troublemaker characters.
826  // Should these be merged into the title char list?
827  $unicodeBlacklist = '/[' .
828  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
829  '\x{00a0}' . # non-breaking space
830  '\x{2000}-\x{200f}' . # various whitespace
831  '\x{2028}-\x{202f}' . # breaks and control chars
832  '\x{3000}' . # ideographic space
833  '\x{e000}-\x{f8ff}' . # private use
834  ']/u';
835  if ( preg_match( $unicodeBlacklist, $name ) ) {
836  return false;
837  }
838 
839  return true;
840  }
841 
853  public static function isUsableName( $name ) {
854  global $wgReservedUsernames;
855  // Must be a valid username, obviously ;)
856  if ( !self::isValidUserName( $name ) ) {
857  return false;
858  }
859 
860  static $reservedUsernames = false;
861  if ( !$reservedUsernames ) {
862  $reservedUsernames = $wgReservedUsernames;
863  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
864  }
865 
866  // Certain names may be reserved for batch processes.
867  foreach ( $reservedUsernames as $reserved ) {
868  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
869  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
870  }
871  if ( $reserved == $name ) {
872  return false;
873  }
874  }
875  return true;
876  }
877 
888  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
889  if ( $groups === [] ) {
890  return UserArrayFromResult::newFromIDs( [] );
891  }
892 
893  $groups = array_unique( (array)$groups );
894  $limit = min( 5000, $limit );
895 
896  $conds = [ 'ug_group' => $groups ];
897  if ( $after !== null ) {
898  $conds[] = 'ug_user > ' . (int)$after;
899  }
900 
901  $dbr = wfGetDB( DB_REPLICA );
902  $ids = $dbr->selectFieldValues(
903  'user_groups',
904  'ug_user',
905  $conds,
906  __METHOD__,
907  [
908  'DISTINCT' => true,
909  'ORDER BY' => 'ug_user',
910  'LIMIT' => $limit,
911  ]
912  ) ?: [];
913  return UserArray::newFromIDs( $ids );
914  }
915 
928  public static function isCreatableName( $name ) {
929  global $wgInvalidUsernameCharacters;
930 
931  // Ensure that the username isn't longer than 235 bytes, so that
932  // (at least for the builtin skins) user javascript and css files
933  // will work. (bug 23080)
934  if ( strlen( $name ) > 235 ) {
935  wfDebugLog( 'username', __METHOD__ .
936  ": '$name' invalid due to length" );
937  return false;
938  }
939 
940  // Preg yells if you try to give it an empty string
941  if ( $wgInvalidUsernameCharacters !== '' ) {
942  if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
943  wfDebugLog( 'username', __METHOD__ .
944  ": '$name' invalid due to wgInvalidUsernameCharacters" );
945  return false;
946  }
947  }
948 
949  return self::isUsableName( $name );
950  }
951 
958  public function isValidPassword( $password ) {
959  // simple boolean wrapper for getPasswordValidity
960  return $this->getPasswordValidity( $password ) === true;
961  }
962 
969  public function getPasswordValidity( $password ) {
970  $result = $this->checkPasswordValidity( $password );
971  if ( $result->isGood() ) {
972  return true;
973  } else {
974  $messages = [];
975  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
976  $messages[] = $error['message'];
977  }
978  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
979  $messages[] = $warning['message'];
980  }
981  if ( count( $messages ) === 1 ) {
982  return $messages[0];
983  }
984  return $messages;
985  }
986  }
987 
1006  public function checkPasswordValidity( $password, $purpose = 'login' ) {
1007  global $wgPasswordPolicy;
1008 
1009  $upp = new UserPasswordPolicy(
1010  $wgPasswordPolicy['policies'],
1011  $wgPasswordPolicy['checks']
1012  );
1013 
1015  $result = false; // init $result to false for the internal checks
1016 
1017  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1018  $status->error( $result );
1019  return $status;
1020  }
1021 
1022  if ( $result === false ) {
1023  $status->merge( $upp->checkUserPassword( $this, $password, $purpose ) );
1024  return $status;
1025  } elseif ( $result === true ) {
1026  return $status;
1027  } else {
1028  $status->error( $result );
1029  return $status; // the isValidPassword hook set a string $result and returned true
1030  }
1031  }
1032 
1046  public static function getCanonicalName( $name, $validate = 'valid' ) {
1047  // Force usernames to capital
1049  $name = $wgContLang->ucfirst( $name );
1050 
1051  # Reject names containing '#'; these will be cleaned up
1052  # with title normalisation, but then it's too late to
1053  # check elsewhere
1054  if ( strpos( $name, '#' ) !== false ) {
1055  return false;
1056  }
1057 
1058  // Clean up name according to title rules,
1059  // but only when validation is requested (bug 12654)
1060  $t = ( $validate !== false ) ?
1062  // Check for invalid titles
1063  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1064  return false;
1065  }
1066 
1067  // Reject various classes of invalid names
1068  $name = AuthManager::callLegacyAuthPlugin(
1069  'getCanonicalName', [ $t->getText() ], $t->getText()
1070  );
1071 
1072  switch ( $validate ) {
1073  case false:
1074  break;
1075  case 'valid':
1076  if ( !User::isValidUserName( $name ) ) {
1077  $name = false;
1078  }
1079  break;
1080  case 'usable':
1081  if ( !User::isUsableName( $name ) ) {
1082  $name = false;
1083  }
1084  break;
1085  case 'creatable':
1086  if ( !User::isCreatableName( $name ) ) {
1087  $name = false;
1088  }
1089  break;
1090  default:
1091  throw new InvalidArgumentException(
1092  'Invalid parameter value for $validate in ' . __METHOD__ );
1093  }
1094  return $name;
1095  }
1096 
1105  public static function edits( $uid ) {
1106  wfDeprecated( __METHOD__, '1.21' );
1107  $user = self::newFromId( $uid );
1108  return $user->getEditCount();
1109  }
1110 
1117  public static function randomPassword() {
1118  global $wgMinimalPasswordLength;
1119  return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1120  }
1121 
1130  public function loadDefaults( $name = false ) {
1131  $this->mId = 0;
1132  $this->mName = $name;
1133  $this->mRealName = '';
1134  $this->mEmail = '';
1135  $this->mOptionOverrides = null;
1136  $this->mOptionsLoaded = false;
1137 
1138  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1139  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1140  if ( $loggedOut !== 0 ) {
1141  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1142  } else {
1143  $this->mTouched = '1'; # Allow any pages to be cached
1144  }
1145 
1146  $this->mToken = null; // Don't run cryptographic functions till we need a token
1147  $this->mEmailAuthenticated = null;
1148  $this->mEmailToken = '';
1149  $this->mEmailTokenExpires = null;
1150  $this->mRegistration = wfTimestamp( TS_MW );
1151  $this->mGroups = [];
1152 
1153  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1154  }
1155 
1168  public function isItemLoaded( $item, $all = 'all' ) {
1169  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1170  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1171  }
1172 
1178  protected function setItemLoaded( $item ) {
1179  if ( is_array( $this->mLoadedItems ) ) {
1180  $this->mLoadedItems[$item] = true;
1181  }
1182  }
1183 
1189  private function loadFromSession() {
1190  // Deprecated hook
1191  $result = null;
1192  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1193  if ( $result !== null ) {
1194  return $result;
1195  }
1196 
1197  // MediaWiki\Session\Session already did the necessary authentication of the user
1198  // returned here, so just use it if applicable.
1199  $session = $this->getRequest()->getSession();
1200  $user = $session->getUser();
1201  if ( $user->isLoggedIn() ) {
1202  $this->loadFromUserObject( $user );
1203  // Other code expects these to be set in the session, so set them.
1204  $session->set( 'wsUserID', $this->getId() );
1205  $session->set( 'wsUserName', $this->getName() );
1206  $session->set( 'wsToken', $this->getToken() );
1207  return true;
1208  }
1209 
1210  return false;
1211  }
1212 
1220  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1221  // Paranoia
1222  $this->mId = intval( $this->mId );
1223 
1224  if ( !$this->mId ) {
1225  // Anonymous users are not in the database
1226  $this->loadDefaults();
1227  return false;
1228  }
1229 
1231  $db = wfGetDB( $index );
1232 
1233  $s = $db->selectRow(
1234  'user',
1235  self::selectFields(),
1236  [ 'user_id' => $this->mId ],
1237  __METHOD__,
1238  $options
1239  );
1240 
1241  $this->queryFlagsUsed = $flags;
1242  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1243 
1244  if ( $s !== false ) {
1245  // Initialise user table data
1246  $this->loadFromRow( $s );
1247  $this->mGroups = null; // deferred
1248  $this->getEditCount(); // revalidation for nulls
1249  return true;
1250  } else {
1251  // Invalid user_id
1252  $this->mId = 0;
1253  $this->loadDefaults();
1254  return false;
1255  }
1256  }
1257 
1267  protected function loadFromRow( $row, $data = null ) {
1268  $all = true;
1269 
1270  $this->mGroups = null; // deferred
1271 
1272  if ( isset( $row->user_name ) ) {
1273  $this->mName = $row->user_name;
1274  $this->mFrom = 'name';
1275  $this->setItemLoaded( 'name' );
1276  } else {
1277  $all = false;
1278  }
1279 
1280  if ( isset( $row->user_real_name ) ) {
1281  $this->mRealName = $row->user_real_name;
1282  $this->setItemLoaded( 'realname' );
1283  } else {
1284  $all = false;
1285  }
1286 
1287  if ( isset( $row->user_id ) ) {
1288  $this->mId = intval( $row->user_id );
1289  $this->mFrom = 'id';
1290  $this->setItemLoaded( 'id' );
1291  } else {
1292  $all = false;
1293  }
1294 
1295  if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
1296  self::$idCacheByName[$row->user_name] = $row->user_id;
1297  }
1298 
1299  if ( isset( $row->user_editcount ) ) {
1300  $this->mEditCount = $row->user_editcount;
1301  } else {
1302  $all = false;
1303  }
1304 
1305  if ( isset( $row->user_touched ) ) {
1306  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1307  } else {
1308  $all = false;
1309  }
1310 
1311  if ( isset( $row->user_token ) ) {
1312  // The definition for the column is binary(32), so trim the NULs
1313  // that appends. The previous definition was char(32), so trim
1314  // spaces too.
1315  $this->mToken = rtrim( $row->user_token, " \0" );
1316  if ( $this->mToken === '' ) {
1317  $this->mToken = null;
1318  }
1319  } else {
1320  $all = false;
1321  }
1322 
1323  if ( isset( $row->user_email ) ) {
1324  $this->mEmail = $row->user_email;
1325  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1326  $this->mEmailToken = $row->user_email_token;
1327  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1328  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1329  } else {
1330  $all = false;
1331  }
1332 
1333  if ( $all ) {
1334  $this->mLoadedItems = true;
1335  }
1336 
1337  if ( is_array( $data ) ) {
1338  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1339  $this->mGroups = $data['user_groups'];
1340  }
1341  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1342  $this->loadOptions( $data['user_properties'] );
1343  }
1344  }
1345  }
1346 
1352  protected function loadFromUserObject( $user ) {
1353  $user->load();
1354  foreach ( self::$mCacheVars as $var ) {
1355  $this->$var = $user->$var;
1356  }
1357  }
1358 
1362  private function loadGroups() {
1363  if ( is_null( $this->mGroups ) ) {
1364  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1365  ? wfGetDB( DB_MASTER )
1366  : wfGetDB( DB_REPLICA );
1367  $res = $db->select( 'user_groups',
1368  [ 'ug_group' ],
1369  [ 'ug_user' => $this->mId ],
1370  __METHOD__ );
1371  $this->mGroups = [];
1372  foreach ( $res as $row ) {
1373  $this->mGroups[] = $row->ug_group;
1374  }
1375  }
1376  }
1377 
1392  public function addAutopromoteOnceGroups( $event ) {
1393  global $wgAutopromoteOnceLogInRC;
1394 
1395  if ( wfReadOnly() || !$this->getId() ) {
1396  return [];
1397  }
1398 
1399  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1400  if ( !count( $toPromote ) ) {
1401  return [];
1402  }
1403 
1404  if ( !$this->checkAndSetTouched() ) {
1405  return []; // raced out (bug T48834)
1406  }
1407 
1408  $oldGroups = $this->getGroups(); // previous groups
1409  foreach ( $toPromote as $group ) {
1410  $this->addGroup( $group );
1411  }
1412  // update groups in external authentication database
1413  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false ] );
1414  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1415 
1416  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1417 
1418  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1419  $logEntry->setPerformer( $this );
1420  $logEntry->setTarget( $this->getUserPage() );
1421  $logEntry->setParameters( [
1422  '4::oldgroups' => $oldGroups,
1423  '5::newgroups' => $newGroups,
1424  ] );
1425  $logid = $logEntry->insert();
1426  if ( $wgAutopromoteOnceLogInRC ) {
1427  $logEntry->publish( $logid );
1428  }
1429 
1430  return $toPromote;
1431  }
1432 
1442  protected function makeUpdateConditions( Database $db, array $conditions ) {
1443  if ( $this->mTouched ) {
1444  // CAS check: only update if the row wasn't changed sicne it was loaded.
1445  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1446  }
1447 
1448  return $conditions;
1449  }
1450 
1460  protected function checkAndSetTouched() {
1461  $this->load();
1462 
1463  if ( !$this->mId ) {
1464  return false; // anon
1465  }
1466 
1467  // Get a new user_touched that is higher than the old one
1468  $newTouched = $this->newTouchedTimestamp();
1469 
1470  $dbw = wfGetDB( DB_MASTER );
1471  $dbw->update( 'user',
1472  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1473  $this->makeUpdateConditions( $dbw, [
1474  'user_id' => $this->mId,
1475  ] ),
1476  __METHOD__
1477  );
1478  $success = ( $dbw->affectedRows() > 0 );
1479 
1480  if ( $success ) {
1481  $this->mTouched = $newTouched;
1482  $this->clearSharedCache();
1483  } else {
1484  // Clears on failure too since that is desired if the cache is stale
1485  $this->clearSharedCache( 'refresh' );
1486  }
1487 
1488  return $success;
1489  }
1490 
1498  public function clearInstanceCache( $reloadFrom = false ) {
1499  $this->mNewtalk = -1;
1500  $this->mDatePreference = null;
1501  $this->mBlockedby = -1; # Unset
1502  $this->mHash = false;
1503  $this->mRights = null;
1504  $this->mEffectiveGroups = null;
1505  $this->mImplicitGroups = null;
1506  $this->mGroups = null;
1507  $this->mOptions = null;
1508  $this->mOptionsLoaded = false;
1509  $this->mEditCount = null;
1510 
1511  if ( $reloadFrom ) {
1512  $this->mLoadedItems = [];
1513  $this->mFrom = $reloadFrom;
1514  }
1515  }
1516 
1523  public static function getDefaultOptions() {
1524  global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
1525 
1526  static $defOpt = null;
1527  static $defOptLang = null;
1528 
1529  if ( $defOpt !== null && $defOptLang === $wgContLang->getCode() ) {
1530  // $wgContLang does not change (and should not change) mid-request,
1531  // but the unit tests change it anyway, and expect this method to
1532  // return values relevant to the current $wgContLang.
1533  return $defOpt;
1534  }
1535 
1536  $defOpt = $wgDefaultUserOptions;
1537  // Default language setting
1538  $defOptLang = $wgContLang->getCode();
1539  $defOpt['language'] = $defOptLang;
1540  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1541  $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
1542  }
1543 
1544  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1545  // since extensions may change the set of searchable namespaces depending
1546  // on user groups/permissions.
1547  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1548  $defOpt['searchNs' . $nsnum] = (boolean)$val;
1549  }
1550  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1551 
1552  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1553 
1554  return $defOpt;
1555  }
1556 
1563  public static function getDefaultOption( $opt ) {
1564  $defOpts = self::getDefaultOptions();
1565  if ( isset( $defOpts[$opt] ) ) {
1566  return $defOpts[$opt];
1567  } else {
1568  return null;
1569  }
1570  }
1571 
1578  private function getBlockedStatus( $bFromSlave = true ) {
1579  global $wgProxyWhitelist, $wgUser, $wgApplyIpBlocksToXff;
1580 
1581  if ( -1 != $this->mBlockedby ) {
1582  return;
1583  }
1584 
1585  wfDebug( __METHOD__ . ": checking...\n" );
1586 
1587  // Initialize data...
1588  // Otherwise something ends up stomping on $this->mBlockedby when
1589  // things get lazy-loaded later, causing false positive block hits
1590  // due to -1 !== 0. Probably session-related... Nothing should be
1591  // overwriting mBlockedby, surely?
1592  $this->load();
1593 
1594  # We only need to worry about passing the IP address to the Block generator if the
1595  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1596  # know which IP address they're actually coming from
1597  $ip = null;
1598  if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1599  // $wgUser->getName() only works after the end of Setup.php. Until
1600  // then, assume it's a logged-out user.
1601  $globalUserName = $wgUser->isSafeToLoad()
1602  ? $wgUser->getName()
1603  : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1604  if ( $this->getName() === $globalUserName ) {
1605  $ip = $this->getRequest()->getIP();
1606  }
1607  }
1608 
1609  // User/IP blocking
1610  $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1611 
1612  // Proxy blocking
1613  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1614  // Local list
1615  if ( self::isLocallyBlockedProxy( $ip ) ) {
1616  $block = new Block;
1617  $block->setBlocker( wfMessage( 'proxyblocker' )->text() );
1618  $block->mReason = wfMessage( 'proxyblockreason' )->text();
1619  $block->setTarget( $ip );
1620  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1621  $block = new Block;
1622  $block->setBlocker( wfMessage( 'sorbs' )->text() );
1623  $block->mReason = wfMessage( 'sorbsreason' )->text();
1624  $block->setTarget( $ip );
1625  }
1626  }
1627 
1628  // (bug 23343) Apply IP blocks to the contents of XFF headers, if enabled
1629  if ( !$block instanceof Block
1630  && $wgApplyIpBlocksToXff
1631  && $ip !== null
1632  && !in_array( $ip, $wgProxyWhitelist )
1633  ) {
1634  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1635  $xff = array_map( 'trim', explode( ',', $xff ) );
1636  $xff = array_diff( $xff, [ $ip ] );
1637  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1638  $block = Block::chooseBlock( $xffblocks, $xff );
1639  if ( $block instanceof Block ) {
1640  # Mangle the reason to alert the user that the block
1641  # originated from matching the X-Forwarded-For header.
1642  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
1643  }
1644  }
1645 
1646  if ( $block instanceof Block ) {
1647  wfDebug( __METHOD__ . ": Found block.\n" );
1648  $this->mBlock = $block;
1649  $this->mBlockedby = $block->getByName();
1650  $this->mBlockreason = $block->mReason;
1651  $this->mHideName = $block->mHideName;
1652  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1653  } else {
1654  $this->mBlockedby = '';
1655  $this->mHideName = 0;
1656  $this->mAllowUsertalk = false;
1657  }
1658 
1659  // Extensions
1660  Hooks::run( 'GetBlockedStatus', [ &$this ] );
1661 
1662  }
1663 
1671  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1672  global $wgEnableDnsBlacklist, $wgDnsBlacklistUrls, $wgProxyWhitelist;
1673 
1674  if ( !$wgEnableDnsBlacklist ) {
1675  return false;
1676  }
1677 
1678  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
1679  return false;
1680  }
1681 
1682  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
1683  }
1684 
1692  public function inDnsBlacklist( $ip, $bases ) {
1693 
1694  $found = false;
1695  // @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
1696  if ( IP::isIPv4( $ip ) ) {
1697  // Reverse IP, bug 21255
1698  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1699 
1700  foreach ( (array)$bases as $base ) {
1701  // Make hostname
1702  // If we have an access key, use that too (ProjectHoneypot, etc.)
1703  $basename = $base;
1704  if ( is_array( $base ) ) {
1705  if ( count( $base ) >= 2 ) {
1706  // Access key is 1, base URL is 0
1707  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1708  } else {
1709  $host = "$ipReversed.{$base[0]}";
1710  }
1711  $basename = $base[0];
1712  } else {
1713  $host = "$ipReversed.$base";
1714  }
1715 
1716  // Send query
1717  $ipList = gethostbynamel( $host );
1718 
1719  if ( $ipList ) {
1720  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1721  $found = true;
1722  break;
1723  } else {
1724  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1725  }
1726  }
1727  }
1728 
1729  return $found;
1730  }
1731 
1739  public static function isLocallyBlockedProxy( $ip ) {
1740  global $wgProxyList;
1741 
1742  if ( !$wgProxyList ) {
1743  return false;
1744  }
1745 
1746  if ( !is_array( $wgProxyList ) ) {
1747  // Load from the specified file
1748  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1749  }
1750 
1751  if ( !is_array( $wgProxyList ) ) {
1752  $ret = false;
1753  } elseif ( array_search( $ip, $wgProxyList ) !== false ) {
1754  $ret = true;
1755  } elseif ( array_key_exists( $ip, $wgProxyList ) ) {
1756  // Old-style flipped proxy list
1757  $ret = true;
1758  } else {
1759  $ret = false;
1760  }
1761  return $ret;
1762  }
1763 
1769  public function isPingLimitable() {
1770  global $wgRateLimitsExcludedIPs;
1771  if ( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1772  // No other good way currently to disable rate limits
1773  // for specific IPs. :P
1774  // But this is a crappy hack and should die.
1775  return false;
1776  }
1777  return !$this->isAllowed( 'noratelimit' );
1778  }
1779 
1794  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1795  // Call the 'PingLimiter' hook
1796  $result = false;
1797  if ( !Hooks::run( 'PingLimiter', [ &$this, $action, &$result, $incrBy ] ) ) {
1798  return $result;
1799  }
1800 
1801  global $wgRateLimits;
1802  if ( !isset( $wgRateLimits[$action] ) ) {
1803  return false;
1804  }
1805 
1806  $limits = array_merge(
1807  [ '&can-bypass' => true ],
1808  $wgRateLimits[$action]
1809  );
1810 
1811  // Some groups shouldn't trigger the ping limiter, ever
1812  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1813  return false;
1814  }
1815 
1816  $keys = [];
1817  $id = $this->getId();
1818  $userLimit = false;
1819  $isNewbie = $this->isNewbie();
1820 
1821  if ( $id == 0 ) {
1822  // limits for anons
1823  if ( isset( $limits['anon'] ) ) {
1824  $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1825  }
1826  } else {
1827  // limits for logged-in users
1828  if ( isset( $limits['user'] ) ) {
1829  $userLimit = $limits['user'];
1830  }
1831  // limits for newbie logged-in users
1832  if ( $isNewbie && isset( $limits['newbie'] ) ) {
1833  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
1834  }
1835  }
1836 
1837  // limits for anons and for newbie logged-in users
1838  if ( $isNewbie ) {
1839  // ip-based limits
1840  if ( isset( $limits['ip'] ) ) {
1841  $ip = $this->getRequest()->getIP();
1842  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1843  }
1844  // subnet-based limits
1845  if ( isset( $limits['subnet'] ) ) {
1846  $ip = $this->getRequest()->getIP();
1847  $subnet = IP::getSubnet( $ip );
1848  if ( $subnet !== false ) {
1849  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1850  }
1851  }
1852  }
1853 
1854  // Check for group-specific permissions
1855  // If more than one group applies, use the group with the highest limit ratio (max/period)
1856  foreach ( $this->getGroups() as $group ) {
1857  if ( isset( $limits[$group] ) ) {
1858  if ( $userLimit === false
1859  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1860  ) {
1861  $userLimit = $limits[$group];
1862  }
1863  }
1864  }
1865 
1866  // Set the user limit key
1867  if ( $userLimit !== false ) {
1868  list( $max, $period ) = $userLimit;
1869  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
1870  $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit;
1871  }
1872 
1873  // ip-based limits for all ping-limitable users
1874  if ( isset( $limits['ip-all'] ) ) {
1875  $ip = $this->getRequest()->getIP();
1876  // ignore if user limit is more permissive
1877  if ( $isNewbie || $userLimit === false
1878  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1879  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
1880  }
1881  }
1882 
1883  // subnet-based limits for all ping-limitable users
1884  if ( isset( $limits['subnet-all'] ) ) {
1885  $ip = $this->getRequest()->getIP();
1886  $subnet = IP::getSubnet( $ip );
1887  if ( $subnet !== false ) {
1888  // ignore if user limit is more permissive
1889  if ( $isNewbie || $userLimit === false
1890  || $limits['ip-all'][0] / $limits['ip-all'][1]
1891  > $userLimit[0] / $userLimit[1] ) {
1892  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
1893  }
1894  }
1895  }
1896 
1898 
1899  $triggered = false;
1900  foreach ( $keys as $key => $limit ) {
1901  list( $max, $period ) = $limit;
1902  $summary = "(limit $max in {$period}s)";
1903  $count = $cache->get( $key );
1904  // Already pinged?
1905  if ( $count ) {
1906  if ( $count >= $max ) {
1907  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
1908  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
1909  $triggered = true;
1910  } else {
1911  wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
1912  }
1913  } else {
1914  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
1915  if ( $incrBy > 0 ) {
1916  $cache->add( $key, 0, intval( $period ) ); // first ping
1917  }
1918  }
1919  if ( $incrBy > 0 ) {
1920  $cache->incr( $key, $incrBy );
1921  }
1922  }
1923 
1924  return $triggered;
1925  }
1926 
1934  public function isBlocked( $bFromSlave = true ) {
1935  return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
1936  }
1937 
1944  public function getBlock( $bFromSlave = true ) {
1945  $this->getBlockedStatus( $bFromSlave );
1946  return $this->mBlock instanceof Block ? $this->mBlock : null;
1947  }
1948 
1956  public function isBlockedFrom( $title, $bFromSlave = false ) {
1957  global $wgBlockAllowsUTEdit;
1958 
1959  $blocked = $this->isBlocked( $bFromSlave );
1960  $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
1961  // If a user's name is suppressed, they cannot make edits anywhere
1962  if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
1963  && $title->getNamespace() == NS_USER_TALK ) {
1964  $blocked = false;
1965  wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
1966  }
1967 
1968  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
1969 
1970  return $blocked;
1971  }
1972 
1977  public function blockedBy() {
1978  $this->getBlockedStatus();
1979  return $this->mBlockedby;
1980  }
1981 
1986  public function blockedFor() {
1987  $this->getBlockedStatus();
1988  return $this->mBlockreason;
1989  }
1990 
1995  public function getBlockId() {
1996  $this->getBlockedStatus();
1997  return ( $this->mBlock ? $this->mBlock->getId() : false );
1998  }
1999 
2008  public function isBlockedGlobally( $ip = '' ) {
2009  return $this->getGlobalBlock( $ip ) instanceof Block;
2010  }
2011 
2022  public function getGlobalBlock( $ip = '' ) {
2023  if ( $this->mGlobalBlock !== null ) {
2024  return $this->mGlobalBlock ?: null;
2025  }
2026  // User is already an IP?
2027  if ( IP::isIPAddress( $this->getName() ) ) {
2028  $ip = $this->getName();
2029  } elseif ( !$ip ) {
2030  $ip = $this->getRequest()->getIP();
2031  }
2032  $blocked = false;
2033  $block = null;
2034  Hooks::run( 'UserIsBlockedGlobally', [ &$this, $ip, &$blocked, &$block ] );
2035 
2036  if ( $blocked && $block === null ) {
2037  // back-compat: UserIsBlockedGlobally didn't have $block param first
2038  $block = new Block;
2039  $block->setTarget( $ip );
2040  }
2041 
2042  $this->mGlobalBlock = $blocked ? $block : false;
2043  return $this->mGlobalBlock ?: null;
2044  }
2045 
2051  public function isLocked() {
2052  if ( $this->mLocked !== null ) {
2053  return $this->mLocked;
2054  }
2055  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2056  $this->mLocked = $authUser && $authUser->isLocked();
2057  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2058  return $this->mLocked;
2059  }
2060 
2066  public function isHidden() {
2067  if ( $this->mHideName !== null ) {
2068  return $this->mHideName;
2069  }
2070  $this->getBlockedStatus();
2071  if ( !$this->mHideName ) {
2072  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2073  $this->mHideName = $authUser && $authUser->isHidden();
2074  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2075  }
2076  return $this->mHideName;
2077  }
2078 
2083  public function getId() {
2084  if ( $this->mId === null && $this->mName !== null && User::isIP( $this->mName ) ) {
2085  // Special case, we know the user is anonymous
2086  return 0;
2087  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2088  // Don't load if this was initialized from an ID
2089  $this->load();
2090  }
2091 
2092  return (int)$this->mId;
2093  }
2094 
2099  public function setId( $v ) {
2100  $this->mId = $v;
2101  $this->clearInstanceCache( 'id' );
2102  }
2103 
2108  public function getName() {
2109  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2110  // Special case optimisation
2111  return $this->mName;
2112  } else {
2113  $this->load();
2114  if ( $this->mName === false ) {
2115  // Clean up IPs
2116  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2117  }
2118  return $this->mName;
2119  }
2120  }
2121 
2135  public function setName( $str ) {
2136  $this->load();
2137  $this->mName = $str;
2138  }
2139 
2144  public function getTitleKey() {
2145  return str_replace( ' ', '_', $this->getName() );
2146  }
2147 
2152  public function getNewtalk() {
2153  $this->load();
2154 
2155  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2156  if ( $this->mNewtalk === -1 ) {
2157  $this->mNewtalk = false; # reset talk page status
2158 
2159  // Check memcached separately for anons, who have no
2160  // entire User object stored in there.
2161  if ( !$this->mId ) {
2162  global $wgDisableAnonTalk;
2163  if ( $wgDisableAnonTalk ) {
2164  // Anon newtalk disabled by configuration.
2165  $this->mNewtalk = false;
2166  } else {
2167  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2168  }
2169  } else {
2170  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2171  }
2172  }
2173 
2174  return (bool)$this->mNewtalk;
2175  }
2176 
2190  public function getNewMessageLinks() {
2191  $talks = [];
2192  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$this, &$talks ] ) ) {
2193  return $talks;
2194  } elseif ( !$this->getNewtalk() ) {
2195  return [];
2196  }
2197  $utp = $this->getTalkPage();
2198  $dbr = wfGetDB( DB_REPLICA );
2199  // Get the "last viewed rev" timestamp from the oldest message notification
2200  $timestamp = $dbr->selectField( 'user_newtalk',
2201  'MIN(user_last_timestamp)',
2202  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2203  __METHOD__ );
2205  return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2206  }
2207 
2213  public function getNewMessageRevisionId() {
2214  $newMessageRevisionId = null;
2215  $newMessageLinks = $this->getNewMessageLinks();
2216  if ( $newMessageLinks ) {
2217  // Note: getNewMessageLinks() never returns more than a single link
2218  // and it is always for the same wiki, but we double-check here in
2219  // case that changes some time in the future.
2220  if ( count( $newMessageLinks ) === 1
2221  && $newMessageLinks[0]['wiki'] === wfWikiID()
2222  && $newMessageLinks[0]['rev']
2223  ) {
2225  $newMessageRevision = $newMessageLinks[0]['rev'];
2226  $newMessageRevisionId = $newMessageRevision->getId();
2227  }
2228  }
2229  return $newMessageRevisionId;
2230  }
2231 
2240  protected function checkNewtalk( $field, $id ) {
2241  $dbr = wfGetDB( DB_REPLICA );
2242 
2243  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2244 
2245  return $ok !== false;
2246  }
2247 
2255  protected function updateNewtalk( $field, $id, $curRev = null ) {
2256  // Get timestamp of the talk page revision prior to the current one
2257  $prevRev = $curRev ? $curRev->getPrevious() : false;
2258  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2259  // Mark the user as having new messages since this revision
2260  $dbw = wfGetDB( DB_MASTER );
2261  $dbw->insert( 'user_newtalk',
2262  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2263  __METHOD__,
2264  'IGNORE' );
2265  if ( $dbw->affectedRows() ) {
2266  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2267  return true;
2268  } else {
2269  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2270  return false;
2271  }
2272  }
2273 
2280  protected function deleteNewtalk( $field, $id ) {
2281  $dbw = wfGetDB( DB_MASTER );
2282  $dbw->delete( 'user_newtalk',
2283  [ $field => $id ],
2284  __METHOD__ );
2285  if ( $dbw->affectedRows() ) {
2286  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2287  return true;
2288  } else {
2289  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2290  return false;
2291  }
2292  }
2293 
2300  public function setNewtalk( $val, $curRev = null ) {
2301  if ( wfReadOnly() ) {
2302  return;
2303  }
2304 
2305  $this->load();
2306  $this->mNewtalk = $val;
2307 
2308  if ( $this->isAnon() ) {
2309  $field = 'user_ip';
2310  $id = $this->getName();
2311  } else {
2312  $field = 'user_id';
2313  $id = $this->getId();
2314  }
2315 
2316  if ( $val ) {
2317  $changed = $this->updateNewtalk( $field, $id, $curRev );
2318  } else {
2319  $changed = $this->deleteNewtalk( $field, $id );
2320  }
2321 
2322  if ( $changed ) {
2323  $this->invalidateCache();
2324  }
2325  }
2326 
2332  private function newTouchedTimestamp() {
2334 
2335  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2336  if ( $this->mTouched && $time <= $this->mTouched ) {
2337  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2338  }
2339 
2340  return $time;
2341  }
2342 
2353  public function clearSharedCache( $mode = 'changed' ) {
2354  if ( !$this->getId() ) {
2355  return;
2356  }
2357 
2359  $key = $this->getCacheKey( $cache );
2360  if ( $mode === 'refresh' ) {
2361  $cache->delete( $key, 1 );
2362  } else {
2363  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
2364  function() use ( $cache, $key ) {
2365  $cache->delete( $key );
2366  },
2367  __METHOD__
2368  );
2369  }
2370  }
2371 
2377  public function invalidateCache() {
2378  $this->touch();
2379  $this->clearSharedCache();
2380  }
2381 
2394  public function touch() {
2395  $id = $this->getId();
2396  if ( $id ) {
2397  $key = wfMemcKey( 'user-quicktouched', 'id', $id );
2398  ObjectCache::getMainWANInstance()->touchCheckKey( $key );
2399  $this->mQuickTouched = null;
2400  }
2401  }
2402 
2408  public function validateCache( $timestamp ) {
2409  return ( $timestamp >= $this->getTouched() );
2410  }
2411 
2420  public function getTouched() {
2421  $this->load();
2422 
2423  if ( $this->mId ) {
2424  if ( $this->mQuickTouched === null ) {
2425  $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
2427 
2428  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2429  }
2430 
2431  return max( $this->mTouched, $this->mQuickTouched );
2432  }
2433 
2434  return $this->mTouched;
2435  }
2436 
2442  public function getDBTouched() {
2443  $this->load();
2444 
2445  return $this->mTouched;
2446  }
2447 
2453  public function getPassword() {
2454  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2455  }
2456 
2462  public function getTemporaryPassword() {
2463  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2464  }
2465 
2482  public function setPassword( $str ) {
2483  return $this->setPasswordInternal( $str );
2484  }
2485 
2494  public function setInternalPassword( $str ) {
2495  $this->setPasswordInternal( $str );
2496  }
2497 
2506  private function setPasswordInternal( $str ) {
2507  $manager = AuthManager::singleton();
2508 
2509  // If the user doesn't exist yet, fail
2510  if ( !$manager->userExists( $this->getName() ) ) {
2511  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2512  }
2513 
2514  $status = $this->changeAuthenticationData( [
2515  'username' => $this->getName(),
2516  'password' => $str,
2517  'retype' => $str,
2518  ] );
2519  if ( !$status->isGood() ) {
2521  ->info( __METHOD__ . ': Password change rejected: '
2522  . $status->getWikiText( null, null, 'en' ) );
2523  return false;
2524  }
2525 
2526  $this->setOption( 'watchlisttoken', false );
2527  SessionManager::singleton()->invalidateSessionsForUser( $this );
2528 
2529  return true;
2530  }
2531 
2544  public function changeAuthenticationData( array $data ) {
2545  $manager = AuthManager::singleton();
2546  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2547  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2548 
2549  $status = Status::newGood( 'ignored' );
2550  foreach ( $reqs as $req ) {
2551  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2552  }
2553  if ( $status->getValue() === 'ignored' ) {
2554  $status->warning( 'authenticationdatachange-ignored' );
2555  }
2556 
2557  if ( $status->isGood() ) {
2558  foreach ( $reqs as $req ) {
2559  $manager->changeAuthenticationData( $req );
2560  }
2561  }
2562  return $status;
2563  }
2564 
2571  public function getToken( $forceCreation = true ) {
2572  global $wgAuthenticationTokenVersion;
2573 
2574  $this->load();
2575  if ( !$this->mToken && $forceCreation ) {
2576  $this->setToken();
2577  }
2578 
2579  if ( !$this->mToken ) {
2580  // The user doesn't have a token, return null to indicate that.
2581  return null;
2582  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2583  // We return a random value here so existing token checks are very
2584  // likely to fail.
2585  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2586  } elseif ( $wgAuthenticationTokenVersion === null ) {
2587  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2588  return $this->mToken;
2589  } else {
2590  // $wgAuthenticationTokenVersion in use, so hmac it.
2591  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2592 
2593  // The raw hash can be overly long. Shorten it up.
2594  $len = max( 32, self::TOKEN_LENGTH );
2595  if ( strlen( $ret ) < $len ) {
2596  // Should never happen, even md5 is 128 bits
2597  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2598  }
2599  return substr( $ret, -$len );
2600  }
2601  }
2602 
2609  public function setToken( $token = false ) {
2610  $this->load();
2611  if ( $this->mToken === self::INVALID_TOKEN ) {
2613  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2614  } elseif ( !$token ) {
2615  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2616  } else {
2617  $this->mToken = $token;
2618  }
2619  }
2620 
2629  public function setNewpassword( $str, $throttle = true ) {
2630  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2631  }
2632 
2639  public function isPasswordReminderThrottled() {
2640  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2641  }
2642 
2647  public function getEmail() {
2648  $this->load();
2649  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2650  return $this->mEmail;
2651  }
2652 
2658  $this->load();
2659  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2661  }
2662 
2667  public function setEmail( $str ) {
2668  $this->load();
2669  if ( $str == $this->mEmail ) {
2670  return;
2671  }
2672  $this->invalidateEmail();
2673  $this->mEmail = $str;
2674  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2675  }
2676 
2684  public function setEmailWithConfirmation( $str ) {
2686 
2687  if ( !$wgEnableEmail ) {
2688  return Status::newFatal( 'emaildisabled' );
2689  }
2690 
2691  $oldaddr = $this->getEmail();
2692  if ( $str === $oldaddr ) {
2693  return Status::newGood( true );
2694  }
2695 
2696  $type = $oldaddr != '' ? 'changed' : 'set';
2697  $notificationResult = null;
2698 
2699  if ( $wgEmailAuthentication ) {
2700  // Send the user an email notifying the user of the change in registered
2701  // email address on their previous email address
2702  if ( $type == 'changed' ) {
2703  $change = $str != '' ? 'changed' : 'removed';
2704  $notificationResult = $this->sendMail(
2705  wfMessage( 'notificationemail_subject_' . $change )->text(),
2706  wfMessage( 'notificationemail_body_' . $change,
2707  $this->getRequest()->getIP(),
2708  $this->getName(),
2709  $str )->text()
2710  );
2711  }
2712  }
2713 
2714  $this->setEmail( $str );
2715 
2716  if ( $str !== '' && $wgEmailAuthentication ) {
2717  // Send a confirmation request to the new address if needed
2718  $result = $this->sendConfirmationMail( $type );
2719 
2720  if ( $notificationResult !== null ) {
2721  $result->merge( $notificationResult );
2722  }
2723 
2724  if ( $result->isGood() ) {
2725  // Say to the caller that a confirmation and notification mail has been sent
2726  $result->value = 'eauth';
2727  }
2728  } else {
2729  $result = Status::newGood( true );
2730  }
2731 
2732  return $result;
2733  }
2734 
2739  public function getRealName() {
2740  if ( !$this->isItemLoaded( 'realname' ) ) {
2741  $this->load();
2742  }
2743 
2744  return $this->mRealName;
2745  }
2746 
2751  public function setRealName( $str ) {
2752  $this->load();
2753  $this->mRealName = $str;
2754  }
2755 
2766  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2767  global $wgHiddenPrefs;
2768  $this->loadOptions();
2769 
2770  # We want 'disabled' preferences to always behave as the default value for
2771  # users, even if they have set the option explicitly in their settings (ie they
2772  # set it, and then it was disabled removing their ability to change it). But
2773  # we don't want to erase the preferences in the database in case the preference
2774  # is re-enabled again. So don't touch $mOptions, just override the returned value
2775  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2776  return self::getDefaultOption( $oname );
2777  }
2778 
2779  if ( array_key_exists( $oname, $this->mOptions ) ) {
2780  return $this->mOptions[$oname];
2781  } else {
2782  return $defaultOverride;
2783  }
2784  }
2785 
2794  public function getOptions( $flags = 0 ) {
2795  global $wgHiddenPrefs;
2796  $this->loadOptions();
2798 
2799  # We want 'disabled' preferences to always behave as the default value for
2800  # users, even if they have set the option explicitly in their settings (ie they
2801  # set it, and then it was disabled removing their ability to change it). But
2802  # we don't want to erase the preferences in the database in case the preference
2803  # is re-enabled again. So don't touch $mOptions, just override the returned value
2804  foreach ( $wgHiddenPrefs as $pref ) {
2805  $default = self::getDefaultOption( $pref );
2806  if ( $default !== null ) {
2807  $options[$pref] = $default;
2808  }
2809  }
2810 
2811  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2812  $options = array_diff_assoc( $options, self::getDefaultOptions() );
2813  }
2814 
2815  return $options;
2816  }
2817 
2825  public function getBoolOption( $oname ) {
2826  return (bool)$this->getOption( $oname );
2827  }
2828 
2837  public function getIntOption( $oname, $defaultOverride = 0 ) {
2838  $val = $this->getOption( $oname );
2839  if ( $val == '' ) {
2840  $val = $defaultOverride;
2841  }
2842  return intval( $val );
2843  }
2844 
2853  public function setOption( $oname, $val ) {
2854  $this->loadOptions();
2855 
2856  // Explicitly NULL values should refer to defaults
2857  if ( is_null( $val ) ) {
2858  $val = self::getDefaultOption( $oname );
2859  }
2860 
2861  $this->mOptions[$oname] = $val;
2862  }
2863 
2874  public function getTokenFromOption( $oname ) {
2875  global $wgHiddenPrefs;
2876 
2877  $id = $this->getId();
2878  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2879  return false;
2880  }
2881 
2882  $token = $this->getOption( $oname );
2883  if ( !$token ) {
2884  // Default to a value based on the user token to avoid space
2885  // wasted on storing tokens for all users. When this option
2886  // is set manually by the user, only then is it stored.
2887  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2888  }
2889 
2890  return $token;
2891  }
2892 
2902  public function resetTokenFromOption( $oname ) {
2903  global $wgHiddenPrefs;
2904  if ( in_array( $oname, $wgHiddenPrefs ) ) {
2905  return false;
2906  }
2907 
2908  $token = MWCryptRand::generateHex( 40 );
2909  $this->setOption( $oname, $token );
2910  return $token;
2911  }
2912 
2936  public static function listOptionKinds() {
2937  return [
2938  'registered',
2939  'registered-multiselect',
2940  'registered-checkmatrix',
2941  'userjs',
2942  'special',
2943  'unused'
2944  ];
2945  }
2946 
2959  public function getOptionKinds( IContextSource $context, $options = null ) {
2960  $this->loadOptions();
2961  if ( $options === null ) {
2963  }
2964 
2965  $prefs = Preferences::getPreferences( $this, $context );
2966  $mapping = [];
2967 
2968  // Pull out the "special" options, so they don't get converted as
2969  // multiselect or checkmatrix.
2970  $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
2971  foreach ( $specialOptions as $name => $value ) {
2972  unset( $prefs[$name] );
2973  }
2974 
2975  // Multiselect and checkmatrix options are stored in the database with
2976  // one key per option, each having a boolean value. Extract those keys.
2977  $multiselectOptions = [];
2978  foreach ( $prefs as $name => $info ) {
2979  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
2980  ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
2981  $opts = HTMLFormField::flattenOptions( $info['options'] );
2982  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2983 
2984  foreach ( $opts as $value ) {
2985  $multiselectOptions["$prefix$value"] = true;
2986  }
2987 
2988  unset( $prefs[$name] );
2989  }
2990  }
2991  $checkmatrixOptions = [];
2992  foreach ( $prefs as $name => $info ) {
2993  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
2994  ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
2995  $columns = HTMLFormField::flattenOptions( $info['columns'] );
2996  $rows = HTMLFormField::flattenOptions( $info['rows'] );
2997  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2998 
2999  foreach ( $columns as $column ) {
3000  foreach ( $rows as $row ) {
3001  $checkmatrixOptions["$prefix$column-$row"] = true;
3002  }
3003  }
3004 
3005  unset( $prefs[$name] );
3006  }
3007  }
3008 
3009  // $value is ignored
3010  foreach ( $options as $key => $value ) {
3011  if ( isset( $prefs[$key] ) ) {
3012  $mapping[$key] = 'registered';
3013  } elseif ( isset( $multiselectOptions[$key] ) ) {
3014  $mapping[$key] = 'registered-multiselect';
3015  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3016  $mapping[$key] = 'registered-checkmatrix';
3017  } elseif ( isset( $specialOptions[$key] ) ) {
3018  $mapping[$key] = 'special';
3019  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3020  $mapping[$key] = 'userjs';
3021  } else {
3022  $mapping[$key] = 'unused';
3023  }
3024  }
3025 
3026  return $mapping;
3027  }
3028 
3043  public function resetOptions(
3044  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3045  IContextSource $context = null
3046  ) {
3047  $this->load();
3048  $defaultOptions = self::getDefaultOptions();
3049 
3050  if ( !is_array( $resetKinds ) ) {
3051  $resetKinds = [ $resetKinds ];
3052  }
3053 
3054  if ( in_array( 'all', $resetKinds ) ) {
3055  $newOptions = $defaultOptions;
3056  } else {
3057  if ( $context === null ) {
3059  }
3060 
3061  $optionKinds = $this->getOptionKinds( $context );
3062  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3063  $newOptions = [];
3064 
3065  // Use default values for the options that should be deleted, and
3066  // copy old values for the ones that shouldn't.
3067  foreach ( $this->mOptions as $key => $value ) {
3068  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3069  if ( array_key_exists( $key, $defaultOptions ) ) {
3070  $newOptions[$key] = $defaultOptions[$key];
3071  }
3072  } else {
3073  $newOptions[$key] = $value;
3074  }
3075  }
3076  }
3077 
3078  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3079 
3080  $this->mOptions = $newOptions;
3081  $this->mOptionsLoaded = true;
3082  }
3083 
3088  public function getDatePreference() {
3089  // Important migration for old data rows
3090  if ( is_null( $this->mDatePreference ) ) {
3091  global $wgLang;
3092  $value = $this->getOption( 'date' );
3093  $map = $wgLang->getDatePreferenceMigrationMap();
3094  if ( isset( $map[$value] ) ) {
3095  $value = $map[$value];
3096  }
3097  $this->mDatePreference = $value;
3098  }
3099  return $this->mDatePreference;
3100  }
3101 
3108  public function requiresHTTPS() {
3109  global $wgSecureLogin;
3110  if ( !$wgSecureLogin ) {
3111  return false;
3112  } else {
3113  $https = $this->getBoolOption( 'prefershttps' );
3114  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3115  if ( $https ) {
3116  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3117  }
3118  return $https;
3119  }
3120  }
3121 
3127  public function getStubThreshold() {
3128  global $wgMaxArticleSize; # Maximum article size, in Kb
3129  $threshold = $this->getIntOption( 'stubthreshold' );
3130  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3131  // If they have set an impossible value, disable the preference
3132  // so we can use the parser cache again.
3133  $threshold = 0;
3134  }
3135  return $threshold;
3136  }
3137 
3142  public function getRights() {
3143  if ( is_null( $this->mRights ) ) {
3144  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3145  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3146 
3147  // Deny any rights denied by the user's session, unless this
3148  // endpoint has no sessions.
3149  if ( !defined( 'MW_NO_SESSION' ) ) {
3150  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3151  if ( $allowedRights !== null ) {
3152  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3153  }
3154  }
3155 
3156  // Force reindexation of rights when a hook has unset one of them
3157  $this->mRights = array_values( array_unique( $this->mRights ) );
3158 
3159  // If block disables login, we should also remove any
3160  // extra rights blocked users might have, in case the
3161  // blocked user has a pre-existing session (T129738).
3162  // This is checked here for cases where people only call
3163  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3164  // to give a better error message in the common case.
3165  $config = RequestContext::getMain()->getConfig();
3166  if (
3167  $this->isLoggedIn() &&
3168  $config->get( 'BlockDisablesLogin' ) &&
3169  $this->isBlocked()
3170  ) {
3171  $anon = new User;
3172  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3173  }
3174  }
3175  return $this->mRights;
3176  }
3177 
3183  public function getGroups() {
3184  $this->load();
3185  $this->loadGroups();
3186  return $this->mGroups;
3187  }
3188 
3196  public function getEffectiveGroups( $recache = false ) {
3197  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3198  $this->mEffectiveGroups = array_unique( array_merge(
3199  $this->getGroups(), // explicit groups
3200  $this->getAutomaticGroups( $recache ) // implicit groups
3201  ) );
3202  // Hook for additional groups
3203  Hooks::run( 'UserEffectiveGroups', [ &$this, &$this->mEffectiveGroups ] );
3204  // Force reindexation of groups when a hook has unset one of them
3205  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3206  }
3207  return $this->mEffectiveGroups;
3208  }
3209 
3217  public function getAutomaticGroups( $recache = false ) {
3218  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3219  $this->mImplicitGroups = [ '*' ];
3220  if ( $this->getId() ) {
3221  $this->mImplicitGroups[] = 'user';
3222 
3223  $this->mImplicitGroups = array_unique( array_merge(
3224  $this->mImplicitGroups,
3226  ) );
3227  }
3228  if ( $recache ) {
3229  // Assure data consistency with rights/groups,
3230  // as getEffectiveGroups() depends on this function
3231  $this->mEffectiveGroups = null;
3232  }
3233  }
3234  return $this->mImplicitGroups;
3235  }
3236 
3246  public function getFormerGroups() {
3247  $this->load();
3248 
3249  if ( is_null( $this->mFormerGroups ) ) {
3250  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3251  ? wfGetDB( DB_MASTER )
3252  : wfGetDB( DB_REPLICA );
3253  $res = $db->select( 'user_former_groups',
3254  [ 'ufg_group' ],
3255  [ 'ufg_user' => $this->mId ],
3256  __METHOD__ );
3257  $this->mFormerGroups = [];
3258  foreach ( $res as $row ) {
3259  $this->mFormerGroups[] = $row->ufg_group;
3260  }
3261  }
3262 
3263  return $this->mFormerGroups;
3264  }
3265 
3270  public function getEditCount() {
3271  if ( !$this->getId() ) {
3272  return null;
3273  }
3274 
3275  if ( $this->mEditCount === null ) {
3276  /* Populate the count, if it has not been populated yet */
3277  $dbr = wfGetDB( DB_REPLICA );
3278  // check if the user_editcount field has been initialized
3279  $count = $dbr->selectField(
3280  'user', 'user_editcount',
3281  [ 'user_id' => $this->mId ],
3282  __METHOD__
3283  );
3284 
3285  if ( $count === null ) {
3286  // it has not been initialized. do so.
3287  $count = $this->initEditCount();
3288  }
3289  $this->mEditCount = $count;
3290  }
3291  return (int)$this->mEditCount;
3292  }
3293 
3300  public function addGroup( $group ) {
3301  $this->load();
3302 
3303  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group ] ) ) {
3304  return false;
3305  }
3306 
3307  $dbw = wfGetDB( DB_MASTER );
3308  if ( $this->getId() ) {
3309  $dbw->insert( 'user_groups',
3310  [
3311  'ug_user' => $this->getId(),
3312  'ug_group' => $group,
3313  ],
3314  __METHOD__,
3315  [ 'IGNORE' ] );
3316  }
3317 
3318  $this->loadGroups();
3319  $this->mGroups[] = $group;
3320  // In case loadGroups was not called before, we now have the right twice.
3321  // Get rid of the duplicate.
3322  $this->mGroups = array_unique( $this->mGroups );
3323 
3324  // Refresh the groups caches, and clear the rights cache so it will be
3325  // refreshed on the next call to $this->getRights().
3326  $this->getEffectiveGroups( true );
3327  $this->mRights = null;
3328 
3329  $this->invalidateCache();
3330 
3331  return true;
3332  }
3333 
3340  public function removeGroup( $group ) {
3341  $this->load();
3342  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3343  return false;
3344  }
3345 
3346  $dbw = wfGetDB( DB_MASTER );
3347  $dbw->delete( 'user_groups',
3348  [
3349  'ug_user' => $this->getId(),
3350  'ug_group' => $group,
3351  ], __METHOD__
3352  );
3353  // Remember that the user was in this group
3354  $dbw->insert( 'user_former_groups',
3355  [
3356  'ufg_user' => $this->getId(),
3357  'ufg_group' => $group,
3358  ],
3359  __METHOD__,
3360  [ 'IGNORE' ]
3361  );
3362 
3363  $this->loadGroups();
3364  $this->mGroups = array_diff( $this->mGroups, [ $group ] );
3365 
3366  // Refresh the groups caches, and clear the rights cache so it will be
3367  // refreshed on the next call to $this->getRights().
3368  $this->getEffectiveGroups( true );
3369  $this->mRights = null;
3370 
3371  $this->invalidateCache();
3372 
3373  return true;
3374  }
3375 
3380  public function isLoggedIn() {
3381  return $this->getId() != 0;
3382  }
3383 
3388  public function isAnon() {
3389  return !$this->isLoggedIn();
3390  }
3391 
3396  public function isBot() {
3397  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3398  return true;
3399  }
3400 
3401  $isBot = false;
3402  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3403 
3404  return $isBot;
3405  }
3406 
3413  public function isAllowedAny() {
3414  $permissions = func_get_args();
3415  foreach ( $permissions as $permission ) {
3416  if ( $this->isAllowed( $permission ) ) {
3417  return true;
3418  }
3419  }
3420  return false;
3421  }
3422 
3428  public function isAllowedAll() {
3429  $permissions = func_get_args();
3430  foreach ( $permissions as $permission ) {
3431  if ( !$this->isAllowed( $permission ) ) {
3432  return false;
3433  }
3434  }
3435  return true;
3436  }
3437 
3443  public function isAllowed( $action = '' ) {
3444  if ( $action === '' ) {
3445  return true; // In the spirit of DWIM
3446  }
3447  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3448  // by misconfiguration: 0 == 'foo'
3449  return in_array( $action, $this->getRights(), true );
3450  }
3451 
3456  public function useRCPatrol() {
3457  global $wgUseRCPatrol;
3458  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3459  }
3460 
3465  public function useNPPatrol() {
3466  global $wgUseRCPatrol, $wgUseNPPatrol;
3467  return (
3468  ( $wgUseRCPatrol || $wgUseNPPatrol )
3469  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3470  );
3471  }
3472 
3477  public function useFilePatrol() {
3478  global $wgUseRCPatrol, $wgUseFilePatrol;
3479  return (
3480  ( $wgUseRCPatrol || $wgUseFilePatrol )
3481  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3482  );
3483  }
3484 
3490  public function getRequest() {
3491  if ( $this->mRequest ) {
3492  return $this->mRequest;
3493  } else {
3495  return $wgRequest;
3496  }
3497  }
3498 
3507  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3508  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3509  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3510  }
3511  return false;
3512  }
3513 
3521  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3522  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3523  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3524  $this,
3525  [ $title->getSubjectPage(), $title->getTalkPage() ]
3526  );
3527  }
3528  $this->invalidateCache();
3529  }
3530 
3538  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3539  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3540  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3541  $store->removeWatch( $this, $title->getSubjectPage() );
3542  $store->removeWatch( $this, $title->getTalkPage() );
3543  }
3544  $this->invalidateCache();
3545  }
3546 
3555  public function clearNotification( &$title, $oldid = 0 ) {
3556  global $wgUseEnotif, $wgShowUpdatedMarker;
3557 
3558  // Do nothing if the database is locked to writes
3559  if ( wfReadOnly() ) {
3560  return;
3561  }
3562 
3563  // Do nothing if not allowed to edit the watchlist
3564  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3565  return;
3566  }
3567 
3568  // If we're working on user's talk page, we should update the talk page message indicator
3569  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3570  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$this, $oldid ] ) ) {
3571  return;
3572  }
3573 
3574  // Try to update the DB post-send and only if needed...
3575  DeferredUpdates::addCallableUpdate( function() use ( $title, $oldid ) {
3576  if ( !$this->getNewtalk() ) {
3577  return; // no notifications to clear
3578  }
3579 
3580  // Delete the last notifications (they stack up)
3581  $this->setNewtalk( false );
3582 
3583  // If there is a new, unseen, revision, use its timestamp
3584  $nextid = $oldid
3585  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3586  : null;
3587  if ( $nextid ) {
3588  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3589  }
3590  } );
3591  }
3592 
3593  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3594  return;
3595  }
3596 
3597  if ( $this->isAnon() ) {
3598  // Nothing else to do...
3599  return;
3600  }
3601 
3602  // Only update the timestamp if the page is being watched.
3603  // The query to find out if it is watched is cached both in memcached and per-invocation,
3604  // and when it does have to be executed, it can be on a replica DB
3605  // If this is the user's newtalk page, we always update the timestamp
3606  $force = '';
3607  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3608  $force = 'force';
3609  }
3610 
3611  MediaWikiServices::getInstance()->getWatchedItemStore()
3612  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3613  }
3614 
3621  public function clearAllNotifications() {
3622  global $wgUseEnotif, $wgShowUpdatedMarker;
3623  // Do nothing if not allowed to edit the watchlist
3624  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3625  return;
3626  }
3627 
3628  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3629  $this->setNewtalk( false );
3630  return;
3631  }
3632 
3633  $id = $this->getId();
3634  if ( !$id ) {
3635  return;
3636  }
3637 
3638  $dbw = wfGetDB( DB_MASTER );
3639  $asOfTimes = array_unique( $dbw->selectFieldValues(
3640  'watchlist',
3641  'wl_notificationtimestamp',
3642  [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3643  __METHOD__,
3644  [ 'ORDER BY' => 'wl_notificationtimestamp DESC', 'LIMIT' => 500 ]
3645  ) );
3646  if ( !$asOfTimes ) {
3647  return;
3648  }
3649  // Immediately update the most recent touched rows, which hopefully covers what
3650  // the user sees on the watchlist page before pressing "mark all pages visited"....
3651  $dbw->update(
3652  'watchlist',
3653  [ 'wl_notificationtimestamp' => null ],
3654  [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimes ],
3655  __METHOD__
3656  );
3657  // ...and finish the older ones in a post-send update with lag checks...
3659  $dbw,
3660  __METHOD__,
3661  function () use ( $dbw, $id ) {
3662  global $wgUpdateRowsPerQuery;
3663 
3664  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
3665  $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
3666  $asOfTimes = array_unique( $dbw->selectFieldValues(
3667  'watchlist',
3668  'wl_notificationtimestamp',
3669  [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3670  __METHOD__
3671  ) );
3672  foreach ( array_chunk( $asOfTimes, $wgUpdateRowsPerQuery ) as $asOfTimeBatch ) {
3673  $dbw->update(
3674  'watchlist',
3675  [ 'wl_notificationtimestamp' => null ],
3676  [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimeBatch ],
3677  __METHOD__
3678  );
3679  $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
3680  }
3681  }
3682  ) );
3683  // We also need to clear here the "you have new message" notification for the own
3684  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3685  }
3686 
3703  protected function setCookie(
3704  $name, $value, $exp = 0, $secure = null, $params = [], $request = null
3705  ) {
3706  wfDeprecated( __METHOD__, '1.27' );
3707  if ( $request === null ) {
3708  $request = $this->getRequest();
3709  }
3710  $params['secure'] = $secure;
3711  $request->response()->setCookie( $name, $value, $exp, $params );
3712  }
3713 
3724  protected function clearCookie( $name, $secure = null, $params = [] ) {
3725  wfDeprecated( __METHOD__, '1.27' );
3726  $this->setCookie( $name, '', time() - 86400, $secure, $params );
3727  }
3728 
3744  protected function setExtendedLoginCookie( $name, $value, $secure ) {
3745  global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
3746 
3747  wfDeprecated( __METHOD__, '1.27' );
3748 
3749  $exp = time();
3750  $exp += $wgExtendedLoginCookieExpiration !== null
3751  ? $wgExtendedLoginCookieExpiration
3752  : $wgCookieExpiration;
3753 
3754  $this->setCookie( $name, $value, $exp, $secure );
3755  }
3756 
3765  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3766  $this->load();
3767  if ( 0 == $this->mId ) {
3768  return;
3769  }
3770 
3771  $session = $this->getRequest()->getSession();
3772  if ( $request && $session->getRequest() !== $request ) {
3773  $session = $session->sessionWithRequest( $request );
3774  }
3775  $delay = $session->delaySave();
3776 
3777  if ( !$session->getUser()->equals( $this ) ) {
3778  if ( !$session->canSetUser() ) {
3780  ->warning( __METHOD__ .
3781  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3782  );
3783  return;
3784  }
3785  $session->setUser( $this );
3786  }
3787 
3788  $session->setRememberUser( $rememberMe );
3789  if ( $secure !== null ) {
3790  $session->setForceHTTPS( $secure );
3791  }
3792 
3793  $session->persist();
3794 
3795  ScopedCallback::consume( $delay );
3796  }
3797 
3801  public function logout() {
3802  if ( Hooks::run( 'UserLogout', [ &$this ] ) ) {
3803  $this->doLogout();
3804  }
3805  }
3806 
3811  public function doLogout() {
3812  $session = $this->getRequest()->getSession();
3813  if ( !$session->canSetUser() ) {
3815  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3816  $error = 'immutable';
3817  } elseif ( !$session->getUser()->equals( $this ) ) {
3819  ->warning( __METHOD__ .
3820  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3821  );
3822  // But we still may as well make this user object anon
3823  $this->clearInstanceCache( 'defaults' );
3824  $error = 'wronguser';
3825  } else {
3826  $this->clearInstanceCache( 'defaults' );
3827  $delay = $session->delaySave();
3828  $session->unpersist(); // Clear cookies (T127436)
3829  $session->setLoggedOutTimestamp( time() );
3830  $session->setUser( new User );
3831  $session->set( 'wsUserID', 0 ); // Other code expects this
3832  $session->resetAllTokens();
3833  ScopedCallback::consume( $delay );
3834  $error = false;
3835  }
3836  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3837  'event' => 'logout',
3838  'successful' => $error === false,
3839  'status' => $error ?: 'success',
3840  ] );
3841  }
3842 
3847  public function saveSettings() {
3848  if ( wfReadOnly() ) {
3849  // @TODO: caller should deal with this instead!
3850  // This should really just be an exception.
3852  null,
3853  "Could not update user with ID '{$this->mId}'; DB is read-only."
3854  ) );
3855  return;
3856  }
3857 
3858  $this->load();
3859  if ( 0 == $this->mId ) {
3860  return; // anon
3861  }
3862 
3863  // Get a new user_touched that is higher than the old one.
3864  // This will be used for a CAS check as a last-resort safety
3865  // check against race conditions and replica DB lag.
3866  $newTouched = $this->newTouchedTimestamp();
3867 
3868  $dbw = wfGetDB( DB_MASTER );
3869  $dbw->update( 'user',
3870  [ /* SET */
3871  'user_name' => $this->mName,
3872  'user_real_name' => $this->mRealName,
3873  'user_email' => $this->mEmail,
3874  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3875  'user_touched' => $dbw->timestamp( $newTouched ),
3876  'user_token' => strval( $this->mToken ),
3877  'user_email_token' => $this->mEmailToken,
3878  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3879  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
3880  'user_id' => $this->mId,
3881  ] ), __METHOD__
3882  );
3883 
3884  if ( !$dbw->affectedRows() ) {
3885  // Maybe the problem was a missed cache update; clear it to be safe
3886  $this->clearSharedCache( 'refresh' );
3887  // User was changed in the meantime or loaded with stale data
3888  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
3889  throw new MWException(
3890  "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
3891  " the version of the user to be saved is older than the current version."
3892  );
3893  }
3894 
3895  $this->mTouched = $newTouched;
3896  $this->saveOptions();
3897 
3898  Hooks::run( 'UserSaveSettings', [ $this ] );
3899  $this->clearSharedCache();
3900  $this->getUserPage()->invalidateCache();
3901  }
3902 
3909  public function idForName( $flags = 0 ) {
3910  $s = trim( $this->getName() );
3911  if ( $s === '' ) {
3912  return 0;
3913  }
3914 
3915  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
3916  ? wfGetDB( DB_MASTER )
3917  : wfGetDB( DB_REPLICA );
3918 
3919  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
3920  ? [ 'LOCK IN SHARE MODE' ]
3921  : [];
3922 
3923  $id = $db->selectField( 'user',
3924  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
3925 
3926  return (int)$id;
3927  }
3928 
3944  public static function createNew( $name, $params = [] ) {
3945  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
3946  if ( isset( $params[$field] ) ) {
3947  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
3948  unset( $params[$field] );
3949  }
3950  }
3951 
3952  $user = new User;
3953  $user->load();
3954  $user->setToken(); // init token
3955  if ( isset( $params['options'] ) ) {
3956  $user->mOptions = $params['options'] + (array)$user->mOptions;
3957  unset( $params['options'] );
3958  }
3959  $dbw = wfGetDB( DB_MASTER );
3960  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
3961 
3962  $noPass = PasswordFactory::newInvalidPassword()->toString();
3963 
3964  $fields = [
3965  'user_id' => $seqVal,
3966  'user_name' => $name,
3967  'user_password' => $noPass,
3968  'user_newpassword' => $noPass,
3969  'user_email' => $user->mEmail,
3970  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3971  'user_real_name' => $user->mRealName,
3972  'user_token' => strval( $user->mToken ),
3973  'user_registration' => $dbw->timestamp( $user->mRegistration ),
3974  'user_editcount' => 0,
3975  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3976  ];
3977  foreach ( $params as $name => $value ) {
3978  $fields["user_$name"] = $value;
3979  }
3980  $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
3981  if ( $dbw->affectedRows() ) {
3982  $newUser = User::newFromId( $dbw->insertId() );
3983  } else {
3984  $newUser = null;
3985  }
3986  return $newUser;
3987  }
3988 
4015  public function addToDatabase() {
4016  $this->load();
4017  if ( !$this->mToken ) {
4018  $this->setToken(); // init token
4019  }
4020 
4021  $this->mTouched = $this->newTouchedTimestamp();
4022 
4023  $noPass = PasswordFactory::newInvalidPassword()->toString();
4024 
4025  $dbw = wfGetDB( DB_MASTER );
4026  $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
4027  $dbw->insert( 'user',
4028  [
4029  'user_id' => $seqVal,
4030  'user_name' => $this->mName,
4031  'user_password' => $noPass,
4032  'user_newpassword' => $noPass,
4033  'user_email' => $this->mEmail,
4034  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4035  'user_real_name' => $this->mRealName,
4036  'user_token' => strval( $this->mToken ),
4037  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4038  'user_editcount' => 0,
4039  'user_touched' => $dbw->timestamp( $this->mTouched ),
4040  ], __METHOD__,
4041  [ 'IGNORE' ]
4042  );
4043  if ( !$dbw->affectedRows() ) {
4044  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4045  $this->mId = $dbw->selectField(
4046  'user',
4047  'user_id',
4048  [ 'user_name' => $this->mName ],
4049  __METHOD__,
4050  [ 'LOCK IN SHARE MODE' ]
4051  );
4052  $loaded = false;
4053  if ( $this->mId ) {
4054  if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4055  $loaded = true;
4056  }
4057  }
4058  if ( !$loaded ) {
4059  throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
4060  "to insert user '{$this->mName}' row, but it was not present in select!" );
4061  }
4062  return Status::newFatal( 'userexists' );
4063  }
4064  $this->mId = $dbw->insertId();
4065  self::$idCacheByName[$this->mName] = $this->mId;
4066 
4067  // Clear instance cache other than user table data, which is already accurate
4068  $this->clearInstanceCache();
4069 
4070  $this->saveOptions();
4071  return Status::newGood();
4072  }
4073 
4079  public function spreadAnyEditBlock() {
4080  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4081  return $this->spreadBlock();
4082  }
4083 
4084  return false;
4085  }
4086 
4092  protected function spreadBlock() {
4093  wfDebug( __METHOD__ . "()\n" );
4094  $this->load();
4095  if ( $this->mId == 0 ) {
4096  return false;
4097  }
4098 
4099  $userblock = Block::newFromTarget( $this->getName() );
4100  if ( !$userblock ) {
4101  return false;
4102  }
4103 
4104  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4105  }
4106 
4111  public function isBlockedFromCreateAccount() {
4112  $this->getBlockedStatus();
4113  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4114  return $this->mBlock;
4115  }
4116 
4117  # bug 13611: if the IP address the user is trying to create an account from is
4118  # blocked with createaccount disabled, prevent new account creation there even
4119  # when the user is logged in
4120  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4121  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4122  }
4123  return $this->mBlockedFromCreateAccount instanceof Block
4124  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4125  ? $this->mBlockedFromCreateAccount
4126  : false;
4127  }
4128 
4133  public function isBlockedFromEmailuser() {
4134  $this->getBlockedStatus();
4135  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4136  }
4137 
4142  public function isAllowedToCreateAccount() {
4143  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4144  }
4145 
4151  public function getUserPage() {
4152  return Title::makeTitle( NS_USER, $this->getName() );
4153  }
4154 
4160  public function getTalkPage() {
4161  $title = $this->getUserPage();
4162  return $title->getTalkPage();
4163  }
4164 
4170  public function isNewbie() {
4171  return !$this->isAllowed( 'autoconfirmed' );
4172  }
4173 
4180  public function checkPassword( $password ) {
4181  $manager = AuthManager::singleton();
4182  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4183  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4184  [
4185  'username' => $this->getName(),
4186  'password' => $password,
4187  ]
4188  );
4189  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4190  switch ( $res->status ) {
4191  case AuthenticationResponse::PASS:
4192  return true;
4193  case AuthenticationResponse::FAIL:
4194  // Hope it's not a PreAuthenticationProvider that failed...
4196  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4197  return false;
4198  default:
4199  throw new BadMethodCallException(
4200  'AuthManager returned a response unsupported by ' . __METHOD__
4201  );
4202  }
4203  }
4204 
4213  public function checkTemporaryPassword( $plaintext ) {
4214  // Can't check the temporary password individually.
4215  return $this->checkPassword( $plaintext );
4216  }
4217 
4229  public function getEditTokenObject( $salt = '', $request = null ) {
4230  if ( $this->isAnon() ) {
4231  return new LoggedOutEditToken();
4232  }
4233 
4234  if ( !$request ) {
4235  $request = $this->getRequest();
4236  }
4237  return $request->getSession()->getToken( $salt );
4238  }
4239 
4253  public function getEditToken( $salt = '', $request = null ) {
4254  return $this->getEditTokenObject( $salt, $request )->toString();
4255  }
4256 
4263  public static function getEditTokenTimestamp( $val ) {
4264  wfDeprecated( __METHOD__, '1.27' );
4266  }
4267 
4280  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4281  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4282  }
4283 
4294  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4295  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4296  return $this->matchEditToken( $val, $salt, $request, $maxage );
4297  }
4298 
4306  public function sendConfirmationMail( $type = 'created' ) {
4307  global $wgLang;
4308  $expiration = null; // gets passed-by-ref and defined in next line.
4309  $token = $this->confirmationToken( $expiration );
4310  $url = $this->confirmationTokenUrl( $token );
4311  $invalidateURL = $this->invalidationTokenUrl( $token );
4312  $this->saveSettings();
4313 
4314  if ( $type == 'created' || $type === false ) {
4315  $message = 'confirmemail_body';
4316  } elseif ( $type === true ) {
4317  $message = 'confirmemail_body_changed';
4318  } else {
4319  // Messages: confirmemail_body_changed, confirmemail_body_set
4320  $message = 'confirmemail_body_' . $type;
4321  }
4322 
4323  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4324  wfMessage( $message,
4325  $this->getRequest()->getIP(),
4326  $this->getName(),
4327  $url,
4328  $wgLang->userTimeAndDate( $expiration, $this ),
4329  $invalidateURL,
4330  $wgLang->userDate( $expiration, $this ),
4331  $wgLang->userTime( $expiration, $this ) )->text() );
4332  }
4333 
4345  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4347 
4348  if ( $from instanceof User ) {
4349  $sender = MailAddress::newFromUser( $from );
4350  } else {
4351  $sender = new MailAddress( $wgPasswordSender,
4352  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4353  }
4354  $to = MailAddress::newFromUser( $this );
4355 
4356  return UserMailer::send( $to, $sender, $subject, $body, [
4357  'replyTo' => $replyto,
4358  ] );
4359  }
4360 
4371  protected function confirmationToken( &$expiration ) {
4373  $now = time();
4374  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4375  $expiration = wfTimestamp( TS_MW, $expires );
4376  $this->load();
4377  $token = MWCryptRand::generateHex( 32 );
4378  $hash = md5( $token );
4379  $this->mEmailToken = $hash;
4380  $this->mEmailTokenExpires = $expiration;
4381  return $token;
4382  }
4383 
4389  protected function confirmationTokenUrl( $token ) {
4390  return $this->getTokenUrl( 'ConfirmEmail', $token );
4391  }
4392 
4398  protected function invalidationTokenUrl( $token ) {
4399  return $this->getTokenUrl( 'InvalidateEmail', $token );
4400  }
4401 
4416  protected function getTokenUrl( $page, $token ) {
4417  // Hack to bypass localization of 'Special:'
4418  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4419  return $title->getCanonicalURL();
4420  }
4421 
4429  public function confirmEmail() {
4430  // Check if it's already confirmed, so we don't touch the database
4431  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4432  if ( !$this->isEmailConfirmed() ) {
4434  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4435  }
4436  return true;
4437  }
4438 
4446  public function invalidateEmail() {
4447  $this->load();
4448  $this->mEmailToken = null;
4449  $this->mEmailTokenExpires = null;
4450  $this->setEmailAuthenticationTimestamp( null );
4451  $this->mEmail = '';
4452  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4453  return true;
4454  }
4455 
4461  $this->load();
4462  $this->mEmailAuthenticated = $timestamp;
4463  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4464  }
4465 
4471  public function canSendEmail() {
4473  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4474  return false;
4475  }
4476  $canSend = $this->isEmailConfirmed();
4477  Hooks::run( 'UserCanSendEmail', [ &$this, &$canSend ] );
4478  return $canSend;
4479  }
4480 
4486  public function canReceiveEmail() {
4487  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4488  }
4489 
4500  public function isEmailConfirmed() {
4502  $this->load();
4503  $confirmed = true;
4504  if ( Hooks::run( 'EmailConfirmed', [ &$this, &$confirmed ] ) ) {
4505  if ( $this->isAnon() ) {
4506  return false;
4507  }
4508  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4509  return false;
4510  }
4511  if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4512  return false;
4513  }
4514  return true;
4515  } else {
4516  return $confirmed;
4517  }
4518  }
4519 
4524  public function isEmailConfirmationPending() {
4526  return $wgEmailAuthentication &&
4527  !$this->isEmailConfirmed() &&
4528  $this->mEmailToken &&
4529  $this->mEmailTokenExpires > wfTimestamp();
4530  }
4531 
4539  public function getRegistration() {
4540  if ( $this->isAnon() ) {
4541  return false;
4542  }
4543  $this->load();
4544  return $this->mRegistration;
4545  }
4546 
4553  public function getFirstEditTimestamp() {
4554  if ( $this->getId() == 0 ) {
4555  return false; // anons
4556  }
4557  $dbr = wfGetDB( DB_REPLICA );
4558  $time = $dbr->selectField( 'revision', 'rev_timestamp',
4559  [ 'rev_user' => $this->getId() ],
4560  __METHOD__,
4561  [ 'ORDER BY' => 'rev_timestamp ASC' ]
4562  );
4563  if ( !$time ) {
4564  return false; // no edits
4565  }
4566  return wfTimestamp( TS_MW, $time );
4567  }
4568 
4575  public static function getGroupPermissions( $groups ) {
4576  global $wgGroupPermissions, $wgRevokePermissions;
4577  $rights = [];
4578  // grant every granted permission first
4579  foreach ( $groups as $group ) {
4580  if ( isset( $wgGroupPermissions[$group] ) ) {
4581  $rights = array_merge( $rights,
4582  // array_filter removes empty items
4583  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4584  }
4585  }
4586  // now revoke the revoked permissions
4587  foreach ( $groups as $group ) {
4588  if ( isset( $wgRevokePermissions[$group] ) ) {
4589  $rights = array_diff( $rights,
4590  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4591  }
4592  }
4593  return array_unique( $rights );
4594  }
4595 
4602  public static function getGroupsWithPermission( $role ) {
4603  global $wgGroupPermissions;
4604  $allowedGroups = [];
4605  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4606  if ( self::groupHasPermission( $group, $role ) ) {
4607  $allowedGroups[] = $group;
4608  }
4609  }
4610  return $allowedGroups;
4611  }
4612 
4625  public static function groupHasPermission( $group, $role ) {
4626  global $wgGroupPermissions, $wgRevokePermissions;
4627  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
4628  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
4629  }
4630 
4645  public static function isEveryoneAllowed( $right ) {
4646  global $wgGroupPermissions, $wgRevokePermissions;
4647  static $cache = [];
4648 
4649  // Use the cached results, except in unit tests which rely on
4650  // being able change the permission mid-request
4651  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
4652  return $cache[$right];
4653  }
4654 
4655  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
4656  $cache[$right] = false;
4657  return false;
4658  }
4659 
4660  // If it's revoked anywhere, then everyone doesn't have it
4661  foreach ( $wgRevokePermissions as $rights ) {
4662  if ( isset( $rights[$right] ) && $rights[$right] ) {
4663  $cache[$right] = false;
4664  return false;
4665  }
4666  }
4667 
4668  // Remove any rights that aren't allowed to the global-session user,
4669  // unless there are no sessions for this endpoint.
4670  if ( !defined( 'MW_NO_SESSION' ) ) {
4671  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
4672  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
4673  $cache[$right] = false;
4674  return false;
4675  }
4676  }
4677 
4678  // Allow extensions to say false
4679  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
4680  $cache[$right] = false;
4681  return false;
4682  }
4683 
4684  $cache[$right] = true;
4685  return true;
4686  }
4687 
4694  public static function getGroupName( $group ) {
4695  $msg = wfMessage( "group-$group" );
4696  return $msg->isBlank() ? $group : $msg->text();
4697  }
4698 
4706  public static function getGroupMember( $group, $username = '#' ) {
4707  $msg = wfMessage( "group-$group-member", $username );
4708  return $msg->isBlank() ? $group : $msg->text();
4709  }
4710 
4717  public static function getAllGroups() {
4718  global $wgGroupPermissions, $wgRevokePermissions;
4719  return array_diff(
4720  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4721  self::getImplicitGroups()
4722  );
4723  }
4724 
4729  public static function getAllRights() {
4730  if ( self::$mAllRights === false ) {
4731  global $wgAvailableRights;
4732  if ( count( $wgAvailableRights ) ) {
4733  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
4734  } else {
4735  self::$mAllRights = self::$mCoreRights;
4736  }
4737  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
4738  }
4739  return self::$mAllRights;
4740  }
4741 
4746  public static function getImplicitGroups() {
4747  global $wgImplicitGroups;
4748 
4749  $groups = $wgImplicitGroups;
4750  # Deprecated, use $wgImplicitGroups instead
4751  Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
4752 
4753  return $groups;
4754  }
4755 
4762  public static function getGroupPage( $group ) {
4763  $msg = wfMessage( 'grouppage-' . $group )->inContentLanguage();
4764  if ( $msg->exists() ) {
4765  $title = Title::newFromText( $msg->text() );
4766  if ( is_object( $title ) ) {
4767  return $title;
4768  }
4769  }
4770  return false;
4771  }
4772 
4781  public static function makeGroupLinkHTML( $group, $text = '' ) {
4782  if ( $text == '' ) {
4783  $text = self::getGroupName( $group );
4784  }
4785  $title = self::getGroupPage( $group );
4786  if ( $title ) {
4787  return Linker::link( $title, htmlspecialchars( $text ) );
4788  } else {
4789  return htmlspecialchars( $text );
4790  }
4791  }
4792 
4801  public static function makeGroupLinkWiki( $group, $text = '' ) {
4802  if ( $text == '' ) {
4803  $text = self::getGroupName( $group );
4804  }
4805  $title = self::getGroupPage( $group );
4806  if ( $title ) {
4807  $page = $title->getFullText();
4808  return "[[$page|$text]]";
4809  } else {
4810  return $text;
4811  }
4812  }
4813 
4823  public static function changeableByGroup( $group ) {
4824  global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
4825 
4826  $groups = [
4827  'add' => [],
4828  'remove' => [],
4829  'add-self' => [],
4830  'remove-self' => []
4831  ];
4832 
4833  if ( empty( $wgAddGroups[$group] ) ) {
4834  // Don't add anything to $groups
4835  } elseif ( $wgAddGroups[$group] === true ) {
4836  // You get everything
4837  $groups['add'] = self::getAllGroups();
4838  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4839  $groups['add'] = $wgAddGroups[$group];
4840  }
4841 
4842  // Same thing for remove
4843  if ( empty( $wgRemoveGroups[$group] ) ) {
4844  // Do nothing
4845  } elseif ( $wgRemoveGroups[$group] === true ) {
4846  $groups['remove'] = self::getAllGroups();
4847  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4848  $groups['remove'] = $wgRemoveGroups[$group];
4849  }
4850 
4851  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4852  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4853  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4854  if ( is_int( $key ) ) {
4855  $wgGroupsAddToSelf['user'][] = $value;
4856  }
4857  }
4858  }
4859 
4860  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4861  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4862  if ( is_int( $key ) ) {
4863  $wgGroupsRemoveFromSelf['user'][] = $value;
4864  }
4865  }
4866  }
4867 
4868  // Now figure out what groups the user can add to him/herself
4869  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4870  // Do nothing
4871  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4872  // No idea WHY this would be used, but it's there
4873  $groups['add-self'] = User::getAllGroups();
4874  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4875  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4876  }
4877 
4878  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4879  // Do nothing
4880  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4881  $groups['remove-self'] = User::getAllGroups();
4882  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4883  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4884  }
4885 
4886  return $groups;
4887  }
4888 
4896  public function changeableGroups() {
4897  if ( $this->isAllowed( 'userrights' ) ) {
4898  // This group gives the right to modify everything (reverse-
4899  // compatibility with old "userrights lets you change
4900  // everything")
4901  // Using array_merge to make the groups reindexed
4902  $all = array_merge( User::getAllGroups() );
4903  return [
4904  'add' => $all,
4905  'remove' => $all,
4906  'add-self' => [],
4907  'remove-self' => []
4908  ];
4909  }
4910 
4911  // Okay, it's not so simple, we will have to go through the arrays
4912  $groups = [
4913  'add' => [],
4914  'remove' => [],
4915  'add-self' => [],
4916  'remove-self' => []
4917  ];
4918  $addergroups = $this->getEffectiveGroups();
4919 
4920  foreach ( $addergroups as $addergroup ) {
4921  $groups = array_merge_recursive(
4922  $groups, $this->changeableByGroup( $addergroup )
4923  );
4924  $groups['add'] = array_unique( $groups['add'] );
4925  $groups['remove'] = array_unique( $groups['remove'] );
4926  $groups['add-self'] = array_unique( $groups['add-self'] );
4927  $groups['remove-self'] = array_unique( $groups['remove-self'] );
4928  }
4929  return $groups;
4930  }
4931 
4935  public function incEditCount() {
4936  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
4937  function () {
4938  $this->incEditCountImmediate();
4939  },
4940  __METHOD__
4941  );
4942  }
4943 
4949  public function incEditCountImmediate() {
4950  if ( $this->isAnon() ) {
4951  return;
4952  }
4953 
4954  $dbw = wfGetDB( DB_MASTER );
4955  // No rows will be "affected" if user_editcount is NULL
4956  $dbw->update(
4957  'user',
4958  [ 'user_editcount=user_editcount+1' ],
4959  [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
4960  __METHOD__
4961  );
4962  // Lazy initialization check...
4963  if ( $dbw->affectedRows() == 0 ) {
4964  // Now here's a goddamn hack...
4965  $dbr = wfGetDB( DB_REPLICA );
4966  if ( $dbr !== $dbw ) {
4967  // If we actually have a replica DB server, the count is
4968  // at least one behind because the current transaction
4969  // has not been committed and replicated.
4970  $this->mEditCount = $this->initEditCount( 1 );
4971  } else {
4972  // But if DB_REPLICA is selecting the master, then the
4973  // count we just read includes the revision that was
4974  // just added in the working transaction.
4975  $this->mEditCount = $this->initEditCount();
4976  }
4977  } else {
4978  if ( $this->mEditCount === null ) {
4979  $this->getEditCount();
4980  $dbr = wfGetDB( DB_REPLICA );
4981  $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
4982  } else {
4983  $this->mEditCount++;
4984  }
4985  }
4986  // Edit count in user cache too
4987  $this->invalidateCache();
4988  }
4989 
4996  protected function initEditCount( $add = 0 ) {
4997  // Pull from a replica DB to be less cruel to servers
4998  // Accuracy isn't the point anyway here
4999  $dbr = wfGetDB( DB_REPLICA );
5000  $count = (int)$dbr->selectField(
5001  'revision',
5002  'COUNT(rev_user)',
5003  [ 'rev_user' => $this->getId() ],
5004  __METHOD__
5005  );
5006  $count = $count + $add;
5007 
5008  $dbw = wfGetDB( DB_MASTER );
5009  $dbw->update(
5010  'user',
5011  [ 'user_editcount' => $count ],
5012  [ 'user_id' => $this->getId() ],
5013  __METHOD__
5014  );
5015 
5016  return $count;
5017  }
5018 
5025  public static function getRightDescription( $right ) {
5026  $key = "right-$right";
5027  $msg = wfMessage( $key );
5028  return $msg->isBlank() ? $right : $msg->text();
5029  }
5030 
5040  public static function crypt( $password, $salt = false ) {
5041  wfDeprecated( __METHOD__, '1.24' );
5042  $passwordFactory = new PasswordFactory();
5043  $passwordFactory->init( RequestContext::getMain()->getConfig() );
5044  $hash = $passwordFactory->newFromPlaintext( $password );
5045  return $hash->toString();
5046  }
5047 
5059  public static function comparePasswords( $hash, $password, $userId = false ) {
5060  wfDeprecated( __METHOD__, '1.24' );
5061 
5062  // Check for *really* old password hashes that don't even have a type
5063  // The old hash format was just an md5 hex hash, with no type information
5064  if ( preg_match( '/^[0-9a-f]{32}$/', $hash ) ) {
5065  global $wgPasswordSalt;
5066  if ( $wgPasswordSalt ) {
5067  $password = ":B:{$userId}:{$hash}";
5068  } else {
5069  $password = ":A:{$hash}";
5070  }
5071  }
5072 
5073  $passwordFactory = new PasswordFactory();
5074  $passwordFactory->init( RequestContext::getMain()->getConfig() );
5075  $hash = $passwordFactory->newFromCiphertext( $hash );
5076  return $hash->equals( $password );
5077  }
5078 
5099  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5100  return true; // disabled
5101  }
5102 
5111  public function addNewUserLogEntryAutoCreate() {
5112  $this->addNewUserLogEntry( 'autocreate' );
5113 
5114  return true;
5115  }
5116 
5122  protected function loadOptions( $data = null ) {
5124 
5125  $this->load();
5126 
5127  if ( $this->mOptionsLoaded ) {
5128  return;
5129  }
5130 
5131  $this->mOptions = self::getDefaultOptions();
5132 
5133  if ( !$this->getId() ) {
5134  // For unlogged-in users, load language/variant options from request.
5135  // There's no need to do it for logged-in users: they can set preferences,
5136  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5137  // so don't override user's choice (especially when the user chooses site default).
5138  $variant = $wgContLang->getDefaultVariant();
5139  $this->mOptions['variant'] = $variant;
5140  $this->mOptions['language'] = $variant;
5141  $this->mOptionsLoaded = true;
5142  return;
5143  }
5144 
5145  // Maybe load from the object
5146  if ( !is_null( $this->mOptionOverrides ) ) {
5147  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5148  foreach ( $this->mOptionOverrides as $key => $value ) {
5149  $this->mOptions[$key] = $value;
5150  }
5151  } else {
5152  if ( !is_array( $data ) ) {
5153  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5154  // Load from database
5155  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5156  ? wfGetDB( DB_MASTER )
5157  : wfGetDB( DB_REPLICA );
5158 
5159  $res = $dbr->select(
5160  'user_properties',
5161  [ 'up_property', 'up_value' ],
5162  [ 'up_user' => $this->getId() ],
5163  __METHOD__
5164  );
5165 
5166  $this->mOptionOverrides = [];
5167  $data = [];
5168  foreach ( $res as $row ) {
5169  $data[$row->up_property] = $row->up_value;
5170  }
5171  }
5172  foreach ( $data as $property => $value ) {
5173  $this->mOptionOverrides[$property] = $value;
5174  $this->mOptions[$property] = $value;
5175  }
5176  }
5177 
5178  $this->mOptionsLoaded = true;
5179 
5180  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5181  }
5182 
5188  protected function saveOptions() {
5189  $this->loadOptions();
5190 
5191  // Not using getOptions(), to keep hidden preferences in database
5192  $saveOptions = $this->mOptions;
5193 
5194  // Allow hooks to abort, for instance to save to a global profile.
5195  // Reset options to default state before saving.
5196  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5197  return;
5198  }
5199 
5200  $userId = $this->getId();
5201 
5202  $insert_rows = []; // all the new preference rows
5203  foreach ( $saveOptions as $key => $value ) {
5204  // Don't bother storing default values
5205  $defaultOption = self::getDefaultOption( $key );
5206  if ( ( $defaultOption === null && $value !== false && $value !== null )
5207  || $value != $defaultOption
5208  ) {
5209  $insert_rows[] = [
5210  'up_user' => $userId,
5211  'up_property' => $key,
5212  'up_value' => $value,
5213  ];
5214  }
5215  }
5216 
5217  $dbw = wfGetDB( DB_MASTER );
5218 
5219  $res = $dbw->select( 'user_properties',
5220  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5221 
5222  // Find prior rows that need to be removed or updated. These rows will
5223  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5224  $keysDelete = [];
5225  foreach ( $res as $row ) {
5226  if ( !isset( $saveOptions[$row->up_property] )
5227  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5228  ) {
5229  $keysDelete[] = $row->up_property;
5230  }
5231  }
5232 
5233  if ( count( $keysDelete ) ) {
5234  // Do the DELETE by PRIMARY KEY for prior rows.
5235  // In the past a very large portion of calls to this function are for setting
5236  // 'rememberpassword' for new accounts (a preference that has since been removed).
5237  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5238  // caused gap locks on [max user ID,+infinity) which caused high contention since
5239  // updates would pile up on each other as they are for higher (newer) user IDs.
5240  // It might not be necessary these days, but it shouldn't hurt either.
5241  $dbw->delete( 'user_properties',
5242  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5243  }
5244  // Insert the new preference rows
5245  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5246  }
5247 
5254  public static function getPasswordFactory() {
5255  wfDeprecated( __METHOD__, '1.27' );
5256  $ret = new PasswordFactory();
5257  $ret->init( RequestContext::getMain()->getConfig() );
5258  return $ret;
5259  }
5260 
5285  public static function passwordChangeInputAttribs() {
5286  global $wgMinimalPasswordLength;
5287 
5288  if ( $wgMinimalPasswordLength == 0 ) {
5289  return [];
5290  }
5291 
5292  # Note that the pattern requirement will always be satisfied if the
5293  # input is empty, so we need required in all cases.
5294 
5295  # @todo FIXME: Bug 23769: This needs to not claim the password is required
5296  # if e-mail confirmation is being used. Since HTML5 input validation
5297  # is b0rked anyway in some browsers, just return nothing. When it's
5298  # re-enabled, fix this code to not output required for e-mail
5299  # registration.
5300  # $ret = array( 'required' );
5301  $ret = [];
5302 
5303  # We can't actually do this right now, because Opera 9.6 will print out
5304  # the entered password visibly in its error message! When other
5305  # browsers add support for this attribute, or Opera fixes its support,
5306  # we can add support with a version check to avoid doing this on Opera
5307  # versions where it will be a problem. Reported to Opera as
5308  # DSK-262266, but they don't have a public bug tracker for us to follow.
5309  /*
5310  if ( $wgMinimalPasswordLength > 1 ) {
5311  $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}';
5312  $ret['title'] = wfMessage( 'passwordtooshort' )
5313  ->numParams( $wgMinimalPasswordLength )->text();
5314  }
5315  */
5316 
5317  return $ret;
5318  }
5319 
5325  public static function selectFields() {
5326  return [
5327  'user_id',
5328  'user_name',
5329  'user_real_name',
5330  'user_email',
5331  'user_touched',
5332  'user_token',
5333  'user_email_authenticated',
5334  'user_email_token',
5335  'user_email_token_expires',
5336  'user_registration',
5337  'user_editcount',
5338  ];
5339  }
5340 
5348  static function newFatalPermissionDeniedStatus( $permission ) {
5349  global $wgLang;
5350 
5351  $groups = array_map(
5352  [ 'User', 'makeGroupLinkWiki' ],
5353  User::getGroupsWithPermission( $permission )
5354  );
5355 
5356  if ( $groups ) {
5357  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5358  } else {
5359  return Status::newFatal( 'badaccess-group0' );
5360  }
5361  }
5362 
5372  public function getInstanceForUpdate() {
5373  if ( !$this->getId() ) {
5374  return null; // anon
5375  }
5376 
5377  $user = self::newFromId( $this->getId() );
5378  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5379  return null;
5380  }
5381 
5382  return $user;
5383  }
5384 
5392  public function equals( User $user ) {
5393  return $this->getName() === $user->getName();
5394  }
5395 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:525
addAutopromoteOnceGroups($event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1392
getEmail()
Get the user's e-mail address.
Definition: User.php:2647
static randomPassword()
Return a random password.
Definition: User.php:1117
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
isHidden()
Check if user account is hidden.
Definition: User.php:2066
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition: User.php:1442
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:708
string $mBlockedby
Definition: User.php:265
const VERSION
int Serialized record version.
Definition: User.php:69
static getMainWANInstance()
Get the main WAN cache object.
setBlocker($user)
Set the user who implemented (or will implement) this block.
Definition: Block.php:1416
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:2639
static newFromRow($row, $data=null)
Create a new user object from a user row.
Definition: User.php:612
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:2825
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2190
isAllowedAll()
Definition: User.php:3428
string $mDatePreference
Definition: User.php:263
the array() calling protocol came about after MediaWiki 1.4rc1.
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4170
static whoIsReal($id)
Get the real name of a user given their user ID.
Definition: User.php:718
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:4294
$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:4263
isBlockedFrom($title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition: User.php:1956
$context
Definition: load.php:50
checkPassword($password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4180
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1203
clearInstanceCache($reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1498
loadFromSession()
Load user data from the session.
Definition: User.php:1189
const NS_MAIN
Definition: Defines.php:56
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4142
$success
Block $mBlockedFromCreateAccount
Definition: User.php:299
isValidPassword($password)
Is the input a valid password for this user?
Definition: User.php:958
logout()
Log this user out.
Definition: User.php:3801
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3555
isDnsBlacklisted($ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1671
static getImplicitGroups()
Get a list of implicit groups.
Definition: User.php:4746
saveSettings()
Save this user's settings into the database.
Definition: User.php:3847
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:1739
load($flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:358
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4553
static $mCoreRights
Array of Strings Core rights.
Definition: User.php:118
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:1940
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:75
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3413
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4133
if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:664
static getCanonicalName($name, $validate= 'valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1046
static newFatal($message)
Factory function for fatal errors.
Definition: StatusValue.php:63
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:3039
setCookie($name, $value, $exp=0, $secure=null, $params=[], $request=null)
Set a cookie on the user's client.
Definition: User.php:3703
getAutomaticGroups($recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3217
pingLimiter($action= 'edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:1794
const TOKEN_LENGTH
int Number of characters in user_token field.
Definition: User.php:52
clearSharedCache($mode= 'changed')
Clear user data from memcached.
Definition: User.php:2353
loadFromUserObject($user)
Load the data for this user object from another user object.
Definition: User.php:1352
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2394
deleteNewtalk($field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2280
updateNewtalk($field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2255
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:2008
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:1977
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:216
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN boolean columns are always mapped to as the code does not always treat the column as a boolean(which is limited to accepting true, false, 0, 1, t, or f)*The default data type for all VARCHAR
timestamp($ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:2908
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:323
static isUsableName($name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:853
static $idCacheByName
Definition: User.php:304
null for the local wiki Added in
Definition: hooks.txt:1559
getRealName()
Get the user's real name.
Definition: User.php:2739
checkPasswordValidity($password, $purpose= 'login')
Check if this is a valid password for this user.
Definition: User.php:1006
static findUsersByGroup($groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:888
$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:1460
static newFromId($id)
Static factory method for creation from a given user ID.
Definition: User.php:548
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4896
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3621
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3380
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:2707
getTemporaryPassword()
Definition: User.php:2462
checkNewtalk($field, $id)
Internal uncached check for new messages.
Definition: User.php:2240
setPassword($str)
Set the password and reset the random token.
Definition: User.php:2482
static createNew($name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:3944
invalidationTokenUrl($token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4398
addNewUserLogEntry($action=false, $reason= '')
Add a newuser log entry for this user.
Definition: User.php:5099
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:768
setName($str)
Set the user name.
Definition: User.php:2135
array $mFormerGroups
Definition: User.php:277
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2442
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:262
static isIPv6($ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:90
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
static getPreferences($user, IContextSource $context)
Definition: Preferences.php:83
getOptions($flags=0)
Get all user's options.
Definition: User.php:2794
matchEditToken($val, $salt= '', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4280
setToken($token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2609
static isIPAddress($ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:79
Value object representing a logged-out user's edit token.
const DB_MASTER
Definition: defines.php:23
static makeGroupLinkHTML($group, $text= '')
Create a link to the group in HTML, if available; else return the group name.
Definition: User.php:4781
static makeGroupLinkWiki($group, $text= '')
Create a link to the group in Wikitext, if available; else return the group name. ...
Definition: User.php:4801
setOption($oname, $val)
Set the given option for a user.
Definition: User.php:2853
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2108
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5325
string $mName
Cache variables.
Definition: User.php:207
string $mRegistration
Cache variables.
Definition: User.php:226
$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:2684
int $mEditCount
Cache variables.
Definition: User.php:228
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:2544
static isEveryoneAllowed($right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4645
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:1938
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2144
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4717
resetTokenFromOption($oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:2902
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
Definition: defines.php:6
loadOptions($data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5122
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:2936
getIntOption($oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2837
Deferrable Update for closure/callback updates that should use auto-commit mode.
sendMail($subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4345
static send($to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Definition: UserMailer.php:114
string $mEmailTokenExpires
Cache variables.
Definition: User.php:224
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4729
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4500
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:451
static isValidUserName($name)
Is the input a valid username?
Definition: User.php:804
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:1940
static edits($uid)
Count the number of edits of a user.
Definition: User.php:1105
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4111
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:2495
setNewpassword($str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:2629
static crypt($password, $salt=false)
Make a new-style password hash.
Definition: User.php:5040
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:1130
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:239
getBlock($bFromSlave=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1944
sendConfirmationMail($type= 'created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4306
string $mEmailToken
Cache variables.
Definition: User.php:222
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an article
Definition: design.txt:25
setEmailAuthenticationTimestamp($timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4460
static comparePasswords($hash, $password, $userId=false)
Compare a password hash with a plain-text password.
Definition: User.php:5059
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4539
wfGetLB($wiki=false)
Get a load balancer object.
wfReadOnly()
Check whether the wiki is in read-only mode.
static getMain()
Static methods.
setNewtalk($val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2300
static groupHasPermission($group, $role)
Check, if the given group has the given permission.
Definition: User.php:4625
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:5111
getPassword()
Definition: User.php:2453
static changeableByGroup($group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4823
isAllowed($action= '')
Internal mechanics of testing a permission.
Definition: User.php:3443
isItemLoaded($item, $all= 'all')
Return whether an item has been loaded.
Definition: User.php:1168
Block $mBlock
Definition: User.php:293
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"&lt
string $mBlockreason
Definition: User.php:271
isAnon()
Get whether the user is anonymous.
Definition: User.php:3388
inDnsBlacklist($ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1692
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:3043
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:1082
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:1050
validateCache($timestamp)
Validate the cache for this account.
Definition: User.php:2408
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:301
$wgFullyInitialised
Definition: Setup.php:880
$summary
static getSaveBlacklist()
Definition: Preferences.php:73
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3811
integer $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:302
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:472
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4524
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:51
incEditCount()
Deferred version of incEditCountImmediate()
Definition: User.php:4935
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2332
setTarget($target)
Set the target for this block, and update $this->type accordingly.
Definition: Block.php:1400
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:2959
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:653
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:214
clearCookie($name, $secure=null, $params=[])
Clear a cookie on the user's client.
Definition: User.php:3724
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration? ...
Definition: User.php:4471
array $mEffectiveGroups
Definition: User.php:273
$cache
Definition: mcc.php:33
const IGNORE_USER_RIGHTS
Definition: User.php:85
$params
string $mEmailAuthenticated
Cache variables.
Definition: User.php:220
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.
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: defines.php:11
WebRequest $mRequest
Definition: User.php:290
static makeTitleSafe($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:535
static isIP($name)
Does the string match an anonymous IP address?
Definition: User.php:788
getBlockedStatus($bFromSlave=true)
Get blocking information.
Definition: User.php:1578
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:4762
getPasswordValidity($password)
Given unvalidated password input, return error message on failure.
Definition: User.php:969
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:957
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3108
array $mImplicitGroups
Definition: User.php:275
static newGood($value=null)
Factory function for good results.
Definition: StatusValue.php:76
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:200
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
static getDBOptions($bitfield)
Get an appropriate DB index, options, and fallback DB index 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:4092
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...
isBot()
Definition: User.php:3396
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1725
getTokenUrl($page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4416
setInternalPassword($str)
Set the password and reset the random token unconditionally.
Definition: User.php:2494
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:806
loadFromDatabase($flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1220
string $mToken
Cache variables.
Definition: User.php:218
isWatched($title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3507
static newInvalidPassword()
Create an InvalidPassword.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:246
static link($target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php: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:3456
$wgPasswordSender
Sender email address for e-mail notifications.
isBlocked($bFromSlave=true)
Check if user is blocked.
Definition: User.php:1934
array $mOptions
Definition: User.php:285
getOption($oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2766
static getGroupName($group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:4694
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:261
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4446
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1362
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5188
idForName($flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:3909
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:5285
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:110
setId($v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2099
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3127
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3490
$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:212
setExtendedLoginCookie($name, $value, $secure)
Set an extended login cookie on the user's client.
Definition: User.php:3744
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition: Block.php:1122
this hook is for auditing only $req
Definition: hooks.txt:1011
getNewtalk()
Check if the user has new messages.
Definition: User.php:2152
bool $mLocked
Definition: User.php:281
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:806
static newFromConfirmationCode($code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:567
loadFromRow($row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1267
$wgUseEnotif
Definition: Setup.php:343
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3183
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:316
static getGroupMember($group, $username= '#')
Get the localized descriptive name for a member of a group, if it exists.
Definition: User.php:4706
array $mGroups
Cache variables.
Definition: User.php:230
confirmationTokenUrl($token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4389
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:2577
removeGroup($group)
Remove the user from the given group.
Definition: User.php:3340
$lbFactory
prevents($action, $x=null)
Get/set whether the Block prevents a given action.
Definition: Block.php:982
static hasFlags($bitfield, $flags)
The ContentHandler facility adds support for arbitrary content types on wiki pages
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from...
Definition: User.php:4079
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3477
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:3196
getId()
Get the user's ID.
Definition: User.php:2083
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2657
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4015
bool $mAllowUsertalk
Definition: User.php:296
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2377
getTokenFromOption($oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:2874
removeWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3538
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4429
getGlobalBlock($ip= '')
Check if user is blocked on all wikis.
Definition: User.php:2022
static generateHex($chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format...
Definition: MWCryptRand.php:76
setEmail($str)
Set the user's e-mail address.
Definition: User.php:2667
initEditCount($add=0)
Initialize user_editcount from data out of the revision table.
Definition: User.php:4996
bool $mHideName
Definition: User.php:283
static getPasswordFactory()
Lazily instantiate and return a factory object for making passwords.
Definition: User.php:5254
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:928
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:591
getEditCount()
Get the user's edit count.
Definition: User.php:3270
getUserPage()
Get this user's personal page title.
Definition: User.php:4151
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5372
static idFromName($name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:728
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3465
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:1050
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:3765
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3088
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:1995
array $mRights
Definition: User.php:269
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2213
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:256
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:1050
$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:4253
static newFatalPermissionDeniedStatus($permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5348
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:4229
isLocked()
Check if user account is locked.
Definition: User.php:2051
$messages
wfMemcKey()
Make a cache key for the local wiki.
string $mHash
Definition: User.php:267
static validateEmail($addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1942
static addCallableUpdate($callable, $stage=self::POSTSEND, IDatabase $dbw=null)
Add a callable update.
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4486
const DB_REPLICA
Definition: defines.php:22
equals(User $user)
Checks if two user objects point to the same user.
Definition: User.php:5392
loadFromId($flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:421
setRealName($str)
Set the user's real name.
Definition: User.php:2751
addWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3521
array $mOptionOverrides
Cache variables.
Definition: User.php:232
checkTemporaryPassword($plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4213
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:1523
const INVALID_TOKEN
string An invalid value for user_token
Definition: User.php:57
setItemLoaded($item)
Set that an item has been loaded.
Definition: User.php:1178
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:1563
string $mRealName
Cache variables.
Definition: User.php:209
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:341
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:730
getCacheKey(WANObjectCache $cache)
Definition: User.php:462
static getGroupPermissions($groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4575
const NS_USER_TALK
Definition: Defines.php:59
static array $languagesWithVariants
languages supporting variants
getTalkPage()
Get this user's talk page title.
Definition: User.php:4160
static flattenOptions($options)
flatten an array of options to a single array, for instance, a set of "" inside "...
getToken($forceCreation=true)
Get the user's current token.
Definition: User.php:2571
const EDIT_TOKEN_SUFFIX
Global constant made accessible as class constants so that autoloader magic can be used...
Definition: User.php:64
Definition: Block.php:25
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1769
static getRightDescription($right)
Get the description of a given right.
Definition: User.php:5025
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:2495
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:2495
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:511
if($wgRCFilterByAge) $wgDefaultUserOptions['rcdays']
Definition: Setup.php:282
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:93
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1753
const CHECK_USER_RIGHTS
Definition: User.php:80
static getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4602
getRights()
Get the permissions this user has.
Definition: User.php:3142
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3246
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:1986
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:2495
setPasswordInternal($str)
Actually set the password and such.
Definition: User.php:2506
incEditCountImmediate()
Increment the user's edit-count field.
Definition: User.php:4949
int $mId
Cache variables.
Definition: User.php:205
addGroup($group)
Add the user to the given group.
Definition: User.php:3300
$wgUser
Definition: Setup.php:806
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4371
getTouched()
Get the user touched timestamp.
Definition: User.php:2420
Block $mGlobalBlock
Definition: User.php:279
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:244
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304