MediaWiki  REL1_31
User.php
Go to the documentation of this file.
1 <?php
30 use Wikimedia\IPSet;
31 use Wikimedia\ScopedCallback;
35 
41 define( 'EDIT_TOKEN_SUFFIX', Token::SUFFIX );
42 
53 class User implements IDBAccessObject, UserIdentity {
57  const TOKEN_LENGTH = 32;
58 
62  const INVALID_TOKEN = '*** INVALID ***';
63 
70 
74  const VERSION = 12;
75 
81 
85  const CHECK_USER_RIGHTS = true;
86 
90  const IGNORE_USER_RIGHTS = false;
91 
98  protected static $mCacheVars = [
99  // user table
100  'mId',
101  'mName',
102  'mRealName',
103  'mEmail',
104  'mTouched',
105  'mToken',
106  'mEmailAuthenticated',
107  'mEmailToken',
108  'mEmailTokenExpires',
109  'mRegistration',
110  'mEditCount',
111  // user_groups table
112  'mGroupMemberships',
113  // user_properties table
114  'mOptionOverrides',
115  // actor table
116  'mActorId',
117  ];
118 
125  protected static $mCoreRights = [
126  'apihighlimits',
127  'applychangetags',
128  'autoconfirmed',
129  'autocreateaccount',
130  'autopatrol',
131  'bigdelete',
132  'block',
133  'blockemail',
134  'bot',
135  'browsearchive',
136  'changetags',
137  'createaccount',
138  'createpage',
139  'createtalk',
140  'delete',
141  'deletechangetags',
142  'deletedhistory',
143  'deletedtext',
144  'deletelogentry',
145  'deleterevision',
146  'edit',
147  'editcontentmodel',
148  'editinterface',
149  'editprotected',
150  'editmyoptions',
151  'editmyprivateinfo',
152  'editmyusercss',
153  'editmyuserjson',
154  'editmyuserjs',
155  'editmywatchlist',
156  'editsemiprotected',
157  'editusercss',
158  'edituserjson',
159  'edituserjs',
160  'hideuser',
161  'import',
162  'importupload',
163  'ipblock-exempt',
164  'managechangetags',
165  'markbotedits',
166  'mergehistory',
167  'minoredit',
168  'move',
169  'movefile',
170  'move-categorypages',
171  'move-rootuserpages',
172  'move-subpages',
173  'nominornewtalk',
174  'noratelimit',
175  'override-export-depth',
176  'pagelang',
177  'patrol',
178  'patrolmarks',
179  'protect',
180  'purge',
181  'read',
182  'reupload',
183  'reupload-own',
184  'reupload-shared',
185  'rollback',
186  'sendemail',
187  'siteadmin',
188  'suppressionlog',
189  'suppressredirect',
190  'suppressrevision',
191  'unblockself',
192  'undelete',
193  'unwatchedpages',
194  'upload',
195  'upload_by_url',
196  'userrights',
197  'userrights-interwiki',
198  'viewmyprivateinfo',
199  'viewmywatchlist',
200  'viewsuppressed',
201  'writeapi',
202  ];
203 
207  protected static $mAllRights = false;
208 
210  // @{
212  public $mId;
214  public $mName;
216  protected $mActorId;
218  public $mRealName;
219 
221  public $mEmail;
223  public $mTouched;
225  protected $mQuickTouched;
227  protected $mToken;
231  protected $mEmailToken;
235  protected $mRegistration;
237  protected $mEditCount;
241  protected $mOptionOverrides;
242  // @}
243 
247  // @{
249 
253  protected $mLoadedItems = [];
254  // @}
255 
266  public $mFrom;
267 
271  protected $mNewtalk;
273  protected $mDatePreference;
275  public $mBlockedby;
277  protected $mHash;
279  public $mRights;
281  protected $mBlockreason;
283  protected $mEffectiveGroups;
285  protected $mImplicitGroups;
287  protected $mFormerGroups;
289  protected $mGlobalBlock;
291  protected $mLocked;
293  public $mHideName;
295  public $mOptions;
296 
298  private $mRequest;
299 
301  public $mBlock;
302 
304  protected $mAllowUsertalk;
305 
307  private $mBlockedFromCreateAccount = false;
308 
310  protected $queryFlagsUsed = self::READ_NORMAL;
311 
312  public static $idCacheByName = [];
313 
325  public function __construct() {
326  $this->clearInstanceCache( 'defaults' );
327  }
328 
332  public function __toString() {
333  return (string)$this->getName();
334  }
335 
350  public function isSafeToLoad() {
352 
353  // The user is safe to load if:
354  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
355  // * mLoadedItems === true (already loaded)
356  // * mFrom !== 'session' (sessions not involved at all)
357 
358  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
359  $this->mLoadedItems === true || $this->mFrom !== 'session';
360  }
361 
367  public function load( $flags = self::READ_NORMAL ) {
369 
370  if ( $this->mLoadedItems === true ) {
371  return;
372  }
373 
374  // Set it now to avoid infinite recursion in accessors
375  $oldLoadedItems = $this->mLoadedItems;
376  $this->mLoadedItems = true;
377  $this->queryFlagsUsed = $flags;
378 
379  // If this is called too early, things are likely to break.
380  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
382  ->warning( 'User::loadFromSession called before the end of Setup.php', [
383  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
384  ] );
385  $this->loadDefaults();
386  $this->mLoadedItems = $oldLoadedItems;
387  return;
388  }
389 
390  switch ( $this->mFrom ) {
391  case 'defaults':
392  $this->loadDefaults();
393  break;
394  case 'name':
395  // Make sure this thread sees its own changes
396  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
397  if ( $lb->hasOrMadeRecentMasterChanges() ) {
398  $flags |= self::READ_LATEST;
399  $this->queryFlagsUsed = $flags;
400  }
401 
402  $this->mId = self::idFromName( $this->mName, $flags );
403  if ( !$this->mId ) {
404  // Nonexistent user placeholder object
405  $this->loadDefaults( $this->mName );
406  } else {
407  $this->loadFromId( $flags );
408  }
409  break;
410  case 'id':
411  // Make sure this thread sees its own changes, if the ID isn't 0
412  if ( $this->mId != 0 ) {
413  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
414  if ( $lb->hasOrMadeRecentMasterChanges() ) {
415  $flags |= self::READ_LATEST;
416  $this->queryFlagsUsed = $flags;
417  }
418  }
419 
420  $this->loadFromId( $flags );
421  break;
422  case 'actor':
423  // Make sure this thread sees its own changes
424  if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
425  $flags |= self::READ_LATEST;
426  $this->queryFlagsUsed = $flags;
427  }
428 
429  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
430  $row = wfGetDB( $index )->selectRow(
431  'actor',
432  [ 'actor_user', 'actor_name' ],
433  [ 'actor_id' => $this->mActorId ],
434  __METHOD__,
435  $options
436  );
437 
438  if ( !$row ) {
439  // Ugh.
440  $this->loadDefaults();
441  } elseif ( $row->actor_user ) {
442  $this->mId = $row->actor_user;
443  $this->loadFromId( $flags );
444  } else {
445  $this->loadDefaults( $row->actor_name );
446  }
447  break;
448  case 'session':
449  if ( !$this->loadFromSession() ) {
450  // Loading from session failed. Load defaults.
451  $this->loadDefaults();
452  }
453  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
454  break;
455  default:
456  throw new UnexpectedValueException(
457  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
458  }
459  }
460 
466  public function loadFromId( $flags = self::READ_NORMAL ) {
467  if ( $this->mId == 0 ) {
468  // Anonymous users are not in the database (don't need cache)
469  $this->loadDefaults();
470  return false;
471  }
472 
473  // Try cache (unless this needs data from the master DB).
474  // NOTE: if this thread called saveSettings(), the cache was cleared.
475  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
476  if ( $latest ) {
477  if ( !$this->loadFromDatabase( $flags ) ) {
478  // Can't load from ID
479  return false;
480  }
481  } else {
482  $this->loadFromCache();
483  }
484 
485  $this->mLoadedItems = true;
486  $this->queryFlagsUsed = $flags;
487 
488  return true;
489  }
490 
496  public static function purge( $wikiId, $userId ) {
498  $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
499  $cache->delete( $key );
500  }
501 
507  protected function getCacheKey( WANObjectCache $cache ) {
508  return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId );
509  }
510 
517  $id = $this->getId();
518 
519  return $id ? [ $this->getCacheKey( $cache ) ] : [];
520  }
521 
528  protected function loadFromCache() {
530  $data = $cache->getWithSetCallback(
531  $this->getCacheKey( $cache ),
532  $cache::TTL_HOUR,
533  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
534  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
535  wfDebug( "User: cache miss for user {$this->mId}\n" );
536 
537  $this->loadFromDatabase( self::READ_NORMAL );
538  $this->loadGroups();
539  $this->loadOptions();
540 
541  $data = [];
542  foreach ( self::$mCacheVars as $name ) {
543  $data[$name] = $this->$name;
544  }
545 
546  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
547 
548  // if a user group membership is about to expire, the cache needs to
549  // expire at that time (T163691)
550  foreach ( $this->mGroupMemberships as $ugm ) {
551  if ( $ugm->getExpiry() ) {
552  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
553  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
554  $ttl = $secondsUntilExpiry;
555  }
556  }
557  }
558 
559  return $data;
560  },
561  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
562  );
563 
564  // Restore from cache
565  foreach ( self::$mCacheVars as $name ) {
566  $this->$name = $data[$name];
567  }
568 
569  return true;
570  }
571 
573  // @{
574 
591  public static function newFromName( $name, $validate = 'valid' ) {
592  if ( $validate === true ) {
593  $validate = 'valid';
594  }
595  $name = self::getCanonicalName( $name, $validate );
596  if ( $name === false ) {
597  return false;
598  } else {
599  // Create unloaded user object
600  $u = new User;
601  $u->mName = $name;
602  $u->mFrom = 'name';
603  $u->setItemLoaded( 'name' );
604  return $u;
605  }
606  }
607 
614  public static function newFromId( $id ) {
615  $u = new User;
616  $u->mId = $id;
617  $u->mFrom = 'id';
618  $u->setItemLoaded( 'id' );
619  return $u;
620  }
621 
629  public static function newFromActorId( $id ) {
631 
633  throw new BadMethodCallException(
634  'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage is MIGRATION_OLD'
635  );
636  }
637 
638  $u = new User;
639  $u->mActorId = $id;
640  $u->mFrom = 'actor';
641  $u->setItemLoaded( 'actor' );
642  return $u;
643  }
644 
657  public static function newFromAnyId( $userId, $userName, $actorId ) {
659 
660  $user = new User;
661  $user->mFrom = 'defaults';
662 
663  if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD && $actorId !== null ) {
664  $user->mActorId = (int)$actorId;
665  if ( $user->mActorId !== 0 ) {
666  $user->mFrom = 'actor';
667  }
668  $user->setItemLoaded( 'actor' );
669  }
670 
671  if ( $userName !== null && $userName !== '' ) {
672  $user->mName = $userName;
673  $user->mFrom = 'name';
674  $user->setItemLoaded( 'name' );
675  }
676 
677  if ( $userId !== null ) {
678  $user->mId = (int)$userId;
679  if ( $user->mId !== 0 ) {
680  $user->mFrom = 'id';
681  }
682  $user->setItemLoaded( 'id' );
683  }
684 
685  if ( $user->mFrom === 'defaults' ) {
686  throw new InvalidArgumentException(
687  'Cannot create a user with no name, no ID, and no actor ID'
688  );
689  }
690 
691  return $user;
692  }
693 
705  public static function newFromConfirmationCode( $code, $flags = 0 ) {
706  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
707  ? wfGetDB( DB_MASTER )
708  : wfGetDB( DB_REPLICA );
709 
710  $id = $db->selectField(
711  'user',
712  'user_id',
713  [
714  'user_email_token' => md5( $code ),
715  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
716  ]
717  );
718 
719  return $id ? self::newFromId( $id ) : null;
720  }
721 
729  public static function newFromSession( WebRequest $request = null ) {
730  $user = new User;
731  $user->mFrom = 'session';
732  $user->mRequest = $request;
733  return $user;
734  }
735 
750  public static function newFromRow( $row, $data = null ) {
751  $user = new User;
752  $user->loadFromRow( $row, $data );
753  return $user;
754  }
755 
791  public static function newSystemUser( $name, $options = [] ) {
792  $options += [
793  'validate' => 'valid',
794  'create' => true,
795  'steal' => false,
796  ];
797 
798  $name = self::getCanonicalName( $name, $options['validate'] );
799  if ( $name === false ) {
800  return null;
801  }
802 
803  $dbr = wfGetDB( DB_REPLICA );
804  $userQuery = self::getQueryInfo();
805  $row = $dbr->selectRow(
806  $userQuery['tables'],
807  $userQuery['fields'],
808  [ 'user_name' => $name ],
809  __METHOD__,
810  [],
811  $userQuery['joins']
812  );
813  if ( !$row ) {
814  // Try the master database...
815  $dbw = wfGetDB( DB_MASTER );
816  $row = $dbw->selectRow(
817  $userQuery['tables'],
818  $userQuery['fields'],
819  [ 'user_name' => $name ],
820  __METHOD__,
821  [],
822  $userQuery['joins']
823  );
824  }
825 
826  if ( !$row ) {
827  // No user. Create it?
828  return $options['create']
829  ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
830  : null;
831  }
832 
833  $user = self::newFromRow( $row );
834 
835  // A user is considered to exist as a non-system user if it can
836  // authenticate, or has an email set, or has a non-invalid token.
837  if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
838  AuthManager::singleton()->userCanAuthenticate( $name )
839  ) {
840  // User exists. Steal it?
841  if ( !$options['steal'] ) {
842  return null;
843  }
844 
845  AuthManager::singleton()->revokeAccessForUser( $name );
846 
847  $user->invalidateEmail();
848  $user->mToken = self::INVALID_TOKEN;
849  $user->saveSettings();
850  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
851  }
852 
853  return $user;
854  }
855 
856  // @}
857 
863  public static function whoIs( $id ) {
864  return UserCache::singleton()->getProp( $id, 'name' );
865  }
866 
873  public static function whoIsReal( $id ) {
874  return UserCache::singleton()->getProp( $id, 'real_name' );
875  }
876 
883  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
885  if ( is_null( $nt ) ) {
886  // Illegal name
887  return null;
888  }
889 
890  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
891  return self::$idCacheByName[$name];
892  }
893 
894  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
895  $db = wfGetDB( $index );
896 
897  $s = $db->selectRow(
898  'user',
899  [ 'user_id' ],
900  [ 'user_name' => $nt->getText() ],
901  __METHOD__,
902  $options
903  );
904 
905  if ( $s === false ) {
906  $result = null;
907  } else {
908  $result = $s->user_id;
909  }
910 
911  self::$idCacheByName[$name] = $result;
912 
913  if ( count( self::$idCacheByName ) > 1000 ) {
914  self::$idCacheByName = [];
915  }
916 
917  return $result;
918  }
919 
923  public static function resetIdByNameCache() {
924  self::$idCacheByName = [];
925  }
926 
943  public static function isIP( $name ) {
944  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
945  || IP::isIPv6( $name );
946  }
947 
954  public function isIPRange() {
955  return IP::isValidRange( $this->mName );
956  }
957 
969  public static function isValidUserName( $name ) {
971 
972  if ( $name == ''
973  || self::isIP( $name )
974  || strpos( $name, '/' ) !== false
975  || strlen( $name ) > $wgMaxNameChars
976  || $name != $wgContLang->ucfirst( $name )
977  ) {
978  return false;
979  }
980 
981  // Ensure that the name can't be misresolved as a different title,
982  // such as with extra namespace keys at the start.
983  $parsed = Title::newFromText( $name );
984  if ( is_null( $parsed )
985  || $parsed->getNamespace()
986  || strcmp( $name, $parsed->getPrefixedText() ) ) {
987  return false;
988  }
989 
990  // Check an additional blacklist of troublemaker characters.
991  // Should these be merged into the title char list?
992  $unicodeBlacklist = '/[' .
993  '\x{0080}-\x{009f}' . # iso-8859-1 control chars
994  '\x{00a0}' . # non-breaking space
995  '\x{2000}-\x{200f}' . # various whitespace
996  '\x{2028}-\x{202f}' . # breaks and control chars
997  '\x{3000}' . # ideographic space
998  '\x{e000}-\x{f8ff}' . # private use
999  ']/u';
1000  if ( preg_match( $unicodeBlacklist, $name ) ) {
1001  return false;
1002  }
1003 
1004  return true;
1005  }
1006 
1018  public static function isUsableName( $name ) {
1020  // Must be a valid username, obviously ;)
1021  if ( !self::isValidUserName( $name ) ) {
1022  return false;
1023  }
1024 
1025  static $reservedUsernames = false;
1026  if ( !$reservedUsernames ) {
1027  $reservedUsernames = $wgReservedUsernames;
1028  Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
1029  }
1030 
1031  // Certain names may be reserved for batch processes.
1032  foreach ( $reservedUsernames as $reserved ) {
1033  if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1034  $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
1035  }
1036  if ( $reserved == $name ) {
1037  return false;
1038  }
1039  }
1040  return true;
1041  }
1042 
1053  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1054  if ( $groups === [] ) {
1055  return UserArrayFromResult::newFromIDs( [] );
1056  }
1057 
1058  $groups = array_unique( (array)$groups );
1059  $limit = min( 5000, $limit );
1060 
1061  $conds = [ 'ug_group' => $groups ];
1062  if ( $after !== null ) {
1063  $conds[] = 'ug_user > ' . (int)$after;
1064  }
1065 
1066  $dbr = wfGetDB( DB_REPLICA );
1067  $ids = $dbr->selectFieldValues(
1068  'user_groups',
1069  'ug_user',
1070  $conds,
1071  __METHOD__,
1072  [
1073  'DISTINCT' => true,
1074  'ORDER BY' => 'ug_user',
1075  'LIMIT' => $limit,
1076  ]
1077  ) ?: [];
1078  return UserArray::newFromIDs( $ids );
1079  }
1080 
1093  public static function isCreatableName( $name ) {
1095 
1096  // Ensure that the username isn't longer than 235 bytes, so that
1097  // (at least for the builtin skins) user javascript and css files
1098  // will work. (T25080)
1099  if ( strlen( $name ) > 235 ) {
1100  wfDebugLog( 'username', __METHOD__ .
1101  ": '$name' invalid due to length" );
1102  return false;
1103  }
1104 
1105  // Preg yells if you try to give it an empty string
1106  if ( $wgInvalidUsernameCharacters !== '' ) {
1107  if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
1108  wfDebugLog( 'username', __METHOD__ .
1109  ": '$name' invalid due to wgInvalidUsernameCharacters" );
1110  return false;
1111  }
1112  }
1113 
1114  return self::isUsableName( $name );
1115  }
1116 
1123  public function isValidPassword( $password ) {
1124  // simple boolean wrapper for getPasswordValidity
1125  return $this->getPasswordValidity( $password ) === true;
1126  }
1127 
1134  public function getPasswordValidity( $password ) {
1135  $result = $this->checkPasswordValidity( $password );
1136  if ( $result->isGood() ) {
1137  return true;
1138  } else {
1139  $messages = [];
1140  foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1141  $messages[] = $error['message'];
1142  }
1143  foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1144  $messages[] = $warning['message'];
1145  }
1146  if ( count( $messages ) === 1 ) {
1147  return $messages[0];
1148  }
1149  return $messages;
1150  }
1151  }
1152 
1170  public function checkPasswordValidity( $password ) {
1172 
1173  $upp = new UserPasswordPolicy(
1174  $wgPasswordPolicy['policies'],
1175  $wgPasswordPolicy['checks']
1176  );
1177 
1179  $result = false; // init $result to false for the internal checks
1180 
1181  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1182  $status->error( $result );
1183  return $status;
1184  }
1185 
1186  if ( $result === false ) {
1187  $status->merge( $upp->checkUserPassword( $this, $password ) );
1188  return $status;
1189  } elseif ( $result === true ) {
1190  return $status;
1191  } else {
1192  $status->error( $result );
1193  return $status; // the isValidPassword hook set a string $result and returned true
1194  }
1195  }
1196 
1210  public static function getCanonicalName( $name, $validate = 'valid' ) {
1211  // Force usernames to capital
1213  $name = $wgContLang->ucfirst( $name );
1214 
1215  # Reject names containing '#'; these will be cleaned up
1216  # with title normalisation, but then it's too late to
1217  # check elsewhere
1218  if ( strpos( $name, '#' ) !== false ) {
1219  return false;
1220  }
1221 
1222  // Clean up name according to title rules,
1223  // but only when validation is requested (T14654)
1224  $t = ( $validate !== false ) ?
1226  // Check for invalid titles
1227  if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1228  return false;
1229  }
1230 
1231  // Reject various classes of invalid names
1232  $name = AuthManager::callLegacyAuthPlugin(
1233  'getCanonicalName', [ $t->getText() ], $t->getText()
1234  );
1235 
1236  switch ( $validate ) {
1237  case false:
1238  break;
1239  case 'valid':
1240  if ( !self::isValidUserName( $name ) ) {
1241  $name = false;
1242  }
1243  break;
1244  case 'usable':
1245  if ( !self::isUsableName( $name ) ) {
1246  $name = false;
1247  }
1248  break;
1249  case 'creatable':
1250  if ( !self::isCreatableName( $name ) ) {
1251  $name = false;
1252  }
1253  break;
1254  default:
1255  throw new InvalidArgumentException(
1256  'Invalid parameter value for $validate in ' . __METHOD__ );
1257  }
1258  return $name;
1259  }
1260 
1267  public static function randomPassword() {
1270  }
1271 
1280  public function loadDefaults( $name = false ) {
1281  $this->mId = 0;
1282  $this->mName = $name;
1283  $this->mActorId = null;
1284  $this->mRealName = '';
1285  $this->mEmail = '';
1286  $this->mOptionOverrides = null;
1287  $this->mOptionsLoaded = false;
1288 
1289  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1290  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1291  if ( $loggedOut !== 0 ) {
1292  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1293  } else {
1294  $this->mTouched = '1'; # Allow any pages to be cached
1295  }
1296 
1297  $this->mToken = null; // Don't run cryptographic functions till we need a token
1298  $this->mEmailAuthenticated = null;
1299  $this->mEmailToken = '';
1300  $this->mEmailTokenExpires = null;
1301  $this->mRegistration = wfTimestamp( TS_MW );
1302  $this->mGroupMemberships = [];
1303 
1304  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1305  }
1306 
1319  public function isItemLoaded( $item, $all = 'all' ) {
1320  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1321  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1322  }
1323 
1329  protected function setItemLoaded( $item ) {
1330  if ( is_array( $this->mLoadedItems ) ) {
1331  $this->mLoadedItems[$item] = true;
1332  }
1333  }
1334 
1340  private function loadFromSession() {
1341  // Deprecated hook
1342  $result = null;
1343  Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1344  if ( $result !== null ) {
1345  return $result;
1346  }
1347 
1348  // MediaWiki\Session\Session already did the necessary authentication of the user
1349  // returned here, so just use it if applicable.
1350  $session = $this->getRequest()->getSession();
1351  $user = $session->getUser();
1352  if ( $user->isLoggedIn() ) {
1353  $this->loadFromUserObject( $user );
1354 
1355  // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1356  // every session load, because an autoblocked editor might not edit again from the same
1357  // IP address after being blocked.
1358  $config = RequestContext::getMain()->getConfig();
1359  if ( $config->get( 'CookieSetOnAutoblock' ) === true ) {
1360  $block = $this->getBlock();
1361  $shouldSetCookie = $this->getRequest()->getCookie( 'BlockID' ) === null
1362  && $block
1363  && $block->getType() === Block::TYPE_USER
1364  && $block->isAutoblocking();
1365  if ( $shouldSetCookie ) {
1366  wfDebug( __METHOD__ . ': User is autoblocked, setting cookie to track' );
1367  $block->setCookie( $this->getRequest()->response() );
1368  }
1369  }
1370 
1371  // Other code expects these to be set in the session, so set them.
1372  $session->set( 'wsUserID', $this->getId() );
1373  $session->set( 'wsUserName', $this->getName() );
1374  $session->set( 'wsToken', $this->getToken() );
1375  return true;
1376  }
1377  return false;
1378  }
1379 
1387  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1388  // Paranoia
1389  $this->mId = intval( $this->mId );
1390 
1391  if ( !$this->mId ) {
1392  // Anonymous users are not in the database
1393  $this->loadDefaults();
1394  return false;
1395  }
1396 
1397  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1398  $db = wfGetDB( $index );
1399 
1400  $userQuery = self::getQueryInfo();
1401  $s = $db->selectRow(
1402  $userQuery['tables'],
1403  $userQuery['fields'],
1404  [ 'user_id' => $this->mId ],
1405  __METHOD__,
1406  $options,
1407  $userQuery['joins']
1408  );
1409 
1410  $this->queryFlagsUsed = $flags;
1411  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1412 
1413  if ( $s !== false ) {
1414  // Initialise user table data
1415  $this->loadFromRow( $s );
1416  $this->mGroupMemberships = null; // deferred
1417  $this->getEditCount(); // revalidation for nulls
1418  return true;
1419  } else {
1420  // Invalid user_id
1421  $this->mId = 0;
1422  $this->loadDefaults();
1423  return false;
1424  }
1425  }
1426 
1439  protected function loadFromRow( $row, $data = null ) {
1441 
1442  if ( !is_object( $row ) ) {
1443  throw new InvalidArgumentException( '$row must be an object' );
1444  }
1445 
1446  $all = true;
1447 
1448  $this->mGroupMemberships = null; // deferred
1449 
1451  if ( isset( $row->actor_id ) ) {
1452  $this->mActorId = (int)$row->actor_id;
1453  if ( $this->mActorId !== 0 ) {
1454  $this->mFrom = 'actor';
1455  }
1456  $this->setItemLoaded( 'actor' );
1457  } else {
1458  $all = false;
1459  }
1460  }
1461 
1462  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1463  $this->mName = $row->user_name;
1464  $this->mFrom = 'name';
1465  $this->setItemLoaded( 'name' );
1466  } else {
1467  $all = false;
1468  }
1469 
1470  if ( isset( $row->user_real_name ) ) {
1471  $this->mRealName = $row->user_real_name;
1472  $this->setItemLoaded( 'realname' );
1473  } else {
1474  $all = false;
1475  }
1476 
1477  if ( isset( $row->user_id ) ) {
1478  $this->mId = intval( $row->user_id );
1479  if ( $this->mId !== 0 ) {
1480  $this->mFrom = 'id';
1481  }
1482  $this->setItemLoaded( 'id' );
1483  } else {
1484  $all = false;
1485  }
1486 
1487  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1488  self::$idCacheByName[$row->user_name] = $row->user_id;
1489  }
1490 
1491  if ( isset( $row->user_editcount ) ) {
1492  $this->mEditCount = $row->user_editcount;
1493  } else {
1494  $all = false;
1495  }
1496 
1497  if ( isset( $row->user_touched ) ) {
1498  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1499  } else {
1500  $all = false;
1501  }
1502 
1503  if ( isset( $row->user_token ) ) {
1504  // The definition for the column is binary(32), so trim the NULs
1505  // that appends. The previous definition was char(32), so trim
1506  // spaces too.
1507  $this->mToken = rtrim( $row->user_token, " \0" );
1508  if ( $this->mToken === '' ) {
1509  $this->mToken = null;
1510  }
1511  } else {
1512  $all = false;
1513  }
1514 
1515  if ( isset( $row->user_email ) ) {
1516  $this->mEmail = $row->user_email;
1517  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1518  $this->mEmailToken = $row->user_email_token;
1519  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1520  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1521  } else {
1522  $all = false;
1523  }
1524 
1525  if ( $all ) {
1526  $this->mLoadedItems = true;
1527  }
1528 
1529  if ( is_array( $data ) ) {
1530  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1531  if ( !count( $data['user_groups'] ) ) {
1532  $this->mGroupMemberships = [];
1533  } else {
1534  $firstGroup = reset( $data['user_groups'] );
1535  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1536  $this->mGroupMemberships = [];
1537  foreach ( $data['user_groups'] as $row ) {
1538  $ugm = UserGroupMembership::newFromRow( (object)$row );
1539  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1540  }
1541  }
1542  }
1543  }
1544  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1545  $this->loadOptions( $data['user_properties'] );
1546  }
1547  }
1548  }
1549 
1555  protected function loadFromUserObject( $user ) {
1556  $user->load();
1557  foreach ( self::$mCacheVars as $var ) {
1558  $this->$var = $user->$var;
1559  }
1560  }
1561 
1565  private function loadGroups() {
1566  if ( is_null( $this->mGroupMemberships ) ) {
1567  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1568  ? wfGetDB( DB_MASTER )
1569  : wfGetDB( DB_REPLICA );
1570  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1571  $this->mId, $db );
1572  }
1573  }
1574 
1589  public function addAutopromoteOnceGroups( $event ) {
1591 
1592  if ( wfReadOnly() || !$this->getId() ) {
1593  return [];
1594  }
1595 
1596  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1597  if ( !count( $toPromote ) ) {
1598  return [];
1599  }
1600 
1601  if ( !$this->checkAndSetTouched() ) {
1602  return []; // raced out (bug T48834)
1603  }
1604 
1605  $oldGroups = $this->getGroups(); // previous groups
1606  $oldUGMs = $this->getGroupMemberships();
1607  foreach ( $toPromote as $group ) {
1608  $this->addGroup( $group );
1609  }
1610  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1611  $newUGMs = $this->getGroupMemberships();
1612 
1613  // update groups in external authentication database
1614  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1615  AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1616 
1617  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1618  $logEntry->setPerformer( $this );
1619  $logEntry->setTarget( $this->getUserPage() );
1620  $logEntry->setParameters( [
1621  '4::oldgroups' => $oldGroups,
1622  '5::newgroups' => $newGroups,
1623  ] );
1624  $logid = $logEntry->insert();
1625  if ( $wgAutopromoteOnceLogInRC ) {
1626  $logEntry->publish( $logid );
1627  }
1628 
1629  return $toPromote;
1630  }
1631 
1641  protected function makeUpdateConditions( Database $db, array $conditions ) {
1642  if ( $this->mTouched ) {
1643  // CAS check: only update if the row wasn't changed sicne it was loaded.
1644  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1645  }
1646 
1647  return $conditions;
1648  }
1649 
1659  protected function checkAndSetTouched() {
1660  $this->load();
1661 
1662  if ( !$this->mId ) {
1663  return false; // anon
1664  }
1665 
1666  // Get a new user_touched that is higher than the old one
1667  $newTouched = $this->newTouchedTimestamp();
1668 
1669  $dbw = wfGetDB( DB_MASTER );
1670  $dbw->update( 'user',
1671  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1672  $this->makeUpdateConditions( $dbw, [
1673  'user_id' => $this->mId,
1674  ] ),
1675  __METHOD__
1676  );
1677  $success = ( $dbw->affectedRows() > 0 );
1678 
1679  if ( $success ) {
1680  $this->mTouched = $newTouched;
1681  $this->clearSharedCache();
1682  } else {
1683  // Clears on failure too since that is desired if the cache is stale
1684  $this->clearSharedCache( 'refresh' );
1685  }
1686 
1687  return $success;
1688  }
1689 
1697  public function clearInstanceCache( $reloadFrom = false ) {
1698  $this->mNewtalk = -1;
1699  $this->mDatePreference = null;
1700  $this->mBlockedby = -1; # Unset
1701  $this->mHash = false;
1702  $this->mRights = null;
1703  $this->mEffectiveGroups = null;
1704  $this->mImplicitGroups = null;
1705  $this->mGroupMemberships = null;
1706  $this->mOptions = null;
1707  $this->mOptionsLoaded = false;
1708  $this->mEditCount = null;
1709 
1710  if ( $reloadFrom ) {
1711  $this->mLoadedItems = [];
1712  $this->mFrom = $reloadFrom;
1713  }
1714  }
1715 
1722  public static function getDefaultOptions() {
1724 
1725  static $defOpt = null;
1726  static $defOptLang = null;
1727 
1728  if ( $defOpt !== null && $defOptLang === $wgContLang->getCode() ) {
1729  // $wgContLang does not change (and should not change) mid-request,
1730  // but the unit tests change it anyway, and expect this method to
1731  // return values relevant to the current $wgContLang.
1732  return $defOpt;
1733  }
1734 
1735  $defOpt = $wgDefaultUserOptions;
1736  // Default language setting
1737  $defOptLang = $wgContLang->getCode();
1738  $defOpt['language'] = $defOptLang;
1739  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1740  $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
1741  }
1742 
1743  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1744  // since extensions may change the set of searchable namespaces depending
1745  // on user groups/permissions.
1746  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1747  $defOpt['searchNs' . $nsnum] = (bool)$val;
1748  }
1749  $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1750 
1751  Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1752 
1753  return $defOpt;
1754  }
1755 
1762  public static function getDefaultOption( $opt ) {
1763  $defOpts = self::getDefaultOptions();
1764  if ( isset( $defOpts[$opt] ) ) {
1765  return $defOpts[$opt];
1766  } else {
1767  return null;
1768  }
1769  }
1770 
1777  private function getBlockedStatus( $bFromSlave = true ) {
1779 
1780  if ( -1 != $this->mBlockedby ) {
1781  return;
1782  }
1783 
1784  wfDebug( __METHOD__ . ": checking...\n" );
1785 
1786  // Initialize data...
1787  // Otherwise something ends up stomping on $this->mBlockedby when
1788  // things get lazy-loaded later, causing false positive block hits
1789  // due to -1 !== 0. Probably session-related... Nothing should be
1790  // overwriting mBlockedby, surely?
1791  $this->load();
1792 
1793  # We only need to worry about passing the IP address to the Block generator if the
1794  # user is not immune to autoblocks/hardblocks, and they are the current user so we
1795  # know which IP address they're actually coming from
1796  $ip = null;
1797  $sessionUser = RequestContext::getMain()->getUser();
1798  // the session user is set up towards the end of Setup.php. Until then,
1799  // assume it's a logged-out user.
1800  $globalUserName = $sessionUser->isSafeToLoad()
1801  ? $sessionUser->getName()
1802  : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1803  if ( $this->getName() === $globalUserName && !$this->isAllowed( 'ipblock-exempt' ) ) {
1804  $ip = $this->getRequest()->getIP();
1805  }
1806 
1807  // User/IP blocking
1808  $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1809 
1810  // Cookie blocking
1811  if ( !$block instanceof Block ) {
1812  $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
1813  }
1814 
1815  // Proxy blocking
1816  if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1817  // Local list
1818  if ( self::isLocallyBlockedProxy( $ip ) ) {
1819  $block = new Block( [
1820  'byText' => wfMessage( 'proxyblocker' )->text(),
1821  'reason' => wfMessage( 'proxyblockreason' )->text(),
1822  'address' => $ip,
1823  'systemBlock' => 'proxy',
1824  ] );
1825  } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1826  $block = new Block( [
1827  'byText' => wfMessage( 'sorbs' )->text(),
1828  'reason' => wfMessage( 'sorbsreason' )->text(),
1829  'address' => $ip,
1830  'systemBlock' => 'dnsbl',
1831  ] );
1832  }
1833  }
1834 
1835  // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
1836  if ( !$block instanceof Block
1838  && $ip !== null
1839  && !in_array( $ip, $wgProxyWhitelist )
1840  ) {
1841  $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1842  $xff = array_map( 'trim', explode( ',', $xff ) );
1843  $xff = array_diff( $xff, [ $ip ] );
1844  $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1845  $block = Block::chooseBlock( $xffblocks, $xff );
1846  if ( $block instanceof Block ) {
1847  # Mangle the reason to alert the user that the block
1848  # originated from matching the X-Forwarded-For header.
1849  $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
1850  }
1851  }
1852 
1853  if ( !$block instanceof Block
1854  && $ip !== null
1855  && $this->isAnon()
1857  ) {
1858  $block = new Block( [
1859  'address' => $ip,
1860  'byText' => 'MediaWiki default',
1861  'reason' => wfMessage( 'softblockrangesreason', $ip )->text(),
1862  'anonOnly' => true,
1863  'systemBlock' => 'wgSoftBlockRanges',
1864  ] );
1865  }
1866 
1867  if ( $block instanceof Block ) {
1868  wfDebug( __METHOD__ . ": Found block.\n" );
1869  $this->mBlock = $block;
1870  $this->mBlockedby = $block->getByName();
1871  $this->mBlockreason = $block->mReason;
1872  $this->mHideName = $block->mHideName;
1873  $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1874  } else {
1875  $this->mBlock = null;
1876  $this->mBlockedby = '';
1877  $this->mBlockreason = '';
1878  $this->mHideName = 0;
1879  $this->mAllowUsertalk = false;
1880  }
1881 
1882  // Avoid PHP 7.1 warning of passing $this by reference
1883  $thisUser = $this;
1884  // Extensions
1885  Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
1886  }
1887 
1893  protected function getBlockFromCookieValue( $blockCookieVal ) {
1894  // Make sure there's something to check. The cookie value must start with a number.
1895  if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
1896  return false;
1897  }
1898  // Load the Block from the ID in the cookie.
1899  $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
1900  if ( $blockCookieId !== null ) {
1901  // An ID was found in the cookie.
1902  $tmpBlock = Block::newFromID( $blockCookieId );
1903  if ( $tmpBlock instanceof Block ) {
1904  // Check the validity of the block.
1905  $blockIsValid = $tmpBlock->getType() == Block::TYPE_USER
1906  && !$tmpBlock->isExpired()
1907  && $tmpBlock->isAutoblocking();
1908  $config = RequestContext::getMain()->getConfig();
1909  $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
1910  if ( $blockIsValid && $useBlockCookie ) {
1911  // Use the block.
1912  return $tmpBlock;
1913  } else {
1914  // If the block is not valid, remove the cookie.
1915  Block::clearCookie( $this->getRequest()->response() );
1916  }
1917  } else {
1918  // If the block doesn't exist, remove the cookie.
1919  Block::clearCookie( $this->getRequest()->response() );
1920  }
1921  }
1922  return false;
1923  }
1924 
1932  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1934 
1935  if ( !$wgEnableDnsBlacklist ) {
1936  return false;
1937  }
1938 
1939  if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
1940  return false;
1941  }
1942 
1943  return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
1944  }
1945 
1953  public function inDnsBlacklist( $ip, $bases ) {
1954  $found = false;
1955  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1956  if ( IP::isIPv4( $ip ) ) {
1957  // Reverse IP, T23255
1958  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1959 
1960  foreach ( (array)$bases as $base ) {
1961  // Make hostname
1962  // If we have an access key, use that too (ProjectHoneypot, etc.)
1963  $basename = $base;
1964  if ( is_array( $base ) ) {
1965  if ( count( $base ) >= 2 ) {
1966  // Access key is 1, base URL is 0
1967  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1968  } else {
1969  $host = "$ipReversed.{$base[0]}";
1970  }
1971  $basename = $base[0];
1972  } else {
1973  $host = "$ipReversed.$base";
1974  }
1975 
1976  // Send query
1977  $ipList = gethostbynamel( $host );
1978 
1979  if ( $ipList ) {
1980  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1981  $found = true;
1982  break;
1983  } else {
1984  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1985  }
1986  }
1987  }
1988 
1989  return $found;
1990  }
1991 
1999  public static function isLocallyBlockedProxy( $ip ) {
2001 
2002  if ( !$wgProxyList ) {
2003  return false;
2004  }
2005 
2006  if ( !is_array( $wgProxyList ) ) {
2007  // Load values from the specified file
2008  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
2009  }
2010 
2011  $resultProxyList = [];
2012  $deprecatedIPEntries = [];
2013 
2014  // backward compatibility: move all ip addresses in keys to values
2015  foreach ( $wgProxyList as $key => $value ) {
2016  $keyIsIP = IP::isIPAddress( $key );
2017  $valueIsIP = IP::isIPAddress( $value );
2018  if ( $keyIsIP && !$valueIsIP ) {
2019  $deprecatedIPEntries[] = $key;
2020  $resultProxyList[] = $key;
2021  } elseif ( $keyIsIP && $valueIsIP ) {
2022  $deprecatedIPEntries[] = $key;
2023  $resultProxyList[] = $key;
2024  $resultProxyList[] = $value;
2025  } else {
2026  $resultProxyList[] = $value;
2027  }
2028  }
2029 
2030  if ( $deprecatedIPEntries ) {
2031  wfDeprecated(
2032  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
2033  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
2034  }
2035 
2036  $proxyListIPSet = new IPSet( $resultProxyList );
2037  return $proxyListIPSet->match( $ip );
2038  }
2039 
2045  public function isPingLimitable() {
2047  if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
2048  // No other good way currently to disable rate limits
2049  // for specific IPs. :P
2050  // But this is a crappy hack and should die.
2051  return false;
2052  }
2053  return !$this->isAllowed( 'noratelimit' );
2054  }
2055 
2072  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
2073  // Avoid PHP 7.1 warning of passing $this by reference
2074  $user = $this;
2075 
2076  $logger = \MediaWiki\Logger\LoggerFactory::getInstance( 'ratelimit' );
2077 
2078  // Call the 'PingLimiter' hook
2079  $result = false;
2080  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
2081  return $result;
2082  }
2083 
2085  if ( !isset( $wgRateLimits[$action] ) ) {
2086  return false;
2087  }
2088 
2089  $limits = array_merge(
2090  [ '&can-bypass' => true ],
2091  $wgRateLimits[$action]
2092  );
2093 
2094  // Some groups shouldn't trigger the ping limiter, ever
2095  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
2096  return false;
2097  }
2098 
2099  $logger->debug( __METHOD__ . ": limiting $action rate for {$this->getName()}" );
2100 
2101  $keys = [];
2102  $id = $this->getId();
2103  $isNewbie = $this->isNewbie();
2105 
2106  if ( $id == 0 ) {
2107  // "shared anon" limit, for all anons combined
2108  if ( isset( $limits['anon'] ) ) {
2109  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
2110  }
2111  } else {
2112  // "global per name" limit, across sites
2113  if ( isset( $limits['user-global'] ) ) {
2115 
2116  $centralId = $lookup
2117  ? $lookup->centralIdFromLocalUser( $this, CentralIdLookup::AUDIENCE_RAW )
2118  : 0;
2119 
2120  if ( $centralId ) {
2121  // We don't have proper realms, use provider ID.
2122  $realm = $lookup->getProviderId();
2123 
2124  $globalKey = $cache->makeGlobalKey( 'limiter', $action, 'user-global',
2125  $realm, $centralId );
2126  } else {
2127  // Fall back to a local key for a local ID
2128  $globalKey = $cache->makeKey( 'limiter', $action, 'user-global',
2129  'local', $id );
2130  }
2131  $keys[$globalKey] = $limits['user-global'];
2132  }
2133  }
2134 
2135  if ( $isNewbie ) {
2136  // "per ip" limit for anons and newbie users
2137  if ( isset( $limits['ip'] ) ) {
2138  $ip = $this->getRequest()->getIP();
2139  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
2140  }
2141  // "per subnet" limit for anons and newbie users
2142  if ( isset( $limits['subnet'] ) ) {
2143  $ip = $this->getRequest()->getIP();
2144  $subnet = IP::getSubnet( $ip );
2145  if ( $subnet !== false ) {
2146  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
2147  }
2148  }
2149  }
2150 
2151  // determine the "per user account" limit
2152  $userLimit = false;
2153  if ( $id !== 0 && isset( $limits['user'] ) ) {
2154  // default limit for logged-in users
2155  $userLimit = $limits['user'];
2156  }
2157  // limits for newbie logged-in users (overrides all the normal user limits)
2158  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2159  $userLimit = $limits['newbie'];
2160  } else {
2161  // Check for group-specific limits
2162  // If more than one group applies, use the highest allowance (if higher than the default)
2163  foreach ( $this->getGroups() as $group ) {
2164  if ( isset( $limits[$group] ) ) {
2165  if ( $userLimit === false
2166  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2167  ) {
2168  $userLimit = $limits[$group];
2169  }
2170  }
2171  }
2172  }
2173 
2174  // Set the user limit key
2175  if ( $userLimit !== false ) {
2176  list( $max, $period ) = $userLimit;
2177  $logger->debug( __METHOD__ . ": effective user limit: $max in {$period}s" );
2178  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2179  }
2180 
2181  // ip-based limits for all ping-limitable users
2182  if ( isset( $limits['ip-all'] ) ) {
2183  $ip = $this->getRequest()->getIP();
2184  // ignore if user limit is more permissive
2185  if ( $isNewbie || $userLimit === false
2186  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2187  $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip-all', $ip )] = $limits['ip-all'];
2188  }
2189  }
2190 
2191  // subnet-based limits for all ping-limitable users
2192  if ( isset( $limits['subnet-all'] ) ) {
2193  $ip = $this->getRequest()->getIP();
2194  $subnet = IP::getSubnet( $ip );
2195  if ( $subnet !== false ) {
2196  // ignore if user limit is more permissive
2197  if ( $isNewbie || $userLimit === false
2198  || $limits['ip-all'][0] / $limits['ip-all'][1]
2199  > $userLimit[0] / $userLimit[1] ) {
2200  $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet-all', $subnet )]
2201  = $limits['subnet-all'];
2202  }
2203  }
2204  }
2205 
2206  // XXX: We may want to use $cache->getCurrentTime() here, but that would make it
2207  // harder to test for T246991. Also $cache->getCurrentTime() is documented
2208  // as being for testing only, so it apparently should not be called here.
2209  $now = (int)wfTimestamp( TS_UNIX );
2210  $clockFudge = 3; // avoid log spam when a clock is slightly off
2211 
2212  $triggered = false;
2213  foreach ( $keys as $key => $limit ) {
2214  // Do the update in a merge callback, for atomicity.
2215  // To use merge(), we need to explicitly track the desired expiry timestamp.
2216  // This tracking was introduced to investigate T246991. Once it is no longer needed,
2217  // we could go back to incrWithInit(), though that has more potential for race
2218  // conditions between the get() and incrWithInit() calls.
2219  $cache->merge(
2220  $key,
2221  function ( $cache, $key, $data, &$expiry )
2222  use ( $action, $logger, &$triggered, $now, $clockFudge, $limit, $incrBy )
2223  {
2224  // phan is confused because &can-bypass's value is a bool, so it assumes
2225  // that $userLimit is also a bool here.
2226  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2227  list( $max, $period ) = $limit;
2228 
2229  $expiry = $now + (int)$period;
2230  $count = 0;
2231 
2232  // Already pinged?
2233  if ( $data ) {
2234  // NOTE: in order to investigate T246991, we write the expiry time
2235  // into the payload, along with the count.
2236  $fields = explode( '|', $data );
2237  $storedCount = (int)( $fields[0] ?? 0 );
2238  $storedExpiry = (int)( $fields[1] ?? PHP_INT_MAX );
2239 
2240  // Found a stale entry. This should not happen!
2241  if ( $storedExpiry < ( $now + $clockFudge ) ) {
2242  $logger->info(
2243  'User::pingLimiter: '
2244  . 'Stale rate limit entry, cache key failed to expire (T246991)',
2245  [
2246  'action' => $action,
2247  'user' => $this->getName(),
2248  'limit' => $max,
2249  'period' => $period,
2250  'count' => $storedCount,
2251  'key' => $key,
2252  'expiry' => MWTimestamp::convert( TS_DB, $storedExpiry ),
2253  ]
2254  );
2255  } else {
2256  // NOTE: We'll keep the original expiry when bumping counters,
2257  // resulting in a kind of fixed-window throttle.
2258  $expiry = min( $storedExpiry, $now + (int)$period );
2259  $count = $storedCount;
2260  }
2261  }
2262 
2263  // Limit exceeded!
2264  if ( $count >= $max ) {
2265  if ( !$triggered ) {
2266  $logger->info(
2267  'User::pingLimiter: User tripped rate limit',
2268  [
2269  'action' => $action,
2270  'user' => $this->getName(),
2271  'ip' => $this->getRequest()->getIP(),
2272  'limit' => $max,
2273  'period' => $period,
2274  'count' => $count,
2275  'key' => $key
2276  ]
2277  );
2278  }
2279 
2280  $triggered = true;
2281  }
2282 
2283  $count += $incrBy;
2284  $data = "$count|$expiry";
2285  return $data;
2286  }
2287  );
2288  }
2289 
2290  return $triggered;
2291  }
2292 
2300  public function isBlocked( $bFromSlave = true ) {
2301  return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
2302  }
2303 
2310  public function getBlock( $bFromSlave = true ) {
2311  $this->getBlockedStatus( $bFromSlave );
2312  return $this->mBlock instanceof Block ? $this->mBlock : null;
2313  }
2314 
2322  public function isBlockedFrom( $title, $bFromSlave = false ) {
2324 
2325  $blocked = $this->isBlocked( $bFromSlave );
2326  $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
2327  // If a user's name is suppressed, they cannot make edits anywhere
2328  if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
2329  && $title->getNamespace() == NS_USER_TALK ) {
2330  $blocked = false;
2331  wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
2332  }
2333 
2334  Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
2335 
2336  return $blocked;
2337  }
2338 
2343  public function blockedBy() {
2344  $this->getBlockedStatus();
2345  return $this->mBlockedby;
2346  }
2347 
2352  public function blockedFor() {
2353  $this->getBlockedStatus();
2354  return $this->mBlockreason;
2355  }
2356 
2361  public function getBlockId() {
2362  $this->getBlockedStatus();
2363  return ( $this->mBlock ? $this->mBlock->getId() : false );
2364  }
2365 
2374  public function isBlockedGlobally( $ip = '' ) {
2375  return $this->getGlobalBlock( $ip ) instanceof Block;
2376  }
2377 
2388  public function getGlobalBlock( $ip = '' ) {
2389  if ( $this->mGlobalBlock !== null ) {
2390  return $this->mGlobalBlock ?: null;
2391  }
2392  // User is already an IP?
2393  if ( IP::isIPAddress( $this->getName() ) ) {
2394  $ip = $this->getName();
2395  } elseif ( !$ip ) {
2396  $ip = $this->getRequest()->getIP();
2397  }
2398  // Avoid PHP 7.1 warning of passing $this by reference
2399  $user = $this;
2400  $blocked = false;
2401  $block = null;
2402  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2403 
2404  if ( $blocked && $block === null ) {
2405  // back-compat: UserIsBlockedGlobally didn't have $block param first
2406  $block = new Block( [
2407  'address' => $ip,
2408  'systemBlock' => 'global-block'
2409  ] );
2410  }
2411 
2412  $this->mGlobalBlock = $blocked ? $block : false;
2413  return $this->mGlobalBlock ?: null;
2414  }
2415 
2421  public function isLocked() {
2422  if ( $this->mLocked !== null ) {
2423  return $this->mLocked;
2424  }
2425  // Avoid PHP 7.1 warning of passing $this by reference
2426  $user = $this;
2427  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2428  $this->mLocked = $authUser && $authUser->isLocked();
2429  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2430  return $this->mLocked;
2431  }
2432 
2438  public function isHidden() {
2439  if ( $this->mHideName !== null ) {
2440  return $this->mHideName;
2441  }
2442  $this->getBlockedStatus();
2443  if ( !$this->mHideName ) {
2444  // Avoid PHP 7.1 warning of passing $this by reference
2445  $user = $this;
2446  $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2447  $this->mHideName = $authUser && $authUser->isHidden();
2448  Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2449  }
2450  return $this->mHideName;
2451  }
2452 
2457  public function getId() {
2458  if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2459  // Special case, we know the user is anonymous
2460  return 0;
2461  } elseif ( !$this->isItemLoaded( 'id' ) ) {
2462  // Don't load if this was initialized from an ID
2463  $this->load();
2464  }
2465 
2466  return (int)$this->mId;
2467  }
2468 
2473  public function setId( $v ) {
2474  $this->mId = $v;
2475  $this->clearInstanceCache( 'id' );
2476  }
2477 
2482  public function getName() {
2483  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2484  // Special case optimisation
2485  return $this->mName;
2486  } else {
2487  $this->load();
2488  if ( $this->mName === false ) {
2489  // Clean up IPs
2490  $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2491  }
2492  return $this->mName;
2493  }
2494  }
2495 
2509  public function setName( $str ) {
2510  $this->load();
2511  $this->mName = $str;
2512  }
2513 
2520  public function getActorId( IDatabase $dbw = null ) {
2522 
2524  return 0;
2525  }
2526 
2527  if ( !$this->isItemLoaded( 'actor' ) ) {
2528  $this->load();
2529  }
2530 
2531  if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2532  $migration = MediaWikiServices::getInstance()->getActorMigration();
2533  if ( !$dbw ) {
2534  // Read from a database, flags are used for wfGetDB()
2535  $dbw = $this->queryFlagsUsed;
2536  }
2537  $this->mActorId = $migration->getNewActorId( $dbw, $this );
2538 
2539  if ( $dbw instanceof IDatabase ) {
2540  $this->invalidateCache();
2541  }
2542  $this->setItemLoaded( 'actor' );
2543  }
2544 
2545  return (int)$this->mActorId;
2546  }
2547 
2552  public function getTitleKey() {
2553  return str_replace( ' ', '_', $this->getName() );
2554  }
2555 
2560  public function getNewtalk() {
2561  $this->load();
2562 
2563  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2564  if ( $this->mNewtalk === -1 ) {
2565  $this->mNewtalk = false; # reset talk page status
2566 
2567  // Check memcached separately for anons, who have no
2568  // entire User object stored in there.
2569  if ( !$this->mId ) {
2571  if ( $wgDisableAnonTalk ) {
2572  // Anon newtalk disabled by configuration.
2573  $this->mNewtalk = false;
2574  } else {
2575  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2576  }
2577  } else {
2578  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2579  }
2580  }
2581 
2582  return (bool)$this->mNewtalk;
2583  }
2584 
2598  public function getNewMessageLinks() {
2599  // Avoid PHP 7.1 warning of passing $this by reference
2600  $user = $this;
2601  $talks = [];
2602  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2603  return $talks;
2604  } elseif ( !$this->getNewtalk() ) {
2605  return [];
2606  }
2607  $utp = $this->getTalkPage();
2608  $dbr = wfGetDB( DB_REPLICA );
2609  // Get the "last viewed rev" timestamp from the oldest message notification
2610  $timestamp = $dbr->selectField( 'user_newtalk',
2611  'MIN(user_last_timestamp)',
2612  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2613  __METHOD__ );
2614  $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2615  return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2616  }
2617 
2623  public function getNewMessageRevisionId() {
2624  $newMessageRevisionId = null;
2625  $newMessageLinks = $this->getNewMessageLinks();
2626  if ( $newMessageLinks ) {
2627  // Note: getNewMessageLinks() never returns more than a single link
2628  // and it is always for the same wiki, but we double-check here in
2629  // case that changes some time in the future.
2630  if ( count( $newMessageLinks ) === 1
2631  && $newMessageLinks[0]['wiki'] === wfWikiID()
2632  && $newMessageLinks[0]['rev']
2633  ) {
2635  $newMessageRevision = $newMessageLinks[0]['rev'];
2636  $newMessageRevisionId = $newMessageRevision->getId();
2637  }
2638  }
2639  return $newMessageRevisionId;
2640  }
2641 
2650  protected function checkNewtalk( $field, $id ) {
2651  $dbr = wfGetDB( DB_REPLICA );
2652 
2653  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2654 
2655  return $ok !== false;
2656  }
2657 
2665  protected function updateNewtalk( $field, $id, $curRev = null ) {
2666  // Get timestamp of the talk page revision prior to the current one
2667  $prevRev = $curRev ? $curRev->getPrevious() : false;
2668  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2669  // Mark the user as having new messages since this revision
2670  $dbw = wfGetDB( DB_MASTER );
2671  $dbw->insert( 'user_newtalk',
2672  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2673  __METHOD__,
2674  'IGNORE' );
2675  if ( $dbw->affectedRows() ) {
2676  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2677  return true;
2678  } else {
2679  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2680  return false;
2681  }
2682  }
2683 
2690  protected function deleteNewtalk( $field, $id ) {
2691  $dbw = wfGetDB( DB_MASTER );
2692  $dbw->delete( 'user_newtalk',
2693  [ $field => $id ],
2694  __METHOD__ );
2695  if ( $dbw->affectedRows() ) {
2696  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2697  return true;
2698  } else {
2699  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2700  return false;
2701  }
2702  }
2703 
2710  public function setNewtalk( $val, $curRev = null ) {
2711  if ( wfReadOnly() ) {
2712  return;
2713  }
2714 
2715  $this->load();
2716  $this->mNewtalk = $val;
2717 
2718  if ( $this->isAnon() ) {
2719  $field = 'user_ip';
2720  $id = $this->getName();
2721  } else {
2722  $field = 'user_id';
2723  $id = $this->getId();
2724  }
2725 
2726  if ( $val ) {
2727  $changed = $this->updateNewtalk( $field, $id, $curRev );
2728  } else {
2729  $changed = $this->deleteNewtalk( $field, $id );
2730  }
2731 
2732  if ( $changed ) {
2733  $this->invalidateCache();
2734  }
2735  }
2736 
2742  private function newTouchedTimestamp() {
2744 
2745  $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2746  if ( $this->mTouched && $time <= $this->mTouched ) {
2747  $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2748  }
2749 
2750  return $time;
2751  }
2752 
2763  public function clearSharedCache( $mode = 'changed' ) {
2764  if ( !$this->getId() ) {
2765  return;
2766  }
2767 
2769  $key = $this->getCacheKey( $cache );
2770  if ( $mode === 'refresh' ) {
2771  $cache->delete( $key, 1 );
2772  } else {
2773  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2774  if ( $lb->hasOrMadeRecentMasterChanges() ) {
2775  $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
2776  function () use ( $cache, $key ) {
2777  $cache->delete( $key );
2778  },
2779  __METHOD__
2780  );
2781  } else {
2782  $cache->delete( $key );
2783  }
2784  }
2785  }
2786 
2792  public function invalidateCache() {
2793  $this->touch();
2794  $this->clearSharedCache();
2795  }
2796 
2809  public function touch() {
2810  $id = $this->getId();
2811  if ( $id ) {
2812  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2813  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2814  $cache->touchCheckKey( $key );
2815  $this->mQuickTouched = null;
2816  }
2817  }
2818 
2824  public function validateCache( $timestamp ) {
2825  return ( $timestamp >= $this->getTouched() );
2826  }
2827 
2836  public function getTouched() {
2837  $this->load();
2838 
2839  if ( $this->mId ) {
2840  if ( $this->mQuickTouched === null ) {
2841  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2842  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2843 
2844  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2845  }
2846 
2847  return max( $this->mTouched, $this->mQuickTouched );
2848  }
2849 
2850  return $this->mTouched;
2851  }
2852 
2858  public function getDBTouched() {
2859  $this->load();
2860 
2861  return $this->mTouched;
2862  }
2863 
2880  public function setPassword( $str ) {
2881  return $this->setPasswordInternal( $str );
2882  }
2883 
2892  public function setInternalPassword( $str ) {
2893  $this->setPasswordInternal( $str );
2894  }
2895 
2904  private function setPasswordInternal( $str ) {
2905  $manager = AuthManager::singleton();
2906 
2907  // If the user doesn't exist yet, fail
2908  if ( !$manager->userExists( $this->getName() ) ) {
2909  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2910  }
2911 
2912  $status = $this->changeAuthenticationData( [
2913  'username' => $this->getName(),
2914  'password' => $str,
2915  'retype' => $str,
2916  ] );
2917  if ( !$status->isGood() ) {
2919  ->info( __METHOD__ . ': Password change rejected: '
2920  . $status->getWikiText( null, null, 'en' ) );
2921  return false;
2922  }
2923 
2924  $this->setOption( 'watchlisttoken', false );
2925  SessionManager::singleton()->invalidateSessionsForUser( $this );
2926 
2927  return true;
2928  }
2929 
2942  public function changeAuthenticationData( array $data ) {
2943  $manager = AuthManager::singleton();
2944  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2945  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2946 
2947  $status = Status::newGood( 'ignored' );
2948  foreach ( $reqs as $req ) {
2949  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2950  }
2951  if ( $status->getValue() === 'ignored' ) {
2952  $status->warning( 'authenticationdatachange-ignored' );
2953  }
2954 
2955  if ( $status->isGood() ) {
2956  foreach ( $reqs as $req ) {
2957  $manager->changeAuthenticationData( $req );
2958  }
2959  }
2960  return $status;
2961  }
2962 
2969  public function getToken( $forceCreation = true ) {
2971 
2972  $this->load();
2973  if ( !$this->mToken && $forceCreation ) {
2974  $this->setToken();
2975  }
2976 
2977  if ( !$this->mToken ) {
2978  // The user doesn't have a token, return null to indicate that.
2979  return null;
2980  } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2981  // We return a random value here so existing token checks are very
2982  // likely to fail.
2983  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2984  } elseif ( $wgAuthenticationTokenVersion === null ) {
2985  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2986  return $this->mToken;
2987  } else {
2988  // $wgAuthenticationTokenVersion in use, so hmac it.
2989  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2990 
2991  // The raw hash can be overly long. Shorten it up.
2992  $len = max( 32, self::TOKEN_LENGTH );
2993  if ( strlen( $ret ) < $len ) {
2994  // Should never happen, even md5 is 128 bits
2995  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2996  }
2997  return substr( $ret, -$len );
2998  }
2999  }
3000 
3007  public function setToken( $token = false ) {
3008  $this->load();
3009  if ( $this->mToken === self::INVALID_TOKEN ) {
3011  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
3012  } elseif ( !$token ) {
3013  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
3014  } else {
3015  $this->mToken = $token;
3016  }
3017  }
3018 
3027  public function setNewpassword( $str, $throttle = true ) {
3028  throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
3029  }
3030 
3035  public function getEmail() {
3036  $this->load();
3037  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
3038  return $this->mEmail;
3039  }
3040 
3046  $this->load();
3047  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
3049  }
3050 
3055  public function setEmail( $str ) {
3056  $this->load();
3057  if ( $str == $this->mEmail ) {
3058  return;
3059  }
3060  $this->invalidateEmail();
3061  $this->mEmail = $str;
3062  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
3063  }
3064 
3072  public function setEmailWithConfirmation( $str ) {
3074 
3075  if ( !$wgEnableEmail ) {
3076  return Status::newFatal( 'emaildisabled' );
3077  }
3078 
3079  $oldaddr = $this->getEmail();
3080  if ( $str === $oldaddr ) {
3081  return Status::newGood( true );
3082  }
3083 
3084  $type = $oldaddr != '' ? 'changed' : 'set';
3085  $notificationResult = null;
3086 
3087  if ( $wgEmailAuthentication ) {
3088  // Send the user an email notifying the user of the change in registered
3089  // email address on their previous email address
3090  if ( $type == 'changed' ) {
3091  $change = $str != '' ? 'changed' : 'removed';
3092  $notificationResult = $this->sendMail(
3093  wfMessage( 'notificationemail_subject_' . $change )->text(),
3094  wfMessage( 'notificationemail_body_' . $change,
3095  $this->getRequest()->getIP(),
3096  $this->getName(),
3097  $str )->text()
3098  );
3099  }
3100  }
3101 
3102  $this->setEmail( $str );
3103 
3104  if ( $str !== '' && $wgEmailAuthentication ) {
3105  // Send a confirmation request to the new address if needed
3106  $result = $this->sendConfirmationMail( $type );
3107 
3108  if ( $notificationResult !== null ) {
3109  $result->merge( $notificationResult );
3110  }
3111 
3112  if ( $result->isGood() ) {
3113  // Say to the caller that a confirmation and notification mail has been sent
3114  $result->value = 'eauth';
3115  }
3116  } else {
3117  $result = Status::newGood( true );
3118  }
3119 
3120  return $result;
3121  }
3122 
3127  public function getRealName() {
3128  if ( !$this->isItemLoaded( 'realname' ) ) {
3129  $this->load();
3130  }
3131 
3132  return $this->mRealName;
3133  }
3134 
3139  public function setRealName( $str ) {
3140  $this->load();
3141  $this->mRealName = $str;
3142  }
3143 
3154  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3156  $this->loadOptions();
3157 
3158  # We want 'disabled' preferences to always behave as the default value for
3159  # users, even if they have set the option explicitly in their settings (ie they
3160  # set it, and then it was disabled removing their ability to change it). But
3161  # we don't want to erase the preferences in the database in case the preference
3162  # is re-enabled again. So don't touch $mOptions, just override the returned value
3163  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3164  return self::getDefaultOption( $oname );
3165  }
3166 
3167  if ( array_key_exists( $oname, $this->mOptions ) ) {
3168  return $this->mOptions[$oname];
3169  } else {
3170  return $defaultOverride;
3171  }
3172  }
3173 
3182  public function getOptions( $flags = 0 ) {
3184  $this->loadOptions();
3186 
3187  # We want 'disabled' preferences to always behave as the default value for
3188  # users, even if they have set the option explicitly in their settings (ie they
3189  # set it, and then it was disabled removing their ability to change it). But
3190  # we don't want to erase the preferences in the database in case the preference
3191  # is re-enabled again. So don't touch $mOptions, just override the returned value
3192  foreach ( $wgHiddenPrefs as $pref ) {
3193  $default = self::getDefaultOption( $pref );
3194  if ( $default !== null ) {
3195  $options[$pref] = $default;
3196  }
3197  }
3198 
3199  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3200  $options = array_diff_assoc( $options, self::getDefaultOptions() );
3201  }
3202 
3203  return $options;
3204  }
3205 
3213  public function getBoolOption( $oname ) {
3214  return (bool)$this->getOption( $oname );
3215  }
3216 
3225  public function getIntOption( $oname, $defaultOverride = 0 ) {
3226  $val = $this->getOption( $oname );
3227  if ( $val == '' ) {
3228  $val = $defaultOverride;
3229  }
3230  return intval( $val );
3231  }
3232 
3241  public function setOption( $oname, $val ) {
3242  $this->loadOptions();
3243 
3244  // Explicitly NULL values should refer to defaults
3245  if ( is_null( $val ) ) {
3246  $val = self::getDefaultOption( $oname );
3247  }
3248 
3249  $this->mOptions[$oname] = $val;
3250  }
3251 
3262  public function getTokenFromOption( $oname ) {
3264 
3265  $id = $this->getId();
3266  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3267  return false;
3268  }
3269 
3270  $token = $this->getOption( $oname );
3271  if ( !$token ) {
3272  // Default to a value based on the user token to avoid space
3273  // wasted on storing tokens for all users. When this option
3274  // is set manually by the user, only then is it stored.
3275  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3276  }
3277 
3278  return $token;
3279  }
3280 
3290  public function resetTokenFromOption( $oname ) {
3292  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3293  return false;
3294  }
3295 
3296  $token = MWCryptRand::generateHex( 40 );
3297  $this->setOption( $oname, $token );
3298  return $token;
3299  }
3300 
3324  public static function listOptionKinds() {
3325  return [
3326  'registered',
3327  'registered-multiselect',
3328  'registered-checkmatrix',
3329  'userjs',
3330  'special',
3331  'unused'
3332  ];
3333  }
3334 
3347  public function getOptionKinds( IContextSource $context, $options = null ) {
3348  $this->loadOptions();
3349  if ( $options === null ) {
3351  }
3352 
3353  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3354  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3355  $mapping = [];
3356 
3357  // Pull out the "special" options, so they don't get converted as
3358  // multiselect or checkmatrix.
3359  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3360  foreach ( $specialOptions as $name => $value ) {
3361  unset( $prefs[$name] );
3362  }
3363 
3364  // Multiselect and checkmatrix options are stored in the database with
3365  // one key per option, each having a boolean value. Extract those keys.
3366  $multiselectOptions = [];
3367  foreach ( $prefs as $name => $info ) {
3368  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3369  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3370  $opts = HTMLFormField::flattenOptions( $info['options'] );
3371  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
3372 
3373  foreach ( $opts as $value ) {
3374  $multiselectOptions["$prefix$value"] = true;
3375  }
3376 
3377  unset( $prefs[$name] );
3378  }
3379  }
3380  $checkmatrixOptions = [];
3381  foreach ( $prefs as $name => $info ) {
3382  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3383  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3384  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3385  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3386  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
3387 
3388  foreach ( $columns as $column ) {
3389  foreach ( $rows as $row ) {
3390  $checkmatrixOptions["$prefix$column-$row"] = true;
3391  }
3392  }
3393 
3394  unset( $prefs[$name] );
3395  }
3396  }
3397 
3398  // $value is ignored
3399  foreach ( $options as $key => $value ) {
3400  if ( isset( $prefs[$key] ) ) {
3401  $mapping[$key] = 'registered';
3402  } elseif ( isset( $multiselectOptions[$key] ) ) {
3403  $mapping[$key] = 'registered-multiselect';
3404  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3405  $mapping[$key] = 'registered-checkmatrix';
3406  } elseif ( isset( $specialOptions[$key] ) ) {
3407  $mapping[$key] = 'special';
3408  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3409  $mapping[$key] = 'userjs';
3410  } else {
3411  $mapping[$key] = 'unused';
3412  }
3413  }
3414 
3415  return $mapping;
3416  }
3417 
3432  public function resetOptions(
3433  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3434  IContextSource $context = null
3435  ) {
3436  $this->load();
3437  $defaultOptions = self::getDefaultOptions();
3438 
3439  if ( !is_array( $resetKinds ) ) {
3440  $resetKinds = [ $resetKinds ];
3441  }
3442 
3443  if ( in_array( 'all', $resetKinds ) ) {
3444  $newOptions = $defaultOptions;
3445  } else {
3446  if ( $context === null ) {
3448  }
3449 
3450  $optionKinds = $this->getOptionKinds( $context );
3451  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3452  $newOptions = [];
3453 
3454  // Use default values for the options that should be deleted, and
3455  // copy old values for the ones that shouldn't.
3456  foreach ( $this->mOptions as $key => $value ) {
3457  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3458  if ( array_key_exists( $key, $defaultOptions ) ) {
3459  $newOptions[$key] = $defaultOptions[$key];
3460  }
3461  } else {
3462  $newOptions[$key] = $value;
3463  }
3464  }
3465  }
3466 
3467  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3468 
3469  $this->mOptions = $newOptions;
3470  $this->mOptionsLoaded = true;
3471  }
3472 
3477  public function getDatePreference() {
3478  // Important migration for old data rows
3479  if ( is_null( $this->mDatePreference ) ) {
3480  global $wgLang;
3481  $value = $this->getOption( 'date' );
3482  $map = $wgLang->getDatePreferenceMigrationMap();
3483  if ( isset( $map[$value] ) ) {
3484  $value = $map[$value];
3485  }
3486  $this->mDatePreference = $value;
3487  }
3488  return $this->mDatePreference;
3489  }
3490 
3497  public function requiresHTTPS() {
3499  if ( $wgForceHTTPS ) {
3500  return true;
3501  }
3502  if ( !$wgSecureLogin ) {
3503  return false;
3504  } else {
3505  $https = $this->getBoolOption( 'prefershttps' );
3506  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3507  if ( $https ) {
3508  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3509  }
3510  return $https;
3511  }
3512  }
3513 
3519  public function getStubThreshold() {
3520  global $wgMaxArticleSize; # Maximum article size, in Kb
3521  $threshold = $this->getIntOption( 'stubthreshold' );
3522  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3523  // If they have set an impossible value, disable the preference
3524  // so we can use the parser cache again.
3525  $threshold = 0;
3526  }
3527  return $threshold;
3528  }
3529 
3534  public function getRights() {
3535  if ( is_null( $this->mRights ) ) {
3536  $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3537  Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3538 
3539  // Deny any rights denied by the user's session, unless this
3540  // endpoint has no sessions.
3541  if ( !defined( 'MW_NO_SESSION' ) ) {
3542  $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3543  if ( $allowedRights !== null ) {
3544  $this->mRights = array_intersect( $this->mRights, $allowedRights );
3545  }
3546  }
3547 
3548  // Force reindexation of rights when a hook has unset one of them
3549  $this->mRights = array_values( array_unique( $this->mRights ) );
3550 
3551  // If block disables login, we should also remove any
3552  // extra rights blocked users might have, in case the
3553  // blocked user has a pre-existing session (T129738).
3554  // This is checked here for cases where people only call
3555  // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3556  // to give a better error message in the common case.
3557  $config = RequestContext::getMain()->getConfig();
3558  if (
3559  $this->isLoggedIn() &&
3560  $config->get( 'BlockDisablesLogin' ) &&
3561  $this->isBlocked()
3562  ) {
3563  $anon = new User;
3564  $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3565  }
3566  }
3567  return $this->mRights;
3568  }
3569 
3575  public function getGroups() {
3576  $this->load();
3577  $this->loadGroups();
3578  return array_keys( $this->mGroupMemberships );
3579  }
3580 
3588  public function getGroupMemberships() {
3589  $this->load();
3590  $this->loadGroups();
3591  return $this->mGroupMemberships;
3592  }
3593 
3601  public function getEffectiveGroups( $recache = false ) {
3602  if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3603  $this->mEffectiveGroups = array_unique( array_merge(
3604  $this->getGroups(), // explicit groups
3605  $this->getAutomaticGroups( $recache ) // implicit groups
3606  ) );
3607  // Avoid PHP 7.1 warning of passing $this by reference
3608  $user = $this;
3609  // Hook for additional groups
3610  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3611  // Force reindexation of groups when a hook has unset one of them
3612  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3613  }
3614  return $this->mEffectiveGroups;
3615  }
3616 
3624  public function getAutomaticGroups( $recache = false ) {
3625  if ( $recache || is_null( $this->mImplicitGroups ) ) {
3626  $this->mImplicitGroups = [ '*' ];
3627  if ( $this->getId() ) {
3628  $this->mImplicitGroups[] = 'user';
3629 
3630  $this->mImplicitGroups = array_unique( array_merge(
3631  $this->mImplicitGroups,
3633  ) );
3634  }
3635  if ( $recache ) {
3636  // Assure data consistency with rights/groups,
3637  // as getEffectiveGroups() depends on this function
3638  $this->mEffectiveGroups = null;
3639  }
3640  }
3641  return $this->mImplicitGroups;
3642  }
3643 
3653  public function getFormerGroups() {
3654  $this->load();
3655 
3656  if ( is_null( $this->mFormerGroups ) ) {
3657  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3658  ? wfGetDB( DB_MASTER )
3659  : wfGetDB( DB_REPLICA );
3660  $res = $db->select( 'user_former_groups',
3661  [ 'ufg_group' ],
3662  [ 'ufg_user' => $this->mId ],
3663  __METHOD__ );
3664  $this->mFormerGroups = [];
3665  foreach ( $res as $row ) {
3666  $this->mFormerGroups[] = $row->ufg_group;
3667  }
3668  }
3669 
3670  return $this->mFormerGroups;
3671  }
3672 
3677  public function getEditCount() {
3678  if ( !$this->getId() ) {
3679  return null;
3680  }
3681 
3682  if ( $this->mEditCount === null ) {
3683  /* Populate the count, if it has not been populated yet */
3684  $dbr = wfGetDB( DB_REPLICA );
3685  // check if the user_editcount field has been initialized
3686  $count = $dbr->selectField(
3687  'user', 'user_editcount',
3688  [ 'user_id' => $this->mId ],
3689  __METHOD__
3690  );
3691 
3692  if ( $count === null ) {
3693  // it has not been initialized. do so.
3694  $count = $this->initEditCount();
3695  }
3696  $this->mEditCount = $count;
3697  }
3698  return (int)$this->mEditCount;
3699  }
3700 
3712  public function addGroup( $group, $expiry = null ) {
3713  $this->load();
3714  $this->loadGroups();
3715 
3716  if ( $expiry ) {
3717  $expiry = wfTimestamp( TS_MW, $expiry );
3718  }
3719 
3720  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3721  return false;
3722  }
3723 
3724  // create the new UserGroupMembership and put it in the DB
3725  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3726  if ( !$ugm->insert( true ) ) {
3727  return false;
3728  }
3729 
3730  $this->mGroupMemberships[$group] = $ugm;
3731 
3732  // Refresh the groups caches, and clear the rights cache so it will be
3733  // refreshed on the next call to $this->getRights().
3734  $this->getEffectiveGroups( true );
3735  $this->mRights = null;
3736 
3737  $this->invalidateCache();
3738 
3739  return true;
3740  }
3741 
3748  public function removeGroup( $group ) {
3749  $this->load();
3750 
3751  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3752  return false;
3753  }
3754 
3755  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3756  // delete the membership entry
3757  if ( !$ugm || !$ugm->delete() ) {
3758  return false;
3759  }
3760 
3761  $this->loadGroups();
3762  unset( $this->mGroupMemberships[$group] );
3763 
3764  // Refresh the groups caches, and clear the rights cache so it will be
3765  // refreshed on the next call to $this->getRights().
3766  $this->getEffectiveGroups( true );
3767  $this->mRights = null;
3768 
3769  $this->invalidateCache();
3770 
3771  return true;
3772  }
3773 
3782  public function isRegistered() {
3783  return $this->getId() != 0;
3784  }
3785 
3792  public function isLoggedIn() {
3793  return $this->isRegistered();
3794  }
3795 
3800  public function isAnon() {
3801  return !$this->isLoggedIn();
3802  }
3803 
3808  public function isBot() {
3809  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3810  return true;
3811  }
3812 
3813  $isBot = false;
3814  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3815 
3816  return $isBot;
3817  }
3818 
3825  public function isAllowedAny() {
3826  $permissions = func_get_args();
3827  foreach ( $permissions as $permission ) {
3828  if ( $this->isAllowed( $permission ) ) {
3829  return true;
3830  }
3831  }
3832  return false;
3833  }
3834 
3840  public function isAllowedAll() {
3841  $permissions = func_get_args();
3842  foreach ( $permissions as $permission ) {
3843  if ( !$this->isAllowed( $permission ) ) {
3844  return false;
3845  }
3846  }
3847  return true;
3848  }
3849 
3855  public function isAllowed( $action = '' ) {
3856  if ( $action === '' ) {
3857  return true; // In the spirit of DWIM
3858  }
3859  // Use strict parameter to avoid matching numeric 0 accidentally inserted
3860  // by misconfiguration: 0 == 'foo'
3861  return in_array( $action, $this->getRights(), true );
3862  }
3863 
3868  public function useRCPatrol() {
3870  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3871  }
3872 
3877  public function useNPPatrol() {
3879  return (
3880  ( $wgUseRCPatrol || $wgUseNPPatrol )
3881  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3882  );
3883  }
3884 
3889  public function useFilePatrol() {
3891  return (
3892  ( $wgUseRCPatrol || $wgUseFilePatrol )
3893  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3894  );
3895  }
3896 
3902  public function getRequest() {
3903  if ( $this->mRequest ) {
3904  return $this->mRequest;
3905  } else {
3907  return $wgRequest;
3908  }
3909  }
3910 
3919  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3920  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3921  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3922  }
3923  return false;
3924  }
3925 
3933  public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3934  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3935  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3936  $this,
3937  [ $title->getSubjectPage(), $title->getTalkPage() ]
3938  );
3939  }
3940  $this->invalidateCache();
3941  }
3942 
3950  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3951  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3952  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3953  $store->removeWatch( $this, $title->getSubjectPage() );
3954  $store->removeWatch( $this, $title->getTalkPage() );
3955  }
3956  $this->invalidateCache();
3957  }
3958 
3967  public function clearNotification( &$title, $oldid = 0 ) {
3969 
3970  // Do nothing if the database is locked to writes
3971  if ( wfReadOnly() ) {
3972  return;
3973  }
3974 
3975  // Do nothing if not allowed to edit the watchlist
3976  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3977  return;
3978  }
3979 
3980  // If we're working on user's talk page, we should update the talk page message indicator
3981  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3982  // Avoid PHP 7.1 warning of passing $this by reference
3983  $user = $this;
3984  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3985  return;
3986  }
3987 
3988  // Try to update the DB post-send and only if needed...
3989  DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3990  if ( !$this->getNewtalk() ) {
3991  return; // no notifications to clear
3992  }
3993 
3994  // Delete the last notifications (they stack up)
3995  $this->setNewtalk( false );
3996 
3997  // If there is a new, unseen, revision, use its timestamp
3998  $nextid = $oldid
3999  ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
4000  : null;
4001  if ( $nextid ) {
4002  $this->setNewtalk( true, Revision::newFromId( $nextid ) );
4003  }
4004  } );
4005  }
4006 
4007  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4008  return;
4009  }
4010 
4011  if ( $this->isAnon() ) {
4012  // Nothing else to do...
4013  return;
4014  }
4015 
4016  // Only update the timestamp if the page is being watched.
4017  // The query to find out if it is watched is cached both in memcached and per-invocation,
4018  // and when it does have to be executed, it can be on a replica DB
4019  // If this is the user's newtalk page, we always update the timestamp
4020  $force = '';
4021  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
4022  $force = 'force';
4023  }
4024 
4025  MediaWikiServices::getInstance()->getWatchedItemStore()
4026  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
4027  }
4028 
4035  public function clearAllNotifications() {
4037  // Do nothing if not allowed to edit the watchlist
4038  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
4039  return;
4040  }
4041 
4042  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4043  $this->setNewtalk( false );
4044  return;
4045  }
4046 
4047  $id = $this->getId();
4048  if ( !$id ) {
4049  return;
4050  }
4051 
4052  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
4053  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
4054 
4055  // We also need to clear here the "you have new message" notification for the own
4056  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
4057  }
4058 
4064  public function getExperienceLevel() {
4069 
4070  if ( $this->isAnon() ) {
4071  return false;
4072  }
4073 
4074  $editCount = $this->getEditCount();
4075  $registration = $this->getRegistration();
4076  $now = time();
4077  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
4078  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
4079 
4080  if (
4081  $editCount < $wgLearnerEdits ||
4082  $registration > $learnerRegistration
4083  ) {
4084  return 'newcomer';
4085  } elseif (
4086  $editCount > $wgExperiencedUserEdits &&
4087  $registration <= $experiencedRegistration
4088  ) {
4089  return 'experienced';
4090  } else {
4091  return 'learner';
4092  }
4093  }
4094 
4103  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
4104  $this->load();
4105  if ( 0 == $this->mId ) {
4106  return;
4107  }
4108 
4109  $session = $this->getRequest()->getSession();
4110  if ( $request && $session->getRequest() !== $request ) {
4111  $session = $session->sessionWithRequest( $request );
4112  }
4113  $delay = $session->delaySave();
4114 
4115  if ( !$session->getUser()->equals( $this ) ) {
4116  if ( !$session->canSetUser() ) {
4118  ->warning( __METHOD__ .
4119  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
4120  );
4121  return;
4122  }
4123  $session->setUser( $this );
4124  }
4125 
4126  $session->setRememberUser( $rememberMe );
4127  if ( $secure !== null ) {
4128  $session->setForceHTTPS( $secure );
4129  }
4130 
4131  $session->persist();
4132 
4133  ScopedCallback::consume( $delay );
4134  }
4135 
4139  public function logout() {
4140  // Avoid PHP 7.1 warning of passing $this by reference
4141  $user = $this;
4142  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
4143  $this->doLogout();
4144  }
4145  }
4146 
4151  public function doLogout() {
4152  $session = $this->getRequest()->getSession();
4153  if ( !$session->canSetUser() ) {
4155  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
4156  $error = 'immutable';
4157  } elseif ( !$session->getUser()->equals( $this ) ) {
4159  ->warning( __METHOD__ .
4160  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4161  );
4162  // But we still may as well make this user object anon
4163  $this->clearInstanceCache( 'defaults' );
4164  $error = 'wronguser';
4165  } else {
4166  $this->clearInstanceCache( 'defaults' );
4167  $delay = $session->delaySave();
4168  $session->unpersist(); // Clear cookies (T127436)
4169  $session->setLoggedOutTimestamp( time() );
4170  $session->setUser( new User );
4171  $session->set( 'wsUserID', 0 ); // Other code expects this
4172  $session->resetAllTokens();
4173  ScopedCallback::consume( $delay );
4174  $error = false;
4175  }
4176  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4177  'event' => 'logout',
4178  'successful' => $error === false,
4179  'status' => $error ?: 'success',
4180  ] );
4181  }
4182 
4187  public function saveSettings() {
4188  if ( wfReadOnly() ) {
4189  // @TODO: caller should deal with this instead!
4190  // This should really just be an exception.
4192  null,
4193  "Could not update user with ID '{$this->mId}'; DB is read-only."
4194  ) );
4195  return;
4196  }
4197 
4198  $this->load();
4199  if ( 0 == $this->mId ) {
4200  return; // anon
4201  }
4202 
4203  // Get a new user_touched that is higher than the old one.
4204  // This will be used for a CAS check as a last-resort safety
4205  // check against race conditions and replica DB lag.
4206  $newTouched = $this->newTouchedTimestamp();
4207 
4208  $dbw = wfGetDB( DB_MASTER );
4209  $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
4211 
4212  $dbw->update( 'user',
4213  [ /* SET */
4214  'user_name' => $this->mName,
4215  'user_real_name' => $this->mRealName,
4216  'user_email' => $this->mEmail,
4217  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4218  'user_touched' => $dbw->timestamp( $newTouched ),
4219  'user_token' => strval( $this->mToken ),
4220  'user_email_token' => $this->mEmailToken,
4221  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4222  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4223  'user_id' => $this->mId,
4224  ] ), $fname
4225  );
4226 
4227  if ( !$dbw->affectedRows() ) {
4228  // Maybe the problem was a missed cache update; clear it to be safe
4229  $this->clearSharedCache( 'refresh' );
4230  // User was changed in the meantime or loaded with stale data
4231  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4232  throw new MWException(
4233  "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
4234  " the version of the user to be saved is older than the current version."
4235  );
4236  }
4237 
4239  $dbw->update(
4240  'actor',
4241  [ 'actor_name' => $this->mName ],
4242  [ 'actor_user' => $this->mId ],
4243  $fname
4244  );
4245  }
4246  } );
4247 
4248  $this->mTouched = $newTouched;
4249  $this->saveOptions();
4250 
4251  Hooks::run( 'UserSaveSettings', [ $this ] );
4252  $this->clearSharedCache();
4253  $this->getUserPage()->invalidateCache();
4254  }
4255 
4262  public function idForName( $flags = 0 ) {
4263  $s = trim( $this->getName() );
4264  if ( $s === '' ) {
4265  return 0;
4266  }
4267 
4268  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4269  ? wfGetDB( DB_MASTER )
4270  : wfGetDB( DB_REPLICA );
4271 
4272  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4273  ? [ 'LOCK IN SHARE MODE' ]
4274  : [];
4275 
4276  $id = $db->selectField( 'user',
4277  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4278 
4279  return (int)$id;
4280  }
4281 
4297  public static function createNew( $name, $params = [] ) {
4298  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4299  if ( isset( $params[$field] ) ) {
4300  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4301  unset( $params[$field] );
4302  }
4303  }
4304 
4305  $user = new User;
4306  $user->load();
4307  $user->setToken(); // init token
4308  if ( isset( $params['options'] ) ) {
4309  $user->mOptions = $params['options'] + (array)$user->mOptions;
4310  unset( $params['options'] );
4311  }
4312  $dbw = wfGetDB( DB_MASTER );
4313 
4314  $noPass = PasswordFactory::newInvalidPassword()->toString();
4315 
4316  $fields = [
4317  'user_name' => $name,
4318  'user_password' => $noPass,
4319  'user_newpassword' => $noPass,
4320  'user_email' => $user->mEmail,
4321  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4322  'user_real_name' => $user->mRealName,
4323  'user_token' => strval( $user->mToken ),
4324  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4325  'user_editcount' => 0,
4326  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4327  ];
4328  foreach ( $params as $name => $value ) {
4329  $fields["user_$name"] = $value;
4330  }
4331 
4332  return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
4333  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4334  if ( $dbw->affectedRows() ) {
4335  $newUser = self::newFromId( $dbw->insertId() );
4336  // Load the user from master to avoid replica lag
4337  $newUser->load( self::READ_LATEST );
4338  $newUser->updateActorId( $dbw );
4339  } else {
4340  $newUser = null;
4341  }
4342  return $newUser;
4343  } );
4344  }
4345 
4372  public function addToDatabase() {
4373  $this->load();
4374  if ( !$this->mToken ) {
4375  $this->setToken(); // init token
4376  }
4377 
4378  if ( !is_string( $this->mName ) ) {
4379  throw new RuntimeException( "User name field is not set." );
4380  }
4381 
4382  $this->mTouched = $this->newTouchedTimestamp();
4383 
4384  $dbw = wfGetDB( DB_MASTER );
4385  $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
4386  $noPass = PasswordFactory::newInvalidPassword()->toString();
4387  $dbw->insert( 'user',
4388  [
4389  'user_name' => $this->mName,
4390  'user_password' => $noPass,
4391  'user_newpassword' => $noPass,
4392  'user_email' => $this->mEmail,
4393  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4394  'user_real_name' => $this->mRealName,
4395  'user_token' => strval( $this->mToken ),
4396  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4397  'user_editcount' => 0,
4398  'user_touched' => $dbw->timestamp( $this->mTouched ),
4399  ], $fname,
4400  [ 'IGNORE' ]
4401  );
4402  if ( !$dbw->affectedRows() ) {
4403  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4404  $this->mId = $dbw->selectField(
4405  'user',
4406  'user_id',
4407  [ 'user_name' => $this->mName ],
4408  __METHOD__,
4409  [ 'LOCK IN SHARE MODE' ]
4410  );
4411  $loaded = false;
4412  if ( $this->mId ) {
4413  if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4414  $loaded = true;
4415  }
4416  }
4417  if ( !$loaded ) {
4418  throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
4419  "to insert user '{$this->mName}' row, but it was not present in select!" );
4420  }
4421  return Status::newFatal( 'userexists' );
4422  }
4423  $this->mId = $dbw->insertId();
4424  self::$idCacheByName[$this->mName] = $this->mId;
4425  $this->updateActorId( $dbw );
4426 
4427  return Status::newGood();
4428  } );
4429  if ( !$status->isGood() ) {
4430  return $status;
4431  }
4432 
4433  // Clear instance cache other than user table data and actor, which is already accurate
4434  $this->clearInstanceCache();
4435 
4436  $this->saveOptions();
4437  return Status::newGood();
4438  }
4439 
4444  private function updateActorId( IDatabase $dbw ) {
4446 
4448  $dbw->insert(
4449  'actor',
4450  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4451  __METHOD__
4452  );
4453  $this->mActorId = (int)$dbw->insertId();
4454  }
4455  }
4456 
4462  public function spreadAnyEditBlock() {
4463  if ( $this->isLoggedIn() && $this->isBlocked() ) {
4464  return $this->spreadBlock();
4465  }
4466 
4467  return false;
4468  }
4469 
4475  protected function spreadBlock() {
4476  wfDebug( __METHOD__ . "()\n" );
4477  $this->load();
4478  if ( $this->mId == 0 ) {
4479  return false;
4480  }
4481 
4482  $userblock = Block::newFromTarget( $this->getName() );
4483  if ( !$userblock ) {
4484  return false;
4485  }
4486 
4487  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4488  }
4489 
4494  public function isBlockedFromCreateAccount() {
4495  $this->getBlockedStatus();
4496  if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4497  return $this->mBlock;
4498  }
4499 
4500  # T15611: if the IP address the user is trying to create an account from is
4501  # blocked with createaccount disabled, prevent new account creation there even
4502  # when the user is logged in
4503  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4504  $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4505  }
4506  return $this->mBlockedFromCreateAccount instanceof Block
4507  && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4508  ? $this->mBlockedFromCreateAccount
4509  : false;
4510  }
4511 
4516  public function isBlockedFromEmailuser() {
4517  $this->getBlockedStatus();
4518  return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4519  }
4520 
4525  public function isAllowedToCreateAccount() {
4526  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4527  }
4528 
4534  public function getUserPage() {
4535  return Title::makeTitle( NS_USER, $this->getName() );
4536  }
4537 
4543  public function getTalkPage() {
4544  $title = $this->getUserPage();
4545  return $title->getTalkPage();
4546  }
4547 
4553  public function isNewbie() {
4554  return !$this->isAllowed( 'autoconfirmed' );
4555  }
4556 
4563  public function checkPassword( $password ) {
4564  $manager = AuthManager::singleton();
4565  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4566  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4567  [
4568  'username' => $this->getName(),
4569  'password' => $password,
4570  ]
4571  );
4572  $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4573  switch ( $res->status ) {
4574  case AuthenticationResponse::PASS:
4575  return true;
4576  case AuthenticationResponse::FAIL:
4577  // Hope it's not a PreAuthenticationProvider that failed...
4579  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4580  return false;
4581  default:
4582  throw new BadMethodCallException(
4583  'AuthManager returned a response unsupported by ' . __METHOD__
4584  );
4585  }
4586  }
4587 
4596  public function checkTemporaryPassword( $plaintext ) {
4597  // Can't check the temporary password individually.
4598  return $this->checkPassword( $plaintext );
4599  }
4600 
4612  public function getEditTokenObject( $salt = '', $request = null ) {
4613  if ( $this->isAnon() ) {
4614  return new LoggedOutEditToken();
4615  }
4616 
4617  if ( !$request ) {
4618  $request = $this->getRequest();
4619  }
4620  return $request->getSession()->getToken( $salt );
4621  }
4622 
4636  public function getEditToken( $salt = '', $request = null ) {
4637  return $this->getEditTokenObject( $salt, $request )->toString();
4638  }
4639 
4652  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4653  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4654  }
4655 
4666  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4667  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4668  return $this->matchEditToken( $val, $salt, $request, $maxage );
4669  }
4670 
4678  public function sendConfirmationMail( $type = 'created' ) {
4679  global $wgLang;
4680  $expiration = null; // gets passed-by-ref and defined in next line.
4681  $token = $this->confirmationToken( $expiration );
4682  $url = $this->confirmationTokenUrl( $token );
4683  $invalidateURL = $this->invalidationTokenUrl( $token );
4684  $this->saveSettings();
4685 
4686  if ( $type == 'created' || $type === false ) {
4687  $message = 'confirmemail_body';
4688  } elseif ( $type === true ) {
4689  $message = 'confirmemail_body_changed';
4690  } else {
4691  // Messages: confirmemail_body_changed, confirmemail_body_set
4692  $message = 'confirmemail_body_' . $type;
4693  }
4694 
4695  return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4696  wfMessage( $message,
4697  $this->getRequest()->getIP(),
4698  $this->getName(),
4699  $url,
4700  $wgLang->userTimeAndDate( $expiration, $this ),
4701  $invalidateURL,
4702  $wgLang->userDate( $expiration, $this ),
4703  $wgLang->userTime( $expiration, $this ) )->text() );
4704  }
4705 
4717  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4719 
4720  if ( $from instanceof User ) {
4721  $sender = MailAddress::newFromUser( $from );
4722  } else {
4723  $sender = new MailAddress( $wgPasswordSender,
4724  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4725  }
4726  $to = MailAddress::newFromUser( $this );
4727 
4728  return UserMailer::send( $to, $sender, $subject, $body, [
4729  'replyTo' => $replyto,
4730  ] );
4731  }
4732 
4743  protected function confirmationToken( &$expiration ) {
4745  $now = time();
4746  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4747  $expiration = wfTimestamp( TS_MW, $expires );
4748  $this->load();
4749  $token = MWCryptRand::generateHex( 32 );
4750  $hash = md5( $token );
4751  $this->mEmailToken = $hash;
4752  $this->mEmailTokenExpires = $expiration;
4753  return $token;
4754  }
4755 
4761  protected function confirmationTokenUrl( $token ) {
4762  return $this->getTokenUrl( 'ConfirmEmail', $token );
4763  }
4764 
4770  protected function invalidationTokenUrl( $token ) {
4771  return $this->getTokenUrl( 'InvalidateEmail', $token );
4772  }
4773 
4788  protected function getTokenUrl( $page, $token ) {
4789  // Hack to bypass localization of 'Special:'
4790  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4791  return $title->getCanonicalURL();
4792  }
4793 
4801  public function confirmEmail() {
4802  // Check if it's already confirmed, so we don't touch the database
4803  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4804  if ( !$this->isEmailConfirmed() ) {
4806  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4807  }
4808  return true;
4809  }
4810 
4818  public function invalidateEmail() {
4819  $this->load();
4820  $this->mEmailToken = null;
4821  $this->mEmailTokenExpires = null;
4822  $this->setEmailAuthenticationTimestamp( null );
4823  $this->mEmail = '';
4824  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4825  return true;
4826  }
4827 
4832  public function setEmailAuthenticationTimestamp( $timestamp ) {
4833  $this->load();
4834  $this->mEmailAuthenticated = $timestamp;
4835  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4836  }
4837 
4843  public function canSendEmail() {
4845  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4846  return false;
4847  }
4848  $canSend = $this->isEmailConfirmed();
4849  // Avoid PHP 7.1 warning of passing $this by reference
4850  $user = $this;
4851  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4852  return $canSend;
4853  }
4854 
4860  public function canReceiveEmail() {
4861  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4862  }
4863 
4874  public function isEmailConfirmed() {
4876  $this->load();
4877  // Avoid PHP 7.1 warning of passing $this by reference
4878  $user = $this;
4879  $confirmed = true;
4880  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4881  if ( $this->isAnon() ) {
4882  return false;
4883  }
4884  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4885  return false;
4886  }
4888  return false;
4889  }
4890  return true;
4891  } else {
4892  return $confirmed;
4893  }
4894  }
4895 
4900  public function isEmailConfirmationPending() {
4902  return $wgEmailAuthentication &&
4903  !$this->isEmailConfirmed() &&
4904  $this->mEmailToken &&
4905  $this->mEmailTokenExpires > wfTimestamp();
4906  }
4907 
4915  public function getRegistration() {
4916  if ( $this->isAnon() ) {
4917  return false;
4918  }
4919  $this->load();
4920  return $this->mRegistration;
4921  }
4922 
4929  public function getFirstEditTimestamp() {
4930  if ( $this->getId() == 0 ) {
4931  return false; // anons
4932  }
4933  $dbr = wfGetDB( DB_REPLICA );
4934  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4935  $time = $dbr->selectField(
4936  [ 'revision' ] + $actorWhere['tables'],
4937  'rev_timestamp',
4938  [ $actorWhere['conds'] ],
4939  __METHOD__,
4940  [ 'ORDER BY' => 'rev_timestamp ASC' ],
4941  $actorWhere['joins']
4942  );
4943  if ( !$time ) {
4944  return false; // no edits
4945  }
4946  return wfTimestamp( TS_MW, $time );
4947  }
4948 
4955  public static function getGroupPermissions( $groups ) {
4957  $rights = [];
4958  // grant every granted permission first
4959  foreach ( $groups as $group ) {
4960  if ( isset( $wgGroupPermissions[$group] ) ) {
4961  $rights = array_merge( $rights,
4962  // array_filter removes empty items
4963  array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4964  }
4965  }
4966  // now revoke the revoked permissions
4967  foreach ( $groups as $group ) {
4968  if ( isset( $wgRevokePermissions[$group] ) ) {
4969  $rights = array_diff( $rights,
4970  array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4971  }
4972  }
4973  return array_unique( $rights );
4974  }
4975 
4982  public static function getGroupsWithPermission( $role ) {
4984  $allowedGroups = [];
4985  foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4986  if ( self::groupHasPermission( $group, $role ) ) {
4987  $allowedGroups[] = $group;
4988  }
4989  }
4990  return $allowedGroups;
4991  }
4992 
5005  public static function groupHasPermission( $group, $role ) {
5007  return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
5008  && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
5009  }
5010 
5025  public static function isEveryoneAllowed( $right ) {
5027  static $cache = [];
5028 
5029  // Use the cached results, except in unit tests which rely on
5030  // being able change the permission mid-request
5031  if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
5032  return $cache[$right];
5033  }
5034 
5035  if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
5036  $cache[$right] = false;
5037  return false;
5038  }
5039 
5040  // If it's revoked anywhere, then everyone doesn't have it
5041  foreach ( $wgRevokePermissions as $rights ) {
5042  if ( isset( $rights[$right] ) && $rights[$right] ) {
5043  $cache[$right] = false;
5044  return false;
5045  }
5046  }
5047 
5048  // Remove any rights that aren't allowed to the global-session user,
5049  // unless there are no sessions for this endpoint.
5050  if ( !defined( 'MW_NO_SESSION' ) ) {
5051  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
5052  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
5053  $cache[$right] = false;
5054  return false;
5055  }
5056  }
5057 
5058  // Allow extensions to say false
5059  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
5060  $cache[$right] = false;
5061  return false;
5062  }
5063 
5064  $cache[$right] = true;
5065  return true;
5066  }
5067 
5075  public static function getGroupName( $group ) {
5076  wfDeprecated( __METHOD__, '1.29' );
5077  return UserGroupMembership::getGroupName( $group );
5078  }
5079 
5088  public static function getGroupMember( $group, $username = '#' ) {
5089  wfDeprecated( __METHOD__, '1.29' );
5091  }
5092 
5099  public static function getAllGroups() {
5101  return array_diff(
5102  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
5103  self::getImplicitGroups()
5104  );
5105  }
5106 
5111  public static function getAllRights() {
5112  if ( self::$mAllRights === false ) {
5114  if ( count( $wgAvailableRights ) ) {
5115  self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
5116  } else {
5117  self::$mAllRights = self::$mCoreRights;
5118  }
5119  Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
5120  }
5121  return self::$mAllRights;
5122  }
5123 
5128  public static function getImplicitGroups() {
5130 
5131  $groups = $wgImplicitGroups;
5132  # Deprecated, use $wgImplicitGroups instead
5133  Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
5134 
5135  return $groups;
5136  }
5137 
5145  public static function getGroupPage( $group ) {
5146  wfDeprecated( __METHOD__, '1.29' );
5147  return UserGroupMembership::getGroupPage( $group );
5148  }
5149 
5160  public static function makeGroupLinkHTML( $group, $text = '' ) {
5161  wfDeprecated( __METHOD__, '1.29' );
5162 
5163  if ( $text == '' ) {
5164  $text = UserGroupMembership::getGroupName( $group );
5165  }
5167  if ( $title ) {
5168  return MediaWikiServices::getInstance()
5169  ->getLinkRenderer()->makeLink( $title, $text );
5170  } else {
5171  return htmlspecialchars( $text );
5172  }
5173  }
5174 
5185  public static function makeGroupLinkWiki( $group, $text = '' ) {
5186  wfDeprecated( __METHOD__, '1.29' );
5187 
5188  if ( $text == '' ) {
5189  $text = UserGroupMembership::getGroupName( $group );
5190  }
5192  if ( $title ) {
5193  $page = $title->getFullText();
5194  return "[[$page|$text]]";
5195  } else {
5196  return $text;
5197  }
5198  }
5199 
5209  public static function changeableByGroup( $group ) {
5211 
5212  $groups = [
5213  'add' => [],
5214  'remove' => [],
5215  'add-self' => [],
5216  'remove-self' => []
5217  ];
5218 
5219  if ( empty( $wgAddGroups[$group] ) ) {
5220  // Don't add anything to $groups
5221  } elseif ( $wgAddGroups[$group] === true ) {
5222  // You get everything
5223  $groups['add'] = self::getAllGroups();
5224  } elseif ( is_array( $wgAddGroups[$group] ) ) {
5225  $groups['add'] = $wgAddGroups[$group];
5226  }
5227 
5228  // Same thing for remove
5229  if ( empty( $wgRemoveGroups[$group] ) ) {
5230  // Do nothing
5231  } elseif ( $wgRemoveGroups[$group] === true ) {
5232  $groups['remove'] = self::getAllGroups();
5233  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5234  $groups['remove'] = $wgRemoveGroups[$group];
5235  }
5236 
5237  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5238  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5239  foreach ( $wgGroupsAddToSelf as $key => $value ) {
5240  if ( is_int( $key ) ) {
5241  $wgGroupsAddToSelf['user'][] = $value;
5242  }
5243  }
5244  }
5245 
5246  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5247  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5248  if ( is_int( $key ) ) {
5249  $wgGroupsRemoveFromSelf['user'][] = $value;
5250  }
5251  }
5252  }
5253 
5254  // Now figure out what groups the user can add to him/herself
5255  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5256  // Do nothing
5257  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5258  // No idea WHY this would be used, but it's there
5259  $groups['add-self'] = self::getAllGroups();
5260  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5261  $groups['add-self'] = $wgGroupsAddToSelf[$group];
5262  }
5263 
5264  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5265  // Do nothing
5266  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5267  $groups['remove-self'] = self::getAllGroups();
5268  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5269  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5270  }
5271 
5272  return $groups;
5273  }
5274 
5282  public function changeableGroups() {
5283  if ( $this->isAllowed( 'userrights' ) ) {
5284  // This group gives the right to modify everything (reverse-
5285  // compatibility with old "userrights lets you change
5286  // everything")
5287  // Using array_merge to make the groups reindexed
5288  $all = array_merge( self::getAllGroups() );
5289  return [
5290  'add' => $all,
5291  'remove' => $all,
5292  'add-self' => [],
5293  'remove-self' => []
5294  ];
5295  }
5296 
5297  // Okay, it's not so simple, we will have to go through the arrays
5298  $groups = [
5299  'add' => [],
5300  'remove' => [],
5301  'add-self' => [],
5302  'remove-self' => []
5303  ];
5304  $addergroups = $this->getEffectiveGroups();
5305 
5306  foreach ( $addergroups as $addergroup ) {
5307  $groups = array_merge_recursive(
5308  $groups, $this->changeableByGroup( $addergroup )
5309  );
5310  $groups['add'] = array_unique( $groups['add'] );
5311  $groups['remove'] = array_unique( $groups['remove'] );
5312  $groups['add-self'] = array_unique( $groups['add-self'] );
5313  $groups['remove-self'] = array_unique( $groups['remove-self'] );
5314  }
5315  return $groups;
5316  }
5317 
5324  public function incEditCount() {
5325  wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
5326  function () {
5327  $this->incEditCountImmediate();
5328  },
5329  __METHOD__
5330  );
5331  }
5332 
5338  public function incEditCountImmediate() {
5339  if ( $this->isAnon() ) {
5340  return;
5341  }
5342 
5343  $dbw = wfGetDB( DB_MASTER );
5344  // No rows will be "affected" if user_editcount is NULL
5345  $dbw->update(
5346  'user',
5347  [ 'user_editcount=user_editcount+1' ],
5348  [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
5349  __METHOD__
5350  );
5351  // Lazy initialization check...
5352  if ( $dbw->affectedRows() == 0 ) {
5353  // Now here's a goddamn hack...
5354  $dbr = wfGetDB( DB_REPLICA );
5355  if ( $dbr !== $dbw ) {
5356  // If we actually have a replica DB server, the count is
5357  // at least one behind because the current transaction
5358  // has not been committed and replicated.
5359  $this->mEditCount = $this->initEditCount( 1 );
5360  } else {
5361  // But if DB_REPLICA is selecting the master, then the
5362  // count we just read includes the revision that was
5363  // just added in the working transaction.
5364  $this->mEditCount = $this->initEditCount();
5365  }
5366  } else {
5367  if ( $this->mEditCount === null ) {
5368  $this->getEditCount();
5369  $dbr = wfGetDB( DB_REPLICA );
5370  $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
5371  } else {
5372  $this->mEditCount++;
5373  }
5374  }
5375  // Edit count in user cache too
5376  $this->invalidateCache();
5377  }
5378 
5385  protected function initEditCount( $add = 0 ) {
5386  // Pull from a replica DB to be less cruel to servers
5387  // Accuracy isn't the point anyway here
5388  $dbr = wfGetDB( DB_REPLICA );
5389  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5390  $count = (int)$dbr->selectField(
5391  [ 'revision' ] + $actorWhere['tables'],
5392  'COUNT(*)',
5393  [ $actorWhere['conds'] ],
5394  __METHOD__,
5395  [],
5396  $actorWhere['joins']
5397  );
5398  $count = $count + $add;
5399 
5400  $dbw = wfGetDB( DB_MASTER );
5401  $dbw->update(
5402  'user',
5403  [ 'user_editcount' => $count ],
5404  [ 'user_id' => $this->getId() ],
5405  __METHOD__
5406  );
5407 
5408  return $count;
5409  }
5410 
5418  public static function getRightDescription( $right ) {
5419  $key = "right-$right";
5420  $msg = wfMessage( $key );
5421  return $msg->isDisabled() ? $right : $msg->text();
5422  }
5423 
5431  public static function getGrantName( $grant ) {
5432  $key = "grant-$grant";
5433  $msg = wfMessage( $key );
5434  return $msg->isDisabled() ? $grant : $msg->text();
5435  }
5436 
5457  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5458  return true; // disabled
5459  }
5460 
5469  public function addNewUserLogEntryAutoCreate() {
5470  $this->addNewUserLogEntry( 'autocreate' );
5471 
5472  return true;
5473  }
5474 
5480  protected function loadOptions( $data = null ) {
5482 
5483  $this->load();
5484 
5485  if ( $this->mOptionsLoaded ) {
5486  return;
5487  }
5488 
5489  $this->mOptions = self::getDefaultOptions();
5490 
5491  if ( !$this->getId() ) {
5492  // For unlogged-in users, load language/variant options from request.
5493  // There's no need to do it for logged-in users: they can set preferences,
5494  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5495  // so don't override user's choice (especially when the user chooses site default).
5496  $variant = $wgContLang->getDefaultVariant();
5497  $this->mOptions['variant'] = $variant;
5498  $this->mOptions['language'] = $variant;
5499  $this->mOptionsLoaded = true;
5500  return;
5501  }
5502 
5503  // Maybe load from the object
5504  if ( !is_null( $this->mOptionOverrides ) ) {
5505  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5506  foreach ( $this->mOptionOverrides as $key => $value ) {
5507  $this->mOptions[$key] = $value;
5508  }
5509  } else {
5510  if ( !is_array( $data ) ) {
5511  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5512  // Load from database
5513  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5514  ? wfGetDB( DB_MASTER )
5515  : wfGetDB( DB_REPLICA );
5516 
5517  $res = $dbr->select(
5518  'user_properties',
5519  [ 'up_property', 'up_value' ],
5520  [ 'up_user' => $this->getId() ],
5521  __METHOD__
5522  );
5523 
5524  $this->mOptionOverrides = [];
5525  $data = [];
5526  foreach ( $res as $row ) {
5527  // Convert '0' to 0. PHP's boolean conversion considers them both
5528  // false, but e.g. JavaScript considers the former as true.
5529  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5530  // and convert all values here.
5531  if ( $row->up_value === '0' ) {
5532  $row->up_value = 0;
5533  }
5534  $data[$row->up_property] = $row->up_value;
5535  }
5536  }
5537 
5538  // Convert the email blacklist from a new line delimited string
5539  // to an array of ids.
5540  if ( isset( $data['email-blacklist'] ) && $data['email-blacklist'] ) {
5541  $data['email-blacklist'] = array_map( 'intval', explode( "\n", $data['email-blacklist'] ) );
5542  }
5543 
5544  foreach ( $data as $property => $value ) {
5545  $this->mOptionOverrides[$property] = $value;
5546  $this->mOptions[$property] = $value;
5547  }
5548  }
5549 
5550  $this->mOptionsLoaded = true;
5551 
5552  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5553  }
5554 
5560  protected function saveOptions() {
5561  $this->loadOptions();
5562 
5563  // Not using getOptions(), to keep hidden preferences in database
5564  $saveOptions = $this->mOptions;
5565 
5566  // Convert usernames to ids.
5567  if ( isset( $this->mOptions['email-blacklist'] ) ) {
5568  if ( $this->mOptions['email-blacklist'] ) {
5569  $value = $this->mOptions['email-blacklist'];
5570  // Email Blacklist may be an array of ids or a string of new line
5571  // delimnated user names.
5572  if ( is_array( $value ) ) {
5573  $ids = array_filter( $value, 'is_numeric' );
5574  } else {
5575  $lookup = CentralIdLookup::factory();
5576  $ids = $lookup->centralIdsFromNames( explode( "\n", $value ), $this );
5577  }
5578  $this->mOptions['email-blacklist'] = $ids;
5579  $saveOptions['email-blacklist'] = implode( "\n", $this->mOptions['email-blacklist'] );
5580  } else {
5581  // If the blacklist is empty, set it to null rather than an empty string.
5582  $this->mOptions['email-blacklist'] = null;
5583  }
5584  }
5585 
5586  // Allow hooks to abort, for instance to save to a global profile.
5587  // Reset options to default state before saving.
5588  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5589  return;
5590  }
5591 
5592  $userId = $this->getId();
5593 
5594  $insert_rows = []; // all the new preference rows
5595  foreach ( $saveOptions as $key => $value ) {
5596  // Don't bother storing default values
5597  $defaultOption = self::getDefaultOption( $key );
5598  if ( ( $defaultOption === null && $value !== false && $value !== null )
5599  || $value != $defaultOption
5600  ) {
5601  $insert_rows[] = [
5602  'up_user' => $userId,
5603  'up_property' => $key,
5604  'up_value' => $value,
5605  ];
5606  }
5607  }
5608 
5609  $dbw = wfGetDB( DB_MASTER );
5610 
5611  $res = $dbw->select( 'user_properties',
5612  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5613 
5614  // Find prior rows that need to be removed or updated. These rows will
5615  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5616  $keysDelete = [];
5617  foreach ( $res as $row ) {
5618  if ( !isset( $saveOptions[$row->up_property] )
5619  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5620  ) {
5621  $keysDelete[] = $row->up_property;
5622  }
5623  }
5624 
5625  if ( count( $keysDelete ) ) {
5626  // Do the DELETE by PRIMARY KEY for prior rows.
5627  // In the past a very large portion of calls to this function are for setting
5628  // 'rememberpassword' for new accounts (a preference that has since been removed).
5629  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5630  // caused gap locks on [max user ID,+infinity) which caused high contention since
5631  // updates would pile up on each other as they are for higher (newer) user IDs.
5632  // It might not be necessary these days, but it shouldn't hurt either.
5633  $dbw->delete( 'user_properties',
5634  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5635  }
5636  // Insert the new preference rows
5637  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5638  }
5639 
5646  public static function selectFields() {
5647  wfDeprecated( __METHOD__, '1.31' );
5648  return [
5649  'user_id',
5650  'user_name',
5651  'user_real_name',
5652  'user_email',
5653  'user_touched',
5654  'user_token',
5655  'user_email_authenticated',
5656  'user_email_token',
5657  'user_email_token_expires',
5658  'user_registration',
5659  'user_editcount',
5660  ];
5661  }
5662 
5672  public static function getQueryInfo() {
5674 
5675  $ret = [
5676  'tables' => [ 'user' ],
5677  'fields' => [
5678  'user_id',
5679  'user_name',
5680  'user_real_name',
5681  'user_email',
5682  'user_touched',
5683  'user_token',
5684  'user_email_authenticated',
5685  'user_email_token',
5686  'user_email_token_expires',
5687  'user_registration',
5688  'user_editcount',
5689  ],
5690  'joins' => [],
5691  ];
5693  $ret['tables']['user_actor'] = 'actor';
5694  $ret['fields'][] = 'user_actor.actor_id';
5695  $ret['joins']['user_actor'] = [
5696  $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
5697  [ 'user_actor.actor_user = user_id' ]
5698  ];
5699  }
5700  return $ret;
5701  }
5702 
5710  static function newFatalPermissionDeniedStatus( $permission ) {
5711  global $wgLang;
5712 
5713  $groups = [];
5714  foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5715  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5716  }
5717 
5718  if ( $groups ) {
5719  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5720  } else {
5721  return Status::newFatal( 'badaccess-group0' );
5722  }
5723  }
5724 
5734  public function getInstanceForUpdate() {
5735  if ( !$this->getId() ) {
5736  return null; // anon
5737  }
5738 
5739  $user = self::newFromId( $this->getId() );
5740  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5741  return null;
5742  }
5743 
5744  return $user;
5745  }
5746 
5754  public function equals( User $user ) {
5755  return $this->getName() === $user->getName();
5756  }
5757 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1762
User\saveOptions
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5560
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1795
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:4923
Block\prevents
prevents( $action, $x=null)
Get/set whether the Block prevents a given action.
Definition: Block.php:1070
$user
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 account $user
Definition: hooks.txt:247
User\updateNewtalk
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2665
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:466
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:367
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2560
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:48
User\$mOptionsLoaded
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition: User.php:248
$wgProxyWhitelist
$wgProxyWhitelist
Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other methods mi...
Definition: DefaultSettings.php:5621
User\inDnsBlacklist
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1953
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:614
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:273
User\confirmationTokenUrl
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4761
$wgProxyList
$wgProxyList
Big list of banned IP addresses.
Definition: DefaultSettings.php:5977
User\clearSharedCache
clearSharedCache( $mode='changed')
Clear user data from memcached.
Definition: User.php:2763
wfCanIPUseHTTPS
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
Definition: GlobalFunctions.php:3308
User\$mToken
string $mToken
Definition: User.php:227
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:367
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1123
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
User\getId
getId()
Get the user's ID.
Definition: User.php:2457
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:114
User\makeGroupLinkWiki
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name.
Definition: User.php:5185
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3889
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2221
MWCryptHash\hmac
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
Definition: MWCryptHash.php:106
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:3800
User\$mBlock
Block $mBlock
Definition: User.php:301
$rows
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2783
Block\clearCookie
static clearCookie(WebResponse $response)
Unset the 'BlockID' cookie.
Definition: Block.php:1552
User\$mBlockreason
string $mBlockreason
Definition: User.php:281
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4788
User\isRegistered
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3782
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2520
User\$mImplicitGroups
array $mImplicitGroups
Definition: User.php:285
User\isLocallyBlockedProxy
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1999
wfMessage
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 unset offset - wrap String Wrap the message in html(usually something like "&lt
$wgRevokePermissions
$wgRevokePermissions
Permission keys revoked from users in each group.
Definition: DefaultSettings.php:5264
array
the array() calling protocol came about after MediaWiki 1.4rc1.
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3290
$wgBlockAllowsUTEdit
$wgBlockAllowsUTEdit
Set this to true to allow blocked users to edit their own user talk page.
Definition: DefaultSettings.php:5002
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1555
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5710
User\getEditTokenObject
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:4612
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:6985
IP\isInRanges
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition: IP.php:668
$opt
$opt
Definition: postprocess-phan.php:119
User\isBot
isBot()
Definition: User.php:3808
Block\newFromID
static newFromID( $id)
Load a blocked user from their block id.
Definition: Block.php:184
User\newTouchedTimestamp
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition: User.php:2742
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8848
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3677
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:4475
User\incEditCount
incEditCount()
Deferred version of incEditCountImmediate()
Definition: User.php:5324
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:729
wfGetLB
wfGetLB( $wiki=false)
Get a load balancer object.
Definition: GlobalFunctions.php:2825
UserMailer\send
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
User\getOptionKinds
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:3347
Block\chooseBlock
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition: Block.php:1293
User\getBlock
getBlock( $bFromSlave=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2310
text
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:18
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4900
MediaWiki\Logger\LoggerFactory\getInstance
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
Definition: LoggerFactory.php:92
User\getBlockId
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2361
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:3225
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:3182
UserGroupMembership\getGroupName
static getGroupName( $group)
Gets the localized friendly name for a group, if it exists.
Definition: UserGroupMembership.php:442
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:223
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:325
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1980
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2969
$ret
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:2005
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:4462
$wgSoftBlockRanges
string[] $wgSoftBlockRanges
IP ranges that should be considered soft-blocked (anon-only, account creation allowed).
Definition: DefaultSettings.php:5630
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2623
User\loadDefaults
loadDefaults( $name=false)
Set cached properties to default.
Definition: User.php:1280
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:304
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1721
User\$mOptions
array $mOptions
Definition: User.php:295
User\$mNewtalk
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:271
User\loadOptions
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5480
User\makeGroupLinkHTML
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition: User.php:5160
MIGRATION_NEW
const MIGRATION_NEW
Definition: Defines.php:305
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:4856
User\setNewpassword
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition: User.php:3027
Autopromote\getAutopromoteGroups
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:35
StatusValue\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
User\setEmailWithConfirmation
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:3072
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1626
User\$mHideName
bool $mHideName
Definition: User.php:293
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3519
Sanitizer\validateEmail
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:2105
$params
$params
Definition: styleTest.css.php:40
Block\newFromTarget
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:1173
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1262
User\$mLocked
bool $mLocked
Definition: User.php:291
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:591
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1439
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4832
IP\isIPv6
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
User\$mEmail
string $mEmail
Definition: User.php:221
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:4534
User\$mOptionOverrides
array $mOptionOverrides
Definition: User.php:241
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3575
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:657
$s
$s
Definition: mergeMessageFileList.php:187
page
target page
Definition: All_system_messages.txt:1267
User\getBlockFromCookieValue
getBlockFromCookieValue( $blockCookieVal)
Try to load a Block from an ID given in a cookie value.
Definition: User.php:1893
PasswordFactory\generateRandomPasswordString
static generateRandomPasswordString( $minLength=10)
Generate a random string suitable for a password.
Definition: PasswordFactory.php:198
MWCryptRand\generateHex
static generateHex( $chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:76
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3877
$res
$res
Definition: database.txt:21
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
User\getDatePreference
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3477
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:350
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:3055
User\idForName
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:4262
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:969
User
User
Definition: All_system_messages.txt:425
User\sendConfirmationMail
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition: User.php:4678
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:5005
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:3045
User\pingLimiter
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition: User.php:2072
User\$mFrom
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition: User.php:266
$result
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. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. '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 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name '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:Array with elements of the form "language:title" in the order that they will be output. & $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:1993
User\initEditCount
initEditCount( $add=0)
Initialize user_editcount from data out of the revision table.
Definition: User.php:5385
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3868
$base
$base
Definition: generateLocalAutoload.php:11
User\invalidateEmail
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4818
UserGroupMembership\getGroupPage
static getGroupPage( $group)
Gets the title of a page describing a particular user group.
Definition: UserGroupMembership.php:467
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:750
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6859
User\loadGroups
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1565
User\$mHash
string $mHash
Definition: User.php:277
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:6894
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1087
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
User\isIPRange
isIPRange()
Is the user an IP range?
Definition: User.php:954
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
User\deleteNewtalk
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2690
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:89
true
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:2006
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:74
User\equals
equals(User $user)
Checks if two user objects point to the same user.
Definition: User.php:5754
User\$mCacheVars
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:98
php
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:37
pages
The ContentHandler facility adds support for arbitrary content types on wiki pages
Definition: contenthandler.txt:1
User\getRights
getRights()
Get the permissions this user has.
Definition: User.php:3534
$wgClockSkewFudge
$wgClockSkewFudge
Clock skew or the one-second resolution of time() can occasionally cause cache problems when the user...
Definition: DefaultSettings.php:2632
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4297
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3902
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
User\getAutomaticGroups
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3624
User\INVALID_TOKEN
const INVALID_TOKEN
@const string An invalid value for user_token
Definition: User.php:62
User\setPassword
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2880
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1722
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5734
$dbr
$dbr
Definition: testCompression.php:50
$wgMaxNameChars
$wgMaxNameChars
Maximum number of bytes in username.
Definition: DefaultSettings.php:4829
IDBAccessObject\READ_LOCKING
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:62
User\isBlockedFrom
isBlockedFrom( $title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2322
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Name of the external diff engine to use.
Definition: DefaultSettings.php:8847
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:791
UserGroupMembership\getMembershipsForUser
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
Definition: UserGroupMembership.php:310
NS_MAIN
const NS_MAIN
Definition: Defines.php:74
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3712
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:32
LoggedOutEditToken
Value object representing a logged-out user's edit token.
Definition: LoggedOutEditToken.php:35
User\matchEditToken
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition: User.php:4652
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:3035
file
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:106
User\$mRights
array $mRights
Definition: User.php:279
article
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
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:4543
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4372
User\makeUpdateConditions
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition: User.php:1641
IP\isValidRange
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition: IP.php:138
User\invalidateCache
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2792
User\setInternalPassword
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2892
MWException
MediaWiki exception.
Definition: MWException.php:26
User\invalidationTokenUrl
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition: User.php:4770
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2421
User\$mDatePreference
string $mDatePreference
Definition: User.php:273
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:4961
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1123
$property
$property
Definition: styleTest.css.php:44
User\checkPasswordValidity
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1170
if
if(file_exists("$IP/StartProfiler.php"))
Definition: Setup.php:42
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:99
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4801
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1329
UserGroupMembership\getLink
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
Definition: UserGroupMembership.php:375
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2352
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2710
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2812
User\$mBlockedFromCreateAccount
Block $mBlockedFromCreateAccount
Definition: User.php:307
User\$mGlobalBlock
Block $mGlobalBlock
Definition: User.php:289
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4525
MediaWiki\Auth\AuthenticationResponse
This is a value object to hold authentication response data.
Definition: AuthenticationResponse.php:37
User\logout
logout()
Log this user out.
Definition: User.php:4139
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4743
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:507
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1996
User\getImplicitGroups
static getImplicitGroups()
Get a list of implicit groups.
Definition: User.php:5128
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2438
User\randomPassword
static randomPassword()
Return a random password.
Definition: User.php:1267
User\$mBlockedby
string $mBlockedby
Definition: User.php:275
UserGroupMembership\newFromRow
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
Definition: UserGroupMembership.php:93
IP\getSubnet
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition: IP.php:742
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4516
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:943
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2824
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3601
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:2045
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3748
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4860
$wgImplicitGroups
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
Definition: DefaultSettings.php:5269
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4553
User\isAllowedAll
isAllowedAll()
Definition: User.php:3840
$wgLang
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
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2809
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
User\clearInstanceCache
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1697
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1620
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:85
$wgEnableDnsBlacklist
$wgEnableDnsBlacklist
Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies.
Definition: DefaultSettings.php:5590
User\TOKEN_LENGTH
const TOKEN_LENGTH
@const int Number of characters in user_token field.
Definition: User.php:57
$wgDefaultSkin
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
Definition: DefaultSettings.php:3279
$wgReservedUsernames
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
Definition: DefaultSettings.php:4835
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:534
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2009
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3933
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:225
User\setName
setName( $str)
Set the user name.
Definition: User.php:2509
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:218
User\resetOptions
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition: User.php:3432
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:45
User\$mGroupMemberships
UserGroupMembership[] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:239
UserPasswordPolicy
Check if a user's password complies with any password policies that apply to that user,...
Definition: UserPasswordPolicy.php:28
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:6905
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:994
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5518
User\$mLoadedItems
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:253
list
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
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3967
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:4187
User\addNewUserLogEntry
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5457
Block\getBlocksForIPList
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition: Block.php:1212
User\$mCoreRights
static $mCoreRights
Array of Strings Core rights.
Definition: User.php:125
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:977
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4929
MIGRATION_OLD
const MIGRATION_OLD
Definition: Defines.php:302
$fname
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition: Setup.php:112
$wgAutopromoteOnceLogInRC
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
Definition: DefaultSettings.php:5489
$options
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 & $options
Definition: hooks.txt:2001
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:80
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:3139
User\EDIT_TOKEN_SUFFIX
const EDIT_TOKEN_SUFFIX
Global constant made accessible as class constants so that autoloader magic can be used.
Definition: User.php:69
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2598
User\getBlockedStatus
getBlockedStatus( $bFromSlave=true)
Get blocking information.
Definition: User.php:1777
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:562
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:2763
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:77
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3653
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:863
Block\getIdFromCookieValue
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the 'BlockID' cookie.
Definition: Block.php:1588
$value
$value
Definition: styleTest.css.php:45
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\incEditCountImmediate
incEditCountImmediate()
Increment the user's edit-count field.
Definition: User.php:5338
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1340
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:5677
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2374
$wgForceHTTPS
bool $wgForceHTTPS
If this is true, when an insecure HTTP request is received, always redirect to HTTPS.
Definition: DefaultSettings.php:147
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:3154
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
User\isDnsBlacklisted
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1932
User\getTouched
getTouched()
Get the user touched timestamp.
Definition: User.php:2836
User\$mId
int $mId
Cache variables.
Definition: User.php:212
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2388
User\__toString
__toString()
Definition: User.php:332
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
Title\GAID_FOR_UPDATE
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:54
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1659
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:87
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:3127
User\$idCacheByName
static $idCacheByName
Definition: User.php:312
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4444
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:4103
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4955
User\getGroupPage
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition: User.php:5145
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:5282
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:4035
User\VERSION
const VERSION
@const int Serialized record version.
Definition: User.php:74
and
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
User\matchEditTokenNoSuffix
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition: User.php:4666
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5513
User\checkPassword
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4563
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:5099
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3950
$status
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action or null $user:User who performed the tagging when the tagging is subsequent to the action or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, 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. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1255
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:5111
$wgInvalidUsernameCharacters
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
Definition: DefaultSettings.php:4930
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:434
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:5672
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2343
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:8845
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2858
Wikimedia\Rdbms\Database\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:3906
User\getGroupMember
static getGroupMember( $group, $username='#')
Get the localized descriptive name for a member of a group, if it exists.
Definition: User.php:5088
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:83
IP\isIPv4
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:38
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:5209
$req
this hook is for auditing only $req
Definition: hooks.txt:990
Block\TYPE_USER
const TYPE_USER
Definition: Block.php:83
User\getGroupName
static getGroupName( $group)
Get the localized descriptive name for a group, if it exists.
Definition: User.php:5075
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:443
Autopromote\getAutopromoteOnceGroups
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:63
$rev
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:1777
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2473
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:4064
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1660
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4915
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:214
IP\sanitizeIP
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:152
User\isEveryoneAllowed
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:5025
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
LanguageConverter\$languagesWithVariants
static array $languagesWithVariants
languages supporting variants
Definition: LanguageConverter.php:40
User\isAllowedAny
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3825
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5418
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2942
$cache
$cache
Definition: mcc.php:33
$wgAvailableRights
$wgAvailableRights
A list of available rights, in addition to the ones defined by the core.
Definition: DefaultSettings.php:5524
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:5754
$code
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:865
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:380
$wgApplyIpBlocksToXff
$wgApplyIpBlocksToXff
Whether to look at the X-Forwarded-For header's list of (potentially spoofed) IPs and apply IP blocks...
Definition: DefaultSettings.php:5637
User\isLoggedIn
isLoggedIn()
Get whether the user is registered.
Definition: User.php:3792
User\checkTemporaryPassword
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition: User.php:4596
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:32
User\$mActorId
int null $mActorId
Definition: User.php:216
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2552
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:3007
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:883
$wgGroupPermissions
$wgGroupPermissions
Permission keys given to users in each group.
Definition: DefaultSettings.php:5123
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:923
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:233
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1053
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1210
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4874
as
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:22
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5431
Block
Definition: Block.php:27
UserCache\singleton
static singleton()
Definition: UserCache.php:34
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:291
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:629
$wgDnsBlacklistUrls
$wgDnsBlacklistUrls
List of DNS blacklists to use, if $wgEnableDnsBlacklist is true.
Definition: DefaultSettings.php:5615
$keys
$keys
Definition: testCompression.php:67
NS_USER
const NS_USER
Definition: Defines.php:76
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:528
$wgLearnerMemberSince
$wgLearnerMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8846
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1589
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4717
User\getTokenFromOption
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition: User.php:3262
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:785
User\$mEmailToken
string $mEmailToken
Definition: User.php:231
ManualLogEntry
Class for creating log entries manually, to inject them into the database.
Definition: LogEntry.php:432
$wgGroupsRemoveFromSelf
$wgGroupsRemoveFromSelf
Definition: DefaultSettings.php:5297
User\checkNewtalk
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2650
in
null for the wiki Added in
Definition: hooks.txt:1591
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1387
HTMLFormField\flattenOptions
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
Definition: HTMLFormField.php:1122
User\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5646
User\$mName
string $mName
Definition: User.php:214
User\$mRegistration
string $mRegistration
Definition: User.php:235
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:56
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for local wiki users.
Definition: DefaultSettings.php:4509
$t
$t
Definition: testCompression.php:69
User\addNewUserLogEntryAutoCreate
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition: User.php:5469
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:705
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:90
User\$mRequest
WebRequest $mRequest
Definition: User.php:298
$request
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2806
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:737
CentralIdLookup\AUDIENCE_RAW
const AUDIENCE_RAW
Definition: CentralIdLookup.php:33
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3919
MediaWikiServices
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:25
UserGroupMembership\getMembership
static getMembership( $userId, $group, IDatabase $db=null)
Returns a UserGroupMembership object that pertains to the given user and group, or false if the user ...
Definition: UserGroupMembership.php:341
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:3213
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
$wgMinimalPasswordLength
$wgMinimalPasswordLength
Specifies the minimal length of a user password.
Definition: DefaultSettings.php:4728
$wgGroupsAddToSelf
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
Definition: DefaultSettings.php:5292
User\isUsableName
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:1018
User\isBlocked
isBlocked( $bFromSlave=true)
Check if user is blocked.
Definition: User.php:2300
User\$mFormerGroups
array $mFormerGroups
Definition: User.php:287
CentralIdLookup\factory
static factory( $providerId=null)
Fetch a CentralIdLookup.
Definition: CentralIdLookup.php:46
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1599
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4494
User\getEditToken
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:4636
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3497
User\$mAllRights
static $mAllRights
String Cached results of getAllRights()
Definition: User.php:207
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1319
User\purge
static purge( $wikiId, $userId)
Definition: User.php:496
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:111
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3241
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:310
User\getPasswordValidity
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition: User.php:1134
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:873
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2482
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:4949
UserGroupMembership\getGroupMemberName
static getGroupMemberName( $group, $username)
Gets the localized name for a member of a group, if it exists.
Definition: UserGroupMembership.php:455
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:4151
$context
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2811
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:3588
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4843
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1093
$messages
$messages
Definition: LogTests.i18n.php:8
CentralIdLookup\factoryNonLocal
static factoryNonLocal()
Returns a CentralIdLookup that is guaranteed to be non-local.
Definition: CentralIdLookup.php:82
$wgNamespacesToBeSearchedDefault
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
Definition: DefaultSettings.php:6534
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:516
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
User\$mEffectiveGroups
array $mEffectiveGroups
Definition: User.php:283
User\setPasswordInternal
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2904
$wgDisableAnonTalk
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
Definition: DefaultSettings.php:6991
UserGroupMembership
Represents a "user group membership" – a specific instance of a user belonging to a group.
Definition: UserGroupMembership.php:37
IP\isIPAddress
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
User\$mEditCount
int $mEditCount
Definition: User.php:237
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:229
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4982
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3855
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:645
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3324
$wgContLang
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 content language as $wgContLang
Definition: design.txt:57
$type
$type
Definition: testCompression.php:48
$wgActorTableSchemaMigrationStage
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
Definition: DefaultSettings.php:8881