MediaWiki  master
User.php
Go to the documentation of this file.
1 <?php
35 use Wikimedia\Assert\Assert;
36 use Wikimedia\IPSet;
37 use Wikimedia\IPUtils;
41 use Wikimedia\ScopedCallback;
42 
53 class User implements IDBAccessObject, UserIdentity {
54 
58  const TOKEN_LENGTH = 32;
59 
63  const INVALID_TOKEN = '*** INVALID ***';
64 
69  const VERSION = 14;
70 
76 
80  const CHECK_USER_RIGHTS = true;
81 
85  const IGNORE_USER_RIGHTS = false;
86 
94  protected static $mCacheVars = [
95  // user table
96  'mId',
97  'mName',
98  'mRealName',
99  'mEmail',
100  'mTouched',
101  'mToken',
102  'mEmailAuthenticated',
103  'mEmailToken',
104  'mEmailTokenExpires',
105  'mRegistration',
106  'mEditCount',
107  // user_groups table
108  'mGroupMemberships',
109  // user_properties table
110  'mOptionOverrides',
111  // actor table
112  'mActorId',
113  ];
114 
116  // @{
118  public $mId;
120  public $mName;
122  protected $mActorId;
124  public $mRealName;
125 
127  public $mEmail;
129  public $mTouched;
131  protected $mQuickTouched;
133  protected $mToken;
137  protected $mEmailToken;
141  protected $mRegistration;
143  protected $mEditCount;
147  protected $mOptionOverrides;
148  // @}
149 
150  // @{
155 
159  protected $mLoadedItems = [];
160  // @}
161 
172  public $mFrom;
173 
178  protected $mNewtalk;
180  protected $mDatePreference;
187  public $mBlockedby;
189  protected $mHash;
195  protected $mBlockreason;
197  protected $mEffectiveGroups;
199  protected $mImplicitGroups;
201  protected $mFormerGroups;
203  protected $mGlobalBlock;
205  protected $mLocked;
212  public $mHideName;
214  public $mOptions;
215 
217  private $mRequest;
218 
224  public $mBlock;
225 
227  protected $mAllowUsertalk;
228 
230  private $mBlockedFromCreateAccount = false;
231 
233  protected $queryFlagsUsed = self::READ_NORMAL;
234 
236  public static $idCacheByName = [];
237 
249  public function __construct() {
250  $this->clearInstanceCache( 'defaults' );
251  }
252 
256  public function __toString() {
257  return (string)$this->getName();
258  }
259 
260  public function &__get( $name ) {
261  // A shortcut for $mRights deprecation phase
262  if ( $name === 'mRights' ) {
263  $copy = $this->getRights();
264  return $copy;
265  } elseif ( !property_exists( $this, $name ) ) {
266  // T227688 - do not break $u->foo['bar'] = 1
267  wfLogWarning( 'tried to get non-existent property' );
268  $this->$name = null;
269  return $this->$name;
270  } else {
271  wfLogWarning( 'tried to get non-visible property' );
272  $null = null;
273  return $null;
274  }
275  }
276 
277  public function __set( $name, $value ) {
278  // A shortcut for $mRights deprecation phase, only known legitimate use was for
279  // testing purposes, other uses seem bad in principle
280  if ( $name === 'mRights' ) {
281  MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
282  $this,
283  $value === null ? [] : $value
284  );
285  } elseif ( !property_exists( $this, $name ) ) {
286  $this->$name = $value;
287  } else {
288  wfLogWarning( 'tried to set non-visible property' );
289  }
290  }
291 
306  public function isSafeToLoad() {
307  global $wgFullyInitialised;
308 
309  // The user is safe to load if:
310  // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
311  // * mLoadedItems === true (already loaded)
312  // * mFrom !== 'session' (sessions not involved at all)
313 
314  return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
315  $this->mLoadedItems === true || $this->mFrom !== 'session';
316  }
317 
323  public function load( $flags = self::READ_NORMAL ) {
324  global $wgFullyInitialised;
325 
326  if ( $this->mLoadedItems === true ) {
327  return;
328  }
329 
330  // Set it now to avoid infinite recursion in accessors
331  $oldLoadedItems = $this->mLoadedItems;
332  $this->mLoadedItems = true;
333  $this->queryFlagsUsed = $flags;
334 
335  // If this is called too early, things are likely to break.
336  if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
338  ->warning( 'User::loadFromSession called before the end of Setup.php', [
339  'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
340  ] );
341  $this->loadDefaults();
342  $this->mLoadedItems = $oldLoadedItems;
343  return;
344  }
345 
346  switch ( $this->mFrom ) {
347  case 'defaults':
348  $this->loadDefaults();
349  break;
350  case 'id':
351  // Make sure this thread sees its own changes, if the ID isn't 0
352  if ( $this->mId != 0 ) {
353  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
354  if ( $lb->hasOrMadeRecentMasterChanges() ) {
355  $flags |= self::READ_LATEST;
356  $this->queryFlagsUsed = $flags;
357  }
358  }
359 
360  $this->loadFromId( $flags );
361  break;
362  case 'actor':
363  case 'name':
364  // Make sure this thread sees its own changes
365  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
366  if ( $lb->hasOrMadeRecentMasterChanges() ) {
367  $flags |= self::READ_LATEST;
368  $this->queryFlagsUsed = $flags;
369  }
370 
371  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
372  $row = wfGetDB( $index )->selectRow(
373  'actor',
374  [ 'actor_id', 'actor_user', 'actor_name' ],
375  $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
376  __METHOD__,
377  $options
378  );
379 
380  if ( !$row ) {
381  // Ugh.
382  $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
383  } elseif ( $row->actor_user ) {
384  $this->mId = $row->actor_user;
385  $this->loadFromId( $flags );
386  } else {
387  $this->loadDefaults( $row->actor_name, $row->actor_id );
388  }
389  break;
390  case 'session':
391  if ( !$this->loadFromSession() ) {
392  // Loading from session failed. Load defaults.
393  $this->loadDefaults();
394  }
395  Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
396  break;
397  default:
398  throw new UnexpectedValueException(
399  "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
400  }
401  }
402 
408  public function loadFromId( $flags = self::READ_NORMAL ) {
409  if ( $this->mId == 0 ) {
410  // Anonymous users are not in the database (don't need cache)
411  $this->loadDefaults();
412  return false;
413  }
414 
415  // Try cache (unless this needs data from the master DB).
416  // NOTE: if this thread called saveSettings(), the cache was cleared.
417  $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
418  if ( $latest ) {
419  if ( !$this->loadFromDatabase( $flags ) ) {
420  // Can't load from ID
421  return false;
422  }
423  } else {
424  $this->loadFromCache();
425  }
426 
427  $this->mLoadedItems = true;
428  $this->queryFlagsUsed = $flags;
429 
430  return true;
431  }
432 
438  public static function purge( $dbDomain, $userId ) {
439  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
440  $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
441  $cache->delete( $key );
442  }
443 
449  protected function getCacheKey( WANObjectCache $cache ) {
450  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
451 
452  return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
453  }
454 
461  $id = $this->getId();
462 
463  return $id ? [ $this->getCacheKey( $cache ) ] : [];
464  }
465 
472  protected function loadFromCache() {
473  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
474  $data = $cache->getWithSetCallback(
475  $this->getCacheKey( $cache ),
476  $cache::TTL_HOUR,
477  function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
478  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
479  wfDebug( "User: cache miss for user {$this->mId}\n" );
480 
481  $this->loadFromDatabase( self::READ_NORMAL );
482  $this->loadGroups();
483  $this->loadOptions();
484 
485  $data = [];
486  foreach ( self::$mCacheVars as $name ) {
487  $data[$name] = $this->$name;
488  }
489 
490  $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
491 
492  // if a user group membership is about to expire, the cache needs to
493  // expire at that time (T163691)
494  foreach ( $this->mGroupMemberships as $ugm ) {
495  if ( $ugm->getExpiry() ) {
496  $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
497  if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
498  $ttl = $secondsUntilExpiry;
499  }
500  }
501  }
502 
503  return $data;
504  },
505  [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
506  );
507 
508  // Restore from cache
509  foreach ( self::$mCacheVars as $name ) {
510  $this->$name = $data[$name];
511  }
512 
513  return true;
514  }
515 
517  // @{
518 
535  public static function newFromName( $name, $validate = 'valid' ) {
536  if ( $validate === true ) {
537  $validate = 'valid';
538  }
539  $name = self::getCanonicalName( $name, $validate );
540  if ( $name === false ) {
541  return false;
542  }
543 
544  // Create unloaded user object
545  $u = new User;
546  $u->mName = $name;
547  $u->mFrom = 'name';
548  $u->setItemLoaded( 'name' );
549 
550  return $u;
551  }
552 
559  public static function newFromId( $id ) {
560  $u = new User;
561  $u->mId = $id;
562  $u->mFrom = 'id';
563  $u->setItemLoaded( 'id' );
564  return $u;
565  }
566 
574  public static function newFromActorId( $id ) {
575  $u = new User;
576  $u->mActorId = $id;
577  $u->mFrom = 'actor';
578  $u->setItemLoaded( 'actor' );
579  return $u;
580  }
581 
591  public static function newFromIdentity( UserIdentity $identity ) {
592  if ( $identity instanceof User ) {
593  return $identity;
594  }
595 
596  return self::newFromAnyId(
597  $identity->getId() === 0 ? null : $identity->getId(),
598  $identity->getName() === '' ? null : $identity->getName(),
599  $identity->getActorId() === 0 ? null : $identity->getActorId()
600  );
601  }
602 
616  public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
617  // Stop-gap solution for the problem described in T222212.
618  // Force the User ID and Actor ID to zero for users loaded from the database
619  // of another wiki, to prevent subtle data corruption and confusing failure modes.
620  if ( $dbDomain !== false ) {
621  $userId = 0;
622  $actorId = 0;
623  }
624 
625  $user = new User;
626  $user->mFrom = 'defaults';
627 
628  if ( $actorId !== null ) {
629  $user->mActorId = (int)$actorId;
630  if ( $user->mActorId !== 0 ) {
631  $user->mFrom = 'actor';
632  }
633  $user->setItemLoaded( 'actor' );
634  }
635 
636  if ( $userName !== null && $userName !== '' ) {
637  $user->mName = $userName;
638  $user->mFrom = 'name';
639  $user->setItemLoaded( 'name' );
640  }
641 
642  if ( $userId !== null ) {
643  $user->mId = (int)$userId;
644  if ( $user->mId !== 0 ) {
645  $user->mFrom = 'id';
646  }
647  $user->setItemLoaded( 'id' );
648  }
649 
650  if ( $user->mFrom === 'defaults' ) {
651  throw new InvalidArgumentException(
652  'Cannot create a user with no name, no ID, and no actor ID'
653  );
654  }
655 
656  return $user;
657  }
658 
670  public static function newFromConfirmationCode( $code, $flags = 0 ) {
671  $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
672  ? wfGetDB( DB_MASTER )
673  : wfGetDB( DB_REPLICA );
674 
675  $id = $db->selectField(
676  'user',
677  'user_id',
678  [
679  'user_email_token' => md5( $code ),
680  'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
681  ]
682  );
683 
684  return $id ? self::newFromId( $id ) : null;
685  }
686 
694  public static function newFromSession( WebRequest $request = null ) {
695  $user = new User;
696  $user->mFrom = 'session';
697  $user->mRequest = $request;
698  return $user;
699  }
700 
716  public static function newFromRow( $row, $data = null ) {
717  $user = new User;
718  $user->loadFromRow( $row, $data );
719  return $user;
720  }
721 
757  public static function newSystemUser( $name, $options = [] ) {
758  $options += [
759  'validate' => 'valid',
760  'create' => true,
761  'steal' => false,
762  ];
763 
764  $name = self::getCanonicalName( $name, $options['validate'] );
765  if ( $name === false ) {
766  return null;
767  }
768 
769  $dbr = wfGetDB( DB_REPLICA );
770  $userQuery = self::getQueryInfo();
771  $row = $dbr->selectRow(
772  $userQuery['tables'],
773  $userQuery['fields'],
774  [ 'user_name' => $name ],
775  __METHOD__,
776  [],
777  $userQuery['joins']
778  );
779  if ( !$row ) {
780  // Try the master database...
781  $dbw = wfGetDB( DB_MASTER );
782  $row = $dbw->selectRow(
783  $userQuery['tables'],
784  $userQuery['fields'],
785  [ 'user_name' => $name ],
786  __METHOD__,
787  [],
788  $userQuery['joins']
789  );
790  }
791 
792  if ( !$row ) {
793  // No user. Create it?
794  if ( !$options['create'] ) {
795  // No.
796  return null;
797  }
798 
799  // If it's a reserved user that had an anonymous actor created for it at
800  // some point, we need special handling.
801  if ( !self::isValidUserName( $name ) || self::isUsableName( $name ) ) {
802  // Not reserved, so just create it.
803  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
804  }
805 
806  // It is reserved. Check for an anonymous actor row.
807  $dbw = wfGetDB( DB_MASTER );
808  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $name ) {
809  $row = $dbw->selectRow(
810  'actor',
811  [ 'actor_id' ],
812  [ 'actor_name' => $name, 'actor_user' => null ],
813  $fname,
814  [ 'FOR UPDATE' ]
815  );
816  if ( !$row ) {
817  // No anonymous actor.
818  return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
819  }
820 
821  // There is an anonymous actor. Delete the actor row so we can create the user,
822  // then restore the old actor_id so as to not break existing references.
823  // @todo If MediaWiki ever starts using foreign keys for `actor`, this will break things.
824  $dbw->delete( 'actor', [ 'actor_id' => $row->actor_id ], $fname );
825  $user = self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
826  $dbw->update(
827  'actor',
828  [ 'actor_id' => $row->actor_id ],
829  [ 'actor_id' => $user->getActorId() ],
830  $fname
831  );
832  $user->clearInstanceCache( 'id' );
833  $user->invalidateCache();
834  return $user;
835  } );
836  }
837 
838  $user = self::newFromRow( $row );
839 
840  if ( !$user->isSystemUser() ) {
841  // User exists. Steal it?
842  if ( !$options['steal'] ) {
843  return null;
844  }
845 
846  AuthManager::singleton()->revokeAccessForUser( $name );
847 
848  $user->invalidateEmail();
849  $user->mToken = self::INVALID_TOKEN;
850  $user->saveSettings();
851  SessionManager::singleton()->preventSessionsForUser( $user->getName() );
852  }
853 
854  return $user;
855  }
856 
857  // @}
858 
864  public static function whoIs( $id ) {
865  return UserCache::singleton()->getProp( $id, 'name' );
866  }
867 
874  public static function whoIsReal( $id ) {
875  return UserCache::singleton()->getProp( $id, 'real_name' );
876  }
877 
884  public static function idFromName( $name, $flags = self::READ_NORMAL ) {
885  // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
886  $name = (string)$name;
887  $nt = Title::makeTitleSafe( NS_USER, $name );
888  if ( $nt === null ) {
889  // Illegal name
890  return null;
891  }
892 
893  if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
894  return self::$idCacheByName[$name] === null ? null : (int)self::$idCacheByName[$name];
895  }
896 
897  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
898  $db = wfGetDB( $index );
899 
900  $s = $db->selectRow(
901  'user',
902  [ 'user_id' ],
903  [ 'user_name' => $nt->getText() ],
904  __METHOD__,
905  $options
906  );
907 
908  if ( $s === false ) {
909  $result = null;
910  } else {
911  $result = (int)$s->user_id;
912  }
913 
914  if ( count( self::$idCacheByName ) >= 1000 ) {
915  self::$idCacheByName = [];
916  }
917 
918  self::$idCacheByName[$name] = $result;
919 
920  return $result;
921  }
922 
926  public static function resetIdByNameCache() {
927  self::$idCacheByName = [];
928  }
929 
948  public static function isIP( $name ) {
949  return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
950  || IPUtils::isIPv6( $name );
951  }
952 
960  public function isIPRange() {
961  return IPUtils::isValidRange( $this->mName );
962  }
963 
976  public static function isValidUserName( $name ) {
977  return MediaWikiServices::getInstance()->getUserNameUtils()->isValid( $name );
978  }
979 
992  public static function isUsableName( $name ) {
993  return MediaWikiServices::getInstance()->getUserNameUtils()->isUsable( $name );
994  }
995 
1006  public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1007  if ( $groups === [] ) {
1008  return UserArrayFromResult::newFromIDs( [] );
1009  }
1010 
1011  $groups = array_unique( (array)$groups );
1012  $limit = min( 5000, $limit );
1013 
1014  $conds = [ 'ug_group' => $groups ];
1015  if ( $after !== null ) {
1016  $conds[] = 'ug_user > ' . (int)$after;
1017  }
1018 
1019  $dbr = wfGetDB( DB_REPLICA );
1020  $ids = $dbr->selectFieldValues(
1021  'user_groups',
1022  'ug_user',
1023  $conds,
1024  __METHOD__,
1025  [
1026  'DISTINCT' => true,
1027  'ORDER BY' => 'ug_user',
1028  'LIMIT' => $limit,
1029  ]
1030  ) ?: [];
1031  return UserArray::newFromIDs( $ids );
1032  }
1033 
1047  public static function isCreatableName( $name ) {
1048  return MediaWikiServices::getInstance()->getUserNameUtils()->isCreatable( $name );
1049  }
1050 
1057  public function isValidPassword( $password ) {
1058  // simple boolean wrapper for checkPasswordValidity
1059  return $this->checkPasswordValidity( $password )->isGood();
1060  }
1061 
1083  public function checkPasswordValidity( $password ) {
1084  global $wgPasswordPolicy;
1085 
1086  $upp = new UserPasswordPolicy(
1087  $wgPasswordPolicy['policies'],
1088  $wgPasswordPolicy['checks']
1089  );
1090 
1091  $status = Status::newGood( [] );
1092  $result = false; // init $result to false for the internal checks
1093 
1094  if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1095  $status->error( $result );
1096  return $status;
1097  }
1098 
1099  if ( $result === false ) {
1100  $status->merge( $upp->checkUserPassword( $this, $password ), true );
1101  return $status;
1102  }
1103 
1104  if ( $result === true ) {
1105  return $status;
1106  }
1107 
1108  $status->error( $result );
1109  return $status; // the isValidPassword hook set a string $result and returned true
1110  }
1111 
1127  public static function getCanonicalName( $name, $validate = 'valid' ) {
1128  // Backwards compatibility with strings / false
1129  $validationLevels = [
1130  'valid' => UserNameUtils::RIGOR_VALID,
1131  'usable' => UserNameUtils::RIGOR_USABLE,
1132  'creatable' => UserNameUtils::RIGOR_CREATABLE
1133  ];
1134 
1135  if ( $validate === false ) {
1136  $validation = UserNameUtils::RIGOR_NONE;
1137  } elseif ( array_key_exists( $validate, $validationLevels ) ) {
1138  $validation = $validationLevels[ $validate ];
1139  } else {
1140  // Not a recognized value, probably a test for unsupported validation
1141  // levels, regardless, just pass it along
1142  $validation = $validate;
1143  }
1144 
1145  return MediaWikiServices::getInstance()
1146  ->getUserNameUtils()
1147  ->getCanonical( (string)$name, $validation );
1148  }
1149 
1159  public function loadDefaults( $name = false, $actorId = null ) {
1160  $this->mId = 0;
1161  $this->mName = $name;
1162  $this->mActorId = $actorId;
1163  $this->mRealName = '';
1164  $this->mEmail = '';
1165  $this->mOptionOverrides = null;
1166  $this->mOptionsLoaded = false;
1167 
1168  $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1169  ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1170  if ( $loggedOut !== 0 ) {
1171  $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1172  } else {
1173  $this->mTouched = '1'; # Allow any pages to be cached
1174  }
1175 
1176  $this->mToken = null; // Don't run cryptographic functions till we need a token
1177  $this->mEmailAuthenticated = null;
1178  $this->mEmailToken = '';
1179  $this->mEmailTokenExpires = null;
1180  $this->mRegistration = wfTimestamp( TS_MW );
1181  $this->mGroupMemberships = [];
1182 
1183  Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1184  }
1185 
1198  public function isItemLoaded( $item, $all = 'all' ) {
1199  return ( $this->mLoadedItems === true && $all === 'all' ) ||
1200  ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1201  }
1202 
1208  protected function setItemLoaded( $item ) {
1209  if ( is_array( $this->mLoadedItems ) ) {
1210  $this->mLoadedItems[$item] = true;
1211  }
1212  }
1213 
1219  private function loadFromSession() {
1220  // MediaWiki\Session\Session already did the necessary authentication of the user
1221  // returned here, so just use it if applicable.
1222  $session = $this->getRequest()->getSession();
1223  $user = $session->getUser();
1224  if ( $user->isLoggedIn() ) {
1225  $this->loadFromUserObject( $user );
1226 
1227  // Other code expects these to be set in the session, so set them.
1228  $session->set( 'wsUserID', $this->getId() );
1229  $session->set( 'wsUserName', $this->getName() );
1230  $session->set( 'wsToken', $this->getToken() );
1231 
1232  return true;
1233  }
1234 
1235  return false;
1236  }
1237 
1243  public function trackBlockWithCookie() {
1244  wfDeprecated( __METHOD__, '1.34' );
1245  // Obsolete.
1246  // MediaWiki::preOutputCommit() handles this whenever possible.
1247  }
1248 
1256  public function loadFromDatabase( $flags = self::READ_LATEST ) {
1257  // Paranoia
1258  $this->mId = intval( $this->mId );
1259 
1260  if ( !$this->mId ) {
1261  // Anonymous users are not in the database
1262  $this->loadDefaults();
1263  return false;
1264  }
1265 
1266  list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1267  $db = wfGetDB( $index );
1268 
1269  $userQuery = self::getQueryInfo();
1270  $s = $db->selectRow(
1271  $userQuery['tables'],
1272  $userQuery['fields'],
1273  [ 'user_id' => $this->mId ],
1274  __METHOD__,
1275  $options,
1276  $userQuery['joins']
1277  );
1278 
1279  $this->queryFlagsUsed = $flags;
1280  Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1281 
1282  if ( $s !== false ) {
1283  // Initialise user table data
1284  $this->loadFromRow( $s );
1285  $this->mGroupMemberships = null; // deferred
1286  $this->getEditCount(); // revalidation for nulls
1287  return true;
1288  }
1289 
1290  // Invalid user_id
1291  $this->mId = 0;
1292  $this->loadDefaults();
1293 
1294  return false;
1295  }
1296 
1309  protected function loadFromRow( $row, $data = null ) {
1310  if ( !is_object( $row ) ) {
1311  throw new InvalidArgumentException( '$row must be an object' );
1312  }
1313 
1314  $all = true;
1315 
1316  $this->mGroupMemberships = null; // deferred
1317 
1318  if ( isset( $row->actor_id ) ) {
1319  $this->mActorId = (int)$row->actor_id;
1320  if ( $this->mActorId !== 0 ) {
1321  $this->mFrom = 'actor';
1322  }
1323  $this->setItemLoaded( 'actor' );
1324  } else {
1325  $all = false;
1326  }
1327 
1328  if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1329  $this->mName = $row->user_name;
1330  $this->mFrom = 'name';
1331  $this->setItemLoaded( 'name' );
1332  } else {
1333  $all = false;
1334  }
1335 
1336  if ( isset( $row->user_real_name ) ) {
1337  $this->mRealName = $row->user_real_name;
1338  $this->setItemLoaded( 'realname' );
1339  } else {
1340  $all = false;
1341  }
1342 
1343  if ( isset( $row->user_id ) ) {
1344  $this->mId = intval( $row->user_id );
1345  if ( $this->mId !== 0 ) {
1346  $this->mFrom = 'id';
1347  }
1348  $this->setItemLoaded( 'id' );
1349  } else {
1350  $all = false;
1351  }
1352 
1353  if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1354  self::$idCacheByName[$row->user_name] = $row->user_id;
1355  }
1356 
1357  if ( isset( $row->user_editcount ) ) {
1358  $this->mEditCount = $row->user_editcount;
1359  } else {
1360  $all = false;
1361  }
1362 
1363  if ( isset( $row->user_touched ) ) {
1364  $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1365  } else {
1366  $all = false;
1367  }
1368 
1369  if ( isset( $row->user_token ) ) {
1370  // The definition for the column is binary(32), so trim the NULs
1371  // that appends. The previous definition was char(32), so trim
1372  // spaces too.
1373  $this->mToken = rtrim( $row->user_token, " \0" );
1374  if ( $this->mToken === '' ) {
1375  $this->mToken = null;
1376  }
1377  } else {
1378  $all = false;
1379  }
1380 
1381  if ( isset( $row->user_email ) ) {
1382  $this->mEmail = $row->user_email;
1383  $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1384  $this->mEmailToken = $row->user_email_token;
1385  $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1386  $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1387  } else {
1388  $all = false;
1389  }
1390 
1391  if ( $all ) {
1392  $this->mLoadedItems = true;
1393  }
1394 
1395  if ( is_array( $data ) ) {
1396  if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1397  if ( $data['user_groups'] === [] ) {
1398  $this->mGroupMemberships = [];
1399  } else {
1400  $firstGroup = reset( $data['user_groups'] );
1401  if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1402  $this->mGroupMemberships = [];
1403  foreach ( $data['user_groups'] as $row ) {
1404  $ugm = UserGroupMembership::newFromRow( (object)$row );
1405  $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1406  }
1407  }
1408  }
1409  }
1410  if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1411  $this->loadOptions( $data['user_properties'] );
1412  }
1413  }
1414  }
1415 
1421  protected function loadFromUserObject( $user ) {
1422  $user->load();
1423  foreach ( self::$mCacheVars as $var ) {
1424  $this->$var = $user->$var;
1425  }
1426  }
1427 
1431  private function loadGroups() {
1432  if ( $this->mGroupMemberships === null ) {
1433  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1434  ? wfGetDB( DB_MASTER )
1435  : wfGetDB( DB_REPLICA );
1436  $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1437  $this->mId, $db );
1438  }
1439  }
1440 
1455  public function addAutopromoteOnceGroups( $event ) {
1457 
1458  if ( wfReadOnly() || !$this->getId() ) {
1459  return [];
1460  }
1461 
1462  $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1463  if ( $toPromote === [] ) {
1464  return [];
1465  }
1466 
1467  if ( !$this->checkAndSetTouched() ) {
1468  return []; // raced out (bug T48834)
1469  }
1470 
1471  $oldGroups = $this->getGroups(); // previous groups
1472  $oldUGMs = $this->getGroupMemberships();
1473  foreach ( $toPromote as $group ) {
1474  $this->addGroup( $group );
1475  }
1476  $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1477  $newUGMs = $this->getGroupMemberships();
1478 
1479  // update groups in external authentication database
1480  Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1481 
1482  $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1483  $logEntry->setPerformer( $this );
1484  $logEntry->setTarget( $this->getUserPage() );
1485  $logEntry->setParameters( [
1486  '4::oldgroups' => $oldGroups,
1487  '5::newgroups' => $newGroups,
1488  ] );
1489  $logid = $logEntry->insert();
1490  if ( $wgAutopromoteOnceLogInRC ) {
1491  $logEntry->publish( $logid );
1492  }
1493 
1494  return $toPromote;
1495  }
1496 
1506  protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1507  if ( $this->mTouched ) {
1508  // CAS check: only update if the row wasn't changed sicne it was loaded.
1509  $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1510  }
1511 
1512  return $conditions;
1513  }
1514 
1524  protected function checkAndSetTouched() {
1525  $this->load();
1526 
1527  if ( !$this->mId ) {
1528  return false; // anon
1529  }
1530 
1531  // Get a new user_touched that is higher than the old one
1532  $newTouched = $this->newTouchedTimestamp();
1533 
1534  $dbw = wfGetDB( DB_MASTER );
1535  $dbw->update( 'user',
1536  [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1537  $this->makeUpdateConditions( $dbw, [
1538  'user_id' => $this->mId,
1539  ] ),
1540  __METHOD__
1541  );
1542  $success = ( $dbw->affectedRows() > 0 );
1543 
1544  if ( $success ) {
1545  $this->mTouched = $newTouched;
1546  $this->clearSharedCache( 'changed' );
1547  } else {
1548  // Clears on failure too since that is desired if the cache is stale
1549  $this->clearSharedCache( 'refresh' );
1550  }
1551 
1552  return $success;
1553  }
1554 
1562  public function clearInstanceCache( $reloadFrom = false ) {
1563  global $wgFullyInitialised;
1564 
1565  $this->mNewtalk = -1;
1566  $this->mDatePreference = null;
1567  $this->mBlockedby = -1; # Unset
1568  $this->mHash = false;
1569  $this->mEffectiveGroups = null;
1570  $this->mImplicitGroups = null;
1571  $this->mGroupMemberships = null;
1572  $this->mOptions = null;
1573  $this->mOptionsLoaded = false;
1574  $this->mEditCount = null;
1575 
1576  // Replacement of former `$this->mRights = null` line
1577  if ( $wgFullyInitialised && $this->mFrom ) {
1578  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
1579  $this
1580  );
1581  }
1582 
1583  if ( $reloadFrom ) {
1584  $this->mLoadedItems = [];
1585  $this->mFrom = $reloadFrom;
1586  }
1587  }
1588 
1590  private static $defOpt = null;
1592  private static $defOptLang = null;
1593 
1600  public static function resetGetDefaultOptionsForTestsOnly() {
1601  Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
1602  self::$defOpt = null;
1603  self::$defOptLang = null;
1604  }
1605 
1612  public static function getDefaultOptions() {
1616 
1617  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1618  if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
1619  // The content language does not change (and should not change) mid-request, but the
1620  // unit tests change it anyway, and expect this method to return values relevant to the
1621  // current content language.
1622  return self::$defOpt;
1623  }
1624 
1625  self::$defOpt = $wgDefaultUserOptions;
1626  // Default language setting
1627  self::$defOptLang = $contLang->getCode();
1628  self::$defOpt['language'] = self::$defOptLang;
1629  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1630  if ( $langCode === $contLang->getCode() ) {
1631  self::$defOpt['variant'] = $langCode;
1632  } else {
1633  self::$defOpt["variant-$langCode"] = $langCode;
1634  }
1635  }
1636 
1637  // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1638  // since extensions may change the set of searchable namespaces depending
1639  // on user groups/permissions.
1640  foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1641  self::$defOpt['searchNs' . $nsnum] = (bool)$val;
1642  }
1643  self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1644 
1645  Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
1646 
1647  return self::$defOpt;
1648  }
1649 
1656  public static function getDefaultOption( $opt ) {
1657  $defOpts = self::getDefaultOptions();
1658  return $defOpts[$opt] ?? null;
1659  }
1660 
1670  private function getBlockedStatus( $fromReplica = true ) {
1671  if ( $this->mBlockedby != -1 ) {
1672  return;
1673  }
1674 
1675  wfDebug( __METHOD__ . ": checking...\n" );
1676 
1677  // Initialize data...
1678  // Otherwise something ends up stomping on $this->mBlockedby when
1679  // things get lazy-loaded later, causing false positive block hits
1680  // due to -1 !== 0. Probably session-related... Nothing should be
1681  // overwriting mBlockedby, surely?
1682  $this->load();
1683 
1684  // TODO: Block checking shouldn't really be done from the User object. Block
1685  // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1686  // which need more knowledge of the request context than the User should have.
1687  // Since we do currently check blocks from the User, we have to do the following
1688  // here:
1689  // - Check if this is the user associated with the main request
1690  // - If so, pass the relevant request information to the block manager
1691  $request = null;
1692 
1693  // The session user is set up towards the end of Setup.php. Until then,
1694  // assume it's a logged-out user.
1695  $sessionUser = RequestContext::getMain()->getUser();
1696  $globalUserName = $sessionUser->isSafeToLoad()
1697  ? $sessionUser->getName()
1698  : IPUtils::sanitizeIP( $sessionUser->getRequest()->getIP() );
1699 
1700  if ( $this->getName() === $globalUserName ) {
1701  // This is the global user, so we need to pass the request
1702  $request = $this->getRequest();
1703  }
1704 
1705  $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1706  $this,
1707  $request,
1708  $fromReplica
1709  );
1710 
1711  if ( $block ) {
1712  $this->mBlock = $block;
1713  $this->mBlockedby = $block->getByName();
1714  $this->mBlockreason = $block->getReason();
1715  $this->mHideName = $block->getHideName();
1716  $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1717  } else {
1718  $this->mBlock = null;
1719  $this->mBlockedby = '';
1720  $this->mBlockreason = '';
1721  $this->mHideName = 0;
1722  $this->mAllowUsertalk = false;
1723  }
1724  }
1725 
1734  public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1735  wfDeprecated( __METHOD__, '1.34' );
1736  return MediaWikiServices::getInstance()->getBlockManager()
1737  ->isDnsBlacklisted( $ip, $checkWhitelist );
1738  }
1739 
1748  public function inDnsBlacklist( $ip, $bases ) {
1749  wfDeprecated( __METHOD__, '1.34' );
1750 
1751  $found = false;
1752  // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1753  if ( IPUtils::isIPv4( $ip ) ) {
1754  // Reverse IP, T23255
1755  $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1756 
1757  foreach ( (array)$bases as $base ) {
1758  // Make hostname
1759  // If we have an access key, use that too (ProjectHoneypot, etc.)
1760  $basename = $base;
1761  if ( is_array( $base ) ) {
1762  if ( count( $base ) >= 2 ) {
1763  // Access key is 1, base URL is 0
1764  $host = "{$base[1]}.$ipReversed.{$base[0]}";
1765  } else {
1766  $host = "$ipReversed.{$base[0]}";
1767  }
1768  $basename = $base[0];
1769  } else {
1770  $host = "$ipReversed.$base";
1771  }
1772 
1773  // Send query
1774  $ipList = gethostbynamel( $host );
1775 
1776  if ( $ipList ) {
1777  wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1778  $found = true;
1779  break;
1780  }
1781 
1782  wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1783  }
1784  }
1785 
1786  return $found;
1787  }
1788 
1796  public static function isLocallyBlockedProxy( $ip ) {
1797  wfDeprecated( __METHOD__, '1.34' );
1798 
1799  global $wgProxyList;
1800 
1801  if ( !$wgProxyList ) {
1802  return false;
1803  }
1804 
1805  if ( !is_array( $wgProxyList ) ) {
1806  // Load values from the specified file
1807  $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1808  }
1809 
1810  $resultProxyList = [];
1811  $deprecatedIPEntries = [];
1812 
1813  // backward compatibility: move all ip addresses in keys to values
1814  foreach ( $wgProxyList as $key => $value ) {
1815  $keyIsIP = IPUtils::isIPAddress( $key );
1816  $valueIsIP = IPUtils::isIPAddress( $value );
1817  if ( $keyIsIP && !$valueIsIP ) {
1818  $deprecatedIPEntries[] = $key;
1819  $resultProxyList[] = $key;
1820  } elseif ( $keyIsIP && $valueIsIP ) {
1821  $deprecatedIPEntries[] = $key;
1822  $resultProxyList[] = $key;
1823  $resultProxyList[] = $value;
1824  } else {
1825  $resultProxyList[] = $value;
1826  }
1827  }
1828 
1829  if ( $deprecatedIPEntries ) {
1830  wfDeprecated(
1831  'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1832  implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
1833  }
1834 
1835  $proxyListIPSet = new IPSet( $resultProxyList );
1836  return $proxyListIPSet->match( $ip );
1837  }
1838 
1844  public function isPingLimitable() {
1845  global $wgRateLimitsExcludedIPs;
1846  if ( IPUtils::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1847  // No other good way currently to disable rate limits
1848  // for specific IPs. :P
1849  // But this is a crappy hack and should die.
1850  return false;
1851  }
1852  return !$this->isAllowed( 'noratelimit' );
1853  }
1854 
1869  public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1870  // Avoid PHP 7.1 warning of passing $this by reference
1871  $user = $this;
1872  // Call the 'PingLimiter' hook
1873  $result = false;
1874  if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1875  return $result;
1876  }
1877 
1878  global $wgRateLimits;
1879  if ( !isset( $wgRateLimits[$action] ) ) {
1880  return false;
1881  }
1882 
1883  $limits = array_merge(
1884  [ '&can-bypass' => true ],
1885  $wgRateLimits[$action]
1886  );
1887 
1888  // Some groups shouldn't trigger the ping limiter, ever
1889  if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1890  return false;
1891  }
1892 
1893  $keys = [];
1894  $id = $this->getId();
1895  $userLimit = false;
1896  $isNewbie = $this->isNewbie();
1898 
1899  if ( $id == 0 ) {
1900  // limits for anons
1901  if ( isset( $limits['anon'] ) ) {
1902  $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1903  }
1904  } elseif ( isset( $limits['user'] ) ) {
1905  // limits for logged-in users
1906  $userLimit = $limits['user'];
1907  }
1908 
1909  // limits for anons and for newbie logged-in users
1910  if ( $isNewbie ) {
1911  // ip-based limits
1912  if ( isset( $limits['ip'] ) ) {
1913  $ip = $this->getRequest()->getIP();
1914  $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1915  }
1916  // subnet-based limits
1917  if ( isset( $limits['subnet'] ) ) {
1918  $ip = $this->getRequest()->getIP();
1919  $subnet = IPUtils::getSubnet( $ip );
1920  if ( $subnet !== false ) {
1921  $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1922  }
1923  }
1924  }
1925 
1926  // Check for group-specific permissions
1927  // If more than one group applies, use the group with the highest limit ratio (max/period)
1928  foreach ( $this->getGroups() as $group ) {
1929  if ( isset( $limits[$group] ) ) {
1930  if ( $userLimit === false
1931  || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1932  ) {
1933  $userLimit = $limits[$group];
1934  }
1935  }
1936  }
1937 
1938  // limits for newbie logged-in users (override all the normal user limits)
1939  if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1940  $userLimit = $limits['newbie'];
1941  }
1942 
1943  // Set the user limit key
1944  if ( $userLimit !== false ) {
1945  // phan is confused because &can-bypass's value is a bool, so it assumes
1946  // that $userLimit is also a bool here.
1947  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1948  list( $max, $period ) = $userLimit;
1949  wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
1950  $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
1951  }
1952 
1953  // ip-based limits for all ping-limitable users
1954  if ( isset( $limits['ip-all'] ) ) {
1955  $ip = $this->getRequest()->getIP();
1956  // ignore if user limit is more permissive
1957  if ( $isNewbie || $userLimit === false
1958  || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1959  $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
1960  }
1961  }
1962 
1963  // subnet-based limits for all ping-limitable users
1964  if ( isset( $limits['subnet-all'] ) ) {
1965  $ip = $this->getRequest()->getIP();
1966  $subnet = IPUtils::getSubnet( $ip );
1967  if ( $subnet !== false ) {
1968  // ignore if user limit is more permissive
1969  if ( $isNewbie || $userLimit === false
1970  || $limits['ip-all'][0] / $limits['ip-all'][1]
1971  > $userLimit[0] / $userLimit[1] ) {
1972  $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
1973  }
1974  }
1975  }
1976 
1977  $triggered = false;
1978  foreach ( $keys as $key => $limit ) {
1979  // phan is confused because &can-bypass's value is a bool, so it assumes
1980  // that $userLimit is also a bool here.
1981  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1982  list( $max, $period ) = $limit;
1983  $summary = "(limit $max in {$period}s)";
1984  $count = $cache->get( $key );
1985  // Already pinged?
1986  if ( $count && $count >= $max ) {
1987  wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
1988  "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
1989  $triggered = true;
1990  } else {
1991  wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
1992  if ( $incrBy > 0 ) {
1993  $cache->add( $key, 0, intval( $period ) ); // first ping
1994  }
1995  }
1996  if ( $incrBy > 0 ) {
1997  $cache->incrWithInit( $key, (int)$period, $incrBy, $incrBy );
1998  }
1999  }
2000 
2001  return $triggered;
2002  }
2003 
2015  public function isBlocked( $fromReplica = true ) {
2016  return $this->getBlock( $fromReplica ) instanceof AbstractBlock &&
2017  $this->getBlock()->appliesToRight( 'edit' );
2018  }
2019 
2026  public function getBlock( $fromReplica = true ) {
2027  $this->getBlockedStatus( $fromReplica );
2028  return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
2029  }
2030 
2042  public function isBlockedFrom( $title, $fromReplica = false ) {
2043  return MediaWikiServices::getInstance()->getPermissionManager()
2044  ->isBlockedFrom( $this, $title, $fromReplica );
2045  }
2046 
2051  public function blockedBy() {
2052  $this->getBlockedStatus();
2053  return $this->mBlockedby;
2054  }
2055 
2062  public function blockedFor() {
2063  $this->getBlockedStatus();
2064  return $this->mBlockreason;
2065  }
2066 
2071  public function getBlockId() {
2072  $this->getBlockedStatus();
2073  return ( $this->mBlock ? $this->mBlock->getId() : false );
2074  }
2075 
2084  public function isBlockedGlobally( $ip = '' ) {
2085  return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
2086  }
2087 
2098  public function getGlobalBlock( $ip = '' ) {
2099  if ( $this->mGlobalBlock !== null ) {
2100  return $this->mGlobalBlock ?: null;
2101  }
2102  // User is already an IP?
2103  if ( IPUtils::isIPAddress( $this->getName() ) ) {
2104  $ip = $this->getName();
2105  } elseif ( !$ip ) {
2106  $ip = $this->getRequest()->getIP();
2107  }
2108  // Avoid PHP 7.1 warning of passing $this by reference
2109  $user = $this;
2110  $blocked = false;
2111  $block = null;
2112  Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2113 
2114  if ( $blocked && $block === null ) {
2115  // back-compat: UserIsBlockedGlobally didn't have $block param first
2116  $block = new SystemBlock( [
2117  'address' => $ip,
2118  'systemBlock' => 'global-block'
2119  ] );
2120  }
2121 
2122  $this->mGlobalBlock = $blocked ? $block : false;
2123  return $this->mGlobalBlock ?: null;
2124  }
2125 
2131  public function isLocked() {
2132  if ( $this->mLocked !== null ) {
2133  return $this->mLocked;
2134  }
2135  // Reset for hook
2136  $this->mLocked = false;
2137  Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2138  return $this->mLocked;
2139  }
2140 
2146  public function isHidden() {
2147  if ( $this->mHideName !== null ) {
2148  return (bool)$this->mHideName;
2149  }
2150  $this->getBlockedStatus();
2151  return (bool)$this->mHideName;
2152  }
2153 
2158  public function getId() {
2159  if ( $this->mId === null && $this->mName !== null &&
2160  ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
2161  ) {
2162  // Special case, we know the user is anonymous
2163  return 0;
2164  }
2165 
2166  if ( !$this->isItemLoaded( 'id' ) ) {
2167  // Don't load if this was initialized from an ID
2168  $this->load();
2169  }
2170 
2171  return (int)$this->mId;
2172  }
2173 
2178  public function setId( $v ) {
2179  $this->mId = $v;
2180  $this->clearInstanceCache( 'id' );
2181  }
2182 
2187  public function getName() {
2188  if ( $this->isItemLoaded( 'name', 'only' ) ) {
2189  // Special case optimisation
2190  return $this->mName;
2191  }
2192 
2193  $this->load();
2194  if ( $this->mName === false ) {
2195  // Clean up IPs
2196  $this->mName = IPUtils::sanitizeIP( $this->getRequest()->getIP() );
2197  }
2198 
2199  return $this->mName;
2200  }
2201 
2215  public function setName( $str ) {
2216  $this->load();
2217  $this->mName = $str;
2218  }
2219 
2226  public function getActorId( IDatabase $dbw = null ) {
2227  if ( !$this->isItemLoaded( 'actor' ) ) {
2228  $this->load();
2229  }
2230 
2231  if ( !$this->mActorId && $dbw ) {
2232  $q = [
2233  'actor_user' => $this->getId() ?: null,
2234  'actor_name' => (string)$this->getName(),
2235  ];
2236  if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2237  throw new CannotCreateActorException(
2238  'Cannot create an actor for a usable name that is not an existing user: ' .
2239  "user_id={$this->getId()} user_name=\"{$this->getName()}\""
2240  );
2241  }
2242  if ( $q['actor_name'] === '' ) {
2243  throw new CannotCreateActorException(
2244  'Cannot create an actor for a user with no name: ' .
2245  "user_id={$this->getId()} user_name=\"{$this->getName()}\""
2246  );
2247  }
2248  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2249  if ( $dbw->affectedRows() ) {
2250  $this->mActorId = (int)$dbw->insertId();
2251  } else {
2252  // Outdated cache?
2253  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2254  $this->mActorId = (int)$dbw->selectField(
2255  'actor',
2256  'actor_id',
2257  $q,
2258  __METHOD__,
2259  [ 'LOCK IN SHARE MODE' ]
2260  );
2261  if ( !$this->mActorId ) {
2262  throw new CannotCreateActorException(
2263  "Failed to create actor ID for " .
2264  "user_id={$this->getId()} user_name=\"{$this->getName()}\""
2265  );
2266  }
2267  }
2268  $this->invalidateCache();
2269  $this->setItemLoaded( 'actor' );
2270  }
2271 
2272  return (int)$this->mActorId;
2273  }
2274 
2279  public function getTitleKey() {
2280  return str_replace( ' ', '_', $this->getName() );
2281  }
2282 
2287  public function getNewtalk() {
2288  $this->load();
2289 
2290  // Load the newtalk status if it is unloaded (mNewtalk=-1)
2291  if ( $this->mNewtalk === -1 ) {
2292  $this->mNewtalk = false; # reset talk page status
2293 
2294  // Check memcached separately for anons, who have no
2295  // entire User object stored in there.
2296  if ( !$this->mId ) {
2297  global $wgDisableAnonTalk;
2298  if ( $wgDisableAnonTalk ) {
2299  // Anon newtalk disabled by configuration.
2300  $this->mNewtalk = false;
2301  } else {
2302  $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2303  }
2304  } else {
2305  $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2306  }
2307  }
2308 
2309  return (bool)$this->mNewtalk;
2310  }
2311 
2325  public function getNewMessageLinks() {
2326  // Avoid PHP 7.1 warning of passing $this by reference
2327  $user = $this;
2328  $talks = [];
2329  if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2330  return $talks;
2331  }
2332 
2333  if ( !$this->getNewtalk() ) {
2334  return [];
2335  }
2336  $utp = $this->getTalkPage();
2337  $dbr = wfGetDB( DB_REPLICA );
2338  // Get the "last viewed rev" timestamp from the oldest message notification
2339  $timestamp = $dbr->selectField( 'user_newtalk',
2340  'MIN(user_last_timestamp)',
2341  $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2342  __METHOD__ );
2343  $rev = null;
2344  if ( $timestamp ) {
2345  $revRecord = MediaWikiServices::getInstance()
2346  ->getRevisionLookup()
2347  ->getRevisionByTimestamp( $utp, $timestamp );
2348  if ( $revRecord ) {
2349  $rev = new Revision( $revRecord );
2350  }
2351  }
2352  return [
2353  [
2354  'wiki' => WikiMap::getCurrentWikiId(),
2355  'link' => $utp->getLocalURL(),
2356  'rev' => $rev
2357  ]
2358  ];
2359  }
2360 
2366  public function getNewMessageRevisionId() {
2367  $newMessageRevisionId = null;
2368  $newMessageLinks = $this->getNewMessageLinks();
2369 
2370  // Note: getNewMessageLinks() never returns more than a single link
2371  // and it is always for the same wiki, but we double-check here in
2372  // case that changes some time in the future.
2373  if ( $newMessageLinks && count( $newMessageLinks ) === 1
2374  && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2375  && $newMessageLinks[0]['rev']
2376  ) {
2378  $newMessageRevision = $newMessageLinks[0]['rev'];
2379  $newMessageRevisionId = $newMessageRevision->getId();
2380  }
2381 
2382  return $newMessageRevisionId;
2383  }
2384 
2393  protected function checkNewtalk( $field, $id ) {
2394  $dbr = wfGetDB( DB_REPLICA );
2395 
2396  $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2397 
2398  return $ok !== false;
2399  }
2400 
2408  protected function updateNewtalk( $field, $id, $curRev = null ) {
2409  // Get timestamp of the talk page revision prior to the current one
2410  $prevRev = $curRev ? $curRev->getPrevious() : false;
2411  $ts = $prevRev ? $prevRev->getTimestamp() : null;
2412  // Mark the user as having new messages since this revision
2413  $dbw = wfGetDB( DB_MASTER );
2414  $dbw->insert( 'user_newtalk',
2415  [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2416  __METHOD__,
2417  [ 'IGNORE' ] );
2418  if ( $dbw->affectedRows() ) {
2419  wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2420  return true;
2421  }
2422 
2423  wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2424  return false;
2425  }
2426 
2433  protected function deleteNewtalk( $field, $id ) {
2434  $dbw = wfGetDB( DB_MASTER );
2435  $dbw->delete( 'user_newtalk',
2436  [ $field => $id ],
2437  __METHOD__ );
2438  if ( $dbw->affectedRows() ) {
2439  wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2440  return true;
2441  }
2442 
2443  wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2444  return false;
2445  }
2446 
2453  public function setNewtalk( $val, $curRev = null ) {
2454  if ( wfReadOnly() ) {
2455  return;
2456  }
2457 
2458  $this->load();
2459  $this->mNewtalk = $val;
2460 
2461  if ( $this->isAnon() ) {
2462  $field = 'user_ip';
2463  $id = $this->getName();
2464  } else {
2465  $field = 'user_id';
2466  $id = $this->getId();
2467  }
2468 
2469  if ( $val ) {
2470  $changed = $this->updateNewtalk( $field, $id, $curRev );
2471  } else {
2472  $changed = $this->deleteNewtalk( $field, $id );
2473  }
2474 
2475  if ( $changed ) {
2476  $this->invalidateCache();
2477  }
2478  }
2479 
2486  private function newTouchedTimestamp() {
2487  $time = time();
2488  if ( $this->mTouched ) {
2489  $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2490  }
2491 
2492  return wfTimestamp( TS_MW, $time );
2493  }
2494 
2505  public function clearSharedCache( $mode = 'refresh' ) {
2506  if ( !$this->getId() ) {
2507  return;
2508  }
2509 
2510  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2511  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2512  $key = $this->getCacheKey( $cache );
2513 
2514  if ( $mode === 'refresh' ) {
2515  $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2516  } else {
2517  $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2518  function () use ( $cache, $key ) {
2519  $cache->delete( $key );
2520  },
2521  __METHOD__
2522  );
2523  }
2524  }
2525 
2531  public function invalidateCache() {
2532  $this->touch();
2533  $this->clearSharedCache( 'changed' );
2534  }
2535 
2548  public function touch() {
2549  $id = $this->getId();
2550  if ( $id ) {
2551  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2552  $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2553  $cache->touchCheckKey( $key );
2554  $this->mQuickTouched = null;
2555  }
2556  }
2557 
2563  public function validateCache( $timestamp ) {
2564  return ( $timestamp >= $this->getTouched() );
2565  }
2566 
2575  public function getTouched() {
2576  $this->load();
2577 
2578  if ( $this->mId ) {
2579  if ( $this->mQuickTouched === null ) {
2580  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2581  $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2582 
2583  $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2584  }
2585 
2586  return max( $this->mTouched, $this->mQuickTouched );
2587  }
2588 
2589  return $this->mTouched;
2590  }
2591 
2597  public function getDBTouched() {
2598  $this->load();
2599 
2600  return $this->mTouched;
2601  }
2602 
2619  public function setPassword( $str ) {
2620  wfDeprecated( __METHOD__, '1.27' );
2621  return $this->setPasswordInternal( $str );
2622  }
2623 
2632  public function setInternalPassword( $str ) {
2633  wfDeprecated( __METHOD__, '1.27' );
2634  $this->setPasswordInternal( $str );
2635  }
2636 
2645  private function setPasswordInternal( $str ) {
2646  $manager = AuthManager::singleton();
2647 
2648  // If the user doesn't exist yet, fail
2649  if ( !$manager->userExists( $this->getName() ) ) {
2650  throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2651  }
2652 
2653  $status = $this->changeAuthenticationData( [
2654  'username' => $this->getName(),
2655  'password' => $str,
2656  'retype' => $str,
2657  ] );
2658  if ( !$status->isGood() ) {
2660  ->info( __METHOD__ . ': Password change rejected: '
2661  . $status->getWikiText( null, null, 'en' ) );
2662  return false;
2663  }
2664 
2665  $this->setOption( 'watchlisttoken', false );
2666  SessionManager::singleton()->invalidateSessionsForUser( $this );
2667 
2668  return true;
2669  }
2670 
2683  public function changeAuthenticationData( array $data ) {
2684  $manager = AuthManager::singleton();
2685  $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2686  $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2687 
2688  $status = Status::newGood( 'ignored' );
2689  foreach ( $reqs as $req ) {
2690  $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2691  }
2692  if ( $status->getValue() === 'ignored' ) {
2693  $status->warning( 'authenticationdatachange-ignored' );
2694  }
2695 
2696  if ( $status->isGood() ) {
2697  foreach ( $reqs as $req ) {
2698  $manager->changeAuthenticationData( $req );
2699  }
2700  }
2701  return $status;
2702  }
2703 
2710  public function getToken( $forceCreation = true ) {
2712 
2713  $this->load();
2714  if ( !$this->mToken && $forceCreation ) {
2715  $this->setToken();
2716  }
2717 
2718  if ( !$this->mToken ) {
2719  // The user doesn't have a token, return null to indicate that.
2720  return null;
2721  }
2722 
2723  if ( $this->mToken === self::INVALID_TOKEN ) {
2724  // We return a random value here so existing token checks are very
2725  // likely to fail.
2726  return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2727  }
2728 
2729  if ( $wgAuthenticationTokenVersion === null ) {
2730  // $wgAuthenticationTokenVersion not in use, so return the raw secret
2731  return $this->mToken;
2732  }
2733 
2734  // $wgAuthenticationTokenVersion in use, so hmac it.
2735  $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2736 
2737  // The raw hash can be overly long. Shorten it up.
2738  $len = max( 32, self::TOKEN_LENGTH );
2739  if ( strlen( $ret ) < $len ) {
2740  // Should never happen, even md5 is 128 bits
2741  throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2742  }
2743 
2744  return substr( $ret, -$len );
2745  }
2746 
2753  public function setToken( $token = false ) {
2754  $this->load();
2755  if ( $this->mToken === self::INVALID_TOKEN ) {
2757  ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2758  } elseif ( !$token ) {
2759  $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2760  } else {
2761  $this->mToken = $token;
2762  }
2763  }
2764 
2769  public function getEmail() {
2770  $this->load();
2771  Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2772  return $this->mEmail;
2773  }
2774 
2780  $this->load();
2781  Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2783  }
2784 
2789  public function setEmail( $str ) {
2790  $this->load();
2791  if ( $str == $this->mEmail ) {
2792  return;
2793  }
2794  $this->invalidateEmail();
2795  $this->mEmail = $str;
2796  Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2797  }
2798 
2806  public function setEmailWithConfirmation( $str ) {
2808 
2809  if ( !$wgEnableEmail ) {
2810  return Status::newFatal( 'emaildisabled' );
2811  }
2812 
2813  $oldaddr = $this->getEmail();
2814  if ( $str === $oldaddr ) {
2815  return Status::newGood( true );
2816  }
2817 
2818  $type = $oldaddr != '' ? 'changed' : 'set';
2819  $notificationResult = null;
2820 
2821  if ( $wgEmailAuthentication && $type === 'changed' ) {
2822  // Send the user an email notifying the user of the change in registered
2823  // email address on their previous email address
2824  $change = $str != '' ? 'changed' : 'removed';
2825  $notificationResult = $this->sendMail(
2826  wfMessage( 'notificationemail_subject_' . $change )->text(),
2827  wfMessage( 'notificationemail_body_' . $change,
2828  $this->getRequest()->getIP(),
2829  $this->getName(),
2830  $str )->text()
2831  );
2832  }
2833 
2834  $this->setEmail( $str );
2835 
2836  if ( $str !== '' && $wgEmailAuthentication ) {
2837  // Send a confirmation request to the new address if needed
2838  $result = $this->sendConfirmationMail( $type );
2839 
2840  if ( $notificationResult !== null ) {
2841  $result->merge( $notificationResult );
2842  }
2843 
2844  if ( $result->isGood() ) {
2845  // Say to the caller that a confirmation and notification mail has been sent
2846  $result->value = 'eauth';
2847  }
2848  } else {
2849  $result = Status::newGood( true );
2850  }
2851 
2852  return $result;
2853  }
2854 
2859  public function getRealName() {
2860  if ( !$this->isItemLoaded( 'realname' ) ) {
2861  $this->load();
2862  }
2863 
2864  return $this->mRealName;
2865  }
2866 
2871  public function setRealName( $str ) {
2872  $this->load();
2873  $this->mRealName = $str;
2874  }
2875 
2887  public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2888  global $wgHiddenPrefs;
2889  $this->loadOptions();
2890 
2891  # We want 'disabled' preferences to always behave as the default value for
2892  # users, even if they have set the option explicitly in their settings (ie they
2893  # set it, and then it was disabled removing their ability to change it). But
2894  # we don't want to erase the preferences in the database in case the preference
2895  # is re-enabled again. So don't touch $mOptions, just override the returned value
2896  if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2897  return self::getDefaultOption( $oname );
2898  }
2899 
2900  if ( array_key_exists( $oname, $this->mOptions ) ) {
2901  return $this->mOptions[$oname];
2902  }
2903 
2904  return $defaultOverride;
2905  }
2906 
2915  public function getOptions( $flags = 0 ) {
2916  global $wgHiddenPrefs;
2917  $this->loadOptions();
2918  $options = $this->mOptions;
2919 
2920  # We want 'disabled' preferences to always behave as the default value for
2921  # users, even if they have set the option explicitly in their settings (ie they
2922  # set it, and then it was disabled removing their ability to change it). But
2923  # we don't want to erase the preferences in the database in case the preference
2924  # is re-enabled again. So don't touch $mOptions, just override the returned value
2925  foreach ( $wgHiddenPrefs as $pref ) {
2926  $default = self::getDefaultOption( $pref );
2927  if ( $default !== null ) {
2928  $options[$pref] = $default;
2929  }
2930  }
2931 
2932  if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2933  $options = array_diff_assoc( $options, self::getDefaultOptions() );
2934  }
2935 
2936  return $options;
2937  }
2938 
2946  public function getBoolOption( $oname ) {
2947  return (bool)$this->getOption( $oname );
2948  }
2949 
2958  public function getIntOption( $oname, $defaultOverride = 0 ) {
2959  $val = $this->getOption( $oname );
2960  if ( $val == '' ) {
2961  $val = $defaultOverride;
2962  }
2963  return intval( $val );
2964  }
2965 
2974  public function setOption( $oname, $val ) {
2975  $this->loadOptions();
2976 
2977  // Explicitly NULL values should refer to defaults
2978  if ( $val === null ) {
2979  $val = self::getDefaultOption( $oname );
2980  }
2981 
2982  $this->mOptions[$oname] = $val;
2983  }
2984 
2995  public function getTokenFromOption( $oname ) {
2996  global $wgHiddenPrefs;
2997 
2998  $id = $this->getId();
2999  if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3000  return false;
3001  }
3002 
3003  $token = $this->getOption( $oname );
3004  if ( !$token ) {
3005  // Default to a value based on the user token to avoid space
3006  // wasted on storing tokens for all users. When this option
3007  // is set manually by the user, only then is it stored.
3008  $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3009  }
3010 
3011  return $token;
3012  }
3013 
3023  public function resetTokenFromOption( $oname ) {
3024  global $wgHiddenPrefs;
3025  if ( in_array( $oname, $wgHiddenPrefs ) ) {
3026  return false;
3027  }
3028 
3029  $token = MWCryptRand::generateHex( 40 );
3030  $this->setOption( $oname, $token );
3031  return $token;
3032  }
3033 
3057  public static function listOptionKinds() {
3058  return [
3059  'registered',
3060  'registered-multiselect',
3061  'registered-checkmatrix',
3062  'userjs',
3063  'special',
3064  'unused'
3065  ];
3066  }
3067 
3080  public function getOptionKinds( IContextSource $context, $options = null ) {
3081  $this->loadOptions();
3082  if ( $options === null ) {
3083  $options = $this->mOptions;
3084  }
3085 
3086  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3087  $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3088  $mapping = [];
3089 
3090  // Pull out the "special" options, so they don't get converted as
3091  // multiselect or checkmatrix.
3092  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3093  foreach ( $specialOptions as $name => $value ) {
3094  unset( $prefs[$name] );
3095  }
3096 
3097  // Multiselect and checkmatrix options are stored in the database with
3098  // one key per option, each having a boolean value. Extract those keys.
3099  $multiselectOptions = [];
3100  foreach ( $prefs as $name => $info ) {
3101  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3102  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3103  $opts = HTMLFormField::flattenOptions( $info['options'] );
3104  $prefix = $info['prefix'] ?? $name;
3105 
3106  foreach ( $opts as $value ) {
3107  $multiselectOptions["$prefix$value"] = true;
3108  }
3109 
3110  unset( $prefs[$name] );
3111  }
3112  }
3113  $checkmatrixOptions = [];
3114  foreach ( $prefs as $name => $info ) {
3115  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3116  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3117  $columns = HTMLFormField::flattenOptions( $info['columns'] );
3118  $rows = HTMLFormField::flattenOptions( $info['rows'] );
3119  $prefix = $info['prefix'] ?? $name;
3120 
3121  foreach ( $columns as $column ) {
3122  foreach ( $rows as $row ) {
3123  $checkmatrixOptions["$prefix$column-$row"] = true;
3124  }
3125  }
3126 
3127  unset( $prefs[$name] );
3128  }
3129  }
3130 
3131  // $value is ignored
3132  foreach ( $options as $key => $value ) {
3133  if ( isset( $prefs[$key] ) ) {
3134  $mapping[$key] = 'registered';
3135  } elseif ( isset( $multiselectOptions[$key] ) ) {
3136  $mapping[$key] = 'registered-multiselect';
3137  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3138  $mapping[$key] = 'registered-checkmatrix';
3139  } elseif ( isset( $specialOptions[$key] ) ) {
3140  $mapping[$key] = 'special';
3141  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3142  $mapping[$key] = 'userjs';
3143  } else {
3144  $mapping[$key] = 'unused';
3145  }
3146  }
3147 
3148  return $mapping;
3149  }
3150 
3165  public function resetOptions(
3166  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3167  IContextSource $context = null
3168  ) {
3169  $this->load();
3170  $defaultOptions = self::getDefaultOptions();
3171 
3172  if ( !is_array( $resetKinds ) ) {
3173  $resetKinds = [ $resetKinds ];
3174  }
3175 
3176  if ( in_array( 'all', $resetKinds ) ) {
3177  $newOptions = $defaultOptions;
3178  } else {
3179  if ( $context === null ) {
3181  }
3182 
3183  $optionKinds = $this->getOptionKinds( $context );
3184  $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3185  $newOptions = [];
3186 
3187  // Use default values for the options that should be deleted, and
3188  // copy old values for the ones that shouldn't.
3189  foreach ( $this->mOptions as $key => $value ) {
3190  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3191  if ( array_key_exists( $key, $defaultOptions ) ) {
3192  $newOptions[$key] = $defaultOptions[$key];
3193  }
3194  } else {
3195  $newOptions[$key] = $value;
3196  }
3197  }
3198  }
3199 
3200  Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3201 
3202  $this->mOptions = $newOptions;
3203  $this->mOptionsLoaded = true;
3204  }
3205 
3210  public function getDatePreference() {
3211  // Important migration for old data rows
3212  if ( $this->mDatePreference === null ) {
3213  global $wgLang;
3214  $value = $this->getOption( 'date' );
3215  $map = $wgLang->getDatePreferenceMigrationMap();
3216  if ( isset( $map[$value] ) ) {
3217  $value = $map[$value];
3218  }
3219  $this->mDatePreference = $value;
3220  }
3221  return $this->mDatePreference;
3222  }
3223 
3230  public function requiresHTTPS() {
3231  global $wgSecureLogin;
3232  if ( !$wgSecureLogin ) {
3233  return false;
3234  }
3235 
3236  $https = $this->getBoolOption( 'prefershttps' );
3237  Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3238  if ( $https ) {
3239  $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3240  }
3241 
3242  return $https;
3243  }
3244 
3250  public function getStubThreshold() {
3251  global $wgMaxArticleSize; # Maximum article size, in Kb
3252  $threshold = $this->getIntOption( 'stubthreshold' );
3253  if ( $threshold > $wgMaxArticleSize * 1024 ) {
3254  // If they have set an impossible value, disable the preference
3255  // so we can use the parser cache again.
3256  $threshold = 0;
3257  }
3258  return $threshold;
3259  }
3260 
3269  public function getRights() {
3270  return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
3271  }
3272 
3279  public function getGroups() {
3280  $this->load();
3281  $this->loadGroups();
3282  return array_keys( $this->mGroupMemberships );
3283  }
3284 
3292  public function getGroupMemberships() {
3293  $this->load();
3294  $this->loadGroups();
3295  return $this->mGroupMemberships;
3296  }
3297 
3305  public function getEffectiveGroups( $recache = false ) {
3306  if ( $recache || $this->mEffectiveGroups === null ) {
3307  $this->mEffectiveGroups = array_unique( array_merge(
3308  $this->getGroups(), // explicit groups
3309  $this->getAutomaticGroups( $recache ) // implicit groups
3310  ) );
3311  // Avoid PHP 7.1 warning of passing $this by reference
3312  $user = $this;
3313  // Hook for additional groups
3314  Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3315  // Force reindexation of groups when a hook has unset one of them
3316  $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3317  }
3318  return $this->mEffectiveGroups;
3319  }
3320 
3328  public function getAutomaticGroups( $recache = false ) {
3329  if ( $recache || $this->mImplicitGroups === null ) {
3330  $this->mImplicitGroups = [ '*' ];
3331  if ( $this->getId() ) {
3332  $this->mImplicitGroups[] = 'user';
3333 
3334  $this->mImplicitGroups = array_unique( array_merge(
3335  $this->mImplicitGroups,
3337  ) );
3338  }
3339  if ( $recache ) {
3340  // Assure data consistency with rights/groups,
3341  // as getEffectiveGroups() depends on this function
3342  $this->mEffectiveGroups = null;
3343  }
3344  }
3345  return $this->mImplicitGroups;
3346  }
3347 
3357  public function getFormerGroups() {
3358  $this->load();
3359 
3360  if ( $this->mFormerGroups === null ) {
3361  $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3362  ? wfGetDB( DB_MASTER )
3363  : wfGetDB( DB_REPLICA );
3364  $res = $db->select( 'user_former_groups',
3365  [ 'ufg_group' ],
3366  [ 'ufg_user' => $this->mId ],
3367  __METHOD__ );
3368  $this->mFormerGroups = [];
3369  foreach ( $res as $row ) {
3370  $this->mFormerGroups[] = $row->ufg_group;
3371  }
3372  }
3373 
3374  return $this->mFormerGroups;
3375  }
3376 
3381  public function getEditCount() {
3382  if ( !$this->getId() ) {
3383  return null;
3384  }
3385 
3386  if ( $this->mEditCount === null ) {
3387  /* Populate the count, if it has not been populated yet */
3388  $dbr = wfGetDB( DB_REPLICA );
3389  // check if the user_editcount field has been initialized
3390  $count = $dbr->selectField(
3391  'user', 'user_editcount',
3392  [ 'user_id' => $this->mId ],
3393  __METHOD__
3394  );
3395 
3396  if ( $count === null ) {
3397  // it has not been initialized. do so.
3398  $count = $this->initEditCountInternal( $dbr );
3399  }
3400  $this->mEditCount = $count;
3401  }
3402  return (int)$this->mEditCount;
3403  }
3404 
3416  public function addGroup( $group, $expiry = null ) {
3417  $this->load();
3418  $this->loadGroups();
3419 
3420  if ( $expiry ) {
3421  $expiry = wfTimestamp( TS_MW, $expiry );
3422  }
3423 
3424  if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3425  return false;
3426  }
3427 
3428  // create the new UserGroupMembership and put it in the DB
3429  $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3430  if ( !$ugm->insert( true ) ) {
3431  return false;
3432  }
3433 
3434  $this->mGroupMemberships[$group] = $ugm;
3435 
3436  // Refresh the groups caches, and clear the rights cache so it will be
3437  // refreshed on the next call to $this->getRights().
3438  $this->getEffectiveGroups( true );
3439  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3440  $this->invalidateCache();
3441 
3442  return true;
3443  }
3444 
3451  public function removeGroup( $group ) {
3452  $this->load();
3453 
3454  if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3455  return false;
3456  }
3457 
3458  $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3459  // delete the membership entry
3460  if ( !$ugm || !$ugm->delete() ) {
3461  return false;
3462  }
3463 
3464  $this->loadGroups();
3465  unset( $this->mGroupMemberships[$group] );
3466 
3467  // Refresh the groups caches, and clear the rights cache so it will be
3468  // refreshed on the next call to $this->getRights().
3469  $this->getEffectiveGroups( true );
3470  MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
3471  $this->invalidateCache();
3472 
3473  return true;
3474  }
3475 
3485  public function isRegistered() {
3486  return $this->getId() != 0;
3487  }
3488 
3493  public function isLoggedIn() {
3494  return $this->isRegistered();
3495  }
3496 
3501  public function isAnon() {
3502  return !$this->isRegistered();
3503  }
3504 
3509  public function isBot() {
3510  if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3511  return true;
3512  }
3513 
3514  $isBot = false;
3515  Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3516 
3517  return $isBot;
3518  }
3519 
3529  public function isSystemUser() {
3530  $this->load();
3531  if ( $this->mEmail || $this->mToken !== self::INVALID_TOKEN ||
3532  AuthManager::singleton()->userCanAuthenticate( $this->mName )
3533  ) {
3534  return false;
3535  }
3536  return true;
3537  }
3538 
3548  public function isAllowedAny( ...$permissions ) {
3549  return MediaWikiServices::getInstance()
3550  ->getPermissionManager()
3551  ->userHasAnyRight( $this, ...$permissions );
3552  }
3553 
3560  public function isAllowedAll( ...$permissions ) {
3561  return MediaWikiServices::getInstance()
3562  ->getPermissionManager()
3563  ->userHasAllRights( $this, ...$permissions );
3564  }
3565 
3576  public function isAllowed( $action = '' ) {
3577  return MediaWikiServices::getInstance()->getPermissionManager()
3578  ->userHasRight( $this, $action );
3579  }
3580 
3585  public function useRCPatrol() {
3586  global $wgUseRCPatrol;
3587  return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3588  }
3589 
3594  public function useNPPatrol() {
3596  return (
3598  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3599  );
3600  }
3601 
3606  public function useFilePatrol() {
3608  return (
3610  && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3611  );
3612  }
3613 
3619  public function getRequest() {
3620  if ( $this->mRequest ) {
3621  return $this->mRequest;
3622  }
3623 
3624  global $wgRequest;
3625  return $wgRequest;
3626  }
3627 
3636  public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3637  if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3638  return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3639  }
3640  return false;
3641  }
3642 
3652  public function addWatch(
3653  $title,
3654  $checkRights = self::CHECK_USER_RIGHTS,
3655  ?string $expiry = null
3656  ) {
3657  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3658  MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3659  $this,
3660  [ $title->getSubjectPage(), $title->getTalkPage() ],
3661  $expiry
3662  );
3663  }
3664  $this->invalidateCache();
3665  }
3666 
3674  public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3675  if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3676  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3677  $store->removeWatch( $this, $title->getSubjectPage() );
3678  $store->removeWatch( $this, $title->getTalkPage() );
3679  }
3680  $this->invalidateCache();
3681  }
3682 
3691  public function clearNotification( &$title, $oldid = 0 ) {
3693 
3694  // Do nothing if the database is locked to writes
3695  if ( wfReadOnly() ) {
3696  return;
3697  }
3698 
3699  // Do nothing if not allowed to edit the watchlist
3700  if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3701  return;
3702  }
3703 
3704  // If we're working on user's talk page, we should update the talk page message indicator
3705  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3706  // Avoid PHP 7.1 warning of passing $this by reference
3707  $user = $this;
3708  if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3709  return;
3710  }
3711 
3712  // Try to update the DB post-send and only if needed...
3713  DeferredUpdates::addCallableUpdate( function () use ( $oldid ) {
3714  if ( !$this->getNewtalk() ) {
3715  return; // no notifications to clear
3716  }
3717 
3718  // Delete the last notifications (they stack up)
3719  $this->setNewtalk( false );
3720 
3721  // If there is a new, unseen, revision, use its timestamp
3722  if ( $oldid ) {
3723  $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3724  $oldRev = $rl->getRevisionById( $oldid, Title::READ_LATEST );
3725  if ( $oldRev ) {
3726  $newRev = $rl->getNextRevision( $oldRev );
3727  if ( $newRev ) {
3728  // TODO: actually no need to wrap in a revision,
3729  // setNewtalk really only needs a RevRecord
3730  $this->setNewtalk( true, new Revision( $newRev ) );
3731  }
3732  }
3733  }
3734  } );
3735  }
3736 
3737  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3738  return;
3739  }
3740 
3741  if ( $this->isAnon() ) {
3742  // Nothing else to do...
3743  return;
3744  }
3745 
3746  // Only update the timestamp if the page is being watched.
3747  // The query to find out if it is watched is cached both in memcached and per-invocation,
3748  // and when it does have to be executed, it can be on a replica DB
3749  // If this is the user's newtalk page, we always update the timestamp
3750  $force = '';
3751  if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3752  $force = 'force';
3753  }
3754 
3755  MediaWikiServices::getInstance()->getWatchedItemStore()
3756  ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3757  }
3758 
3765  public function clearAllNotifications() {
3767  // Do nothing if not allowed to edit the watchlist
3768  if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3769  return;
3770  }
3771 
3772  if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
3773  $this->setNewtalk( false );
3774  return;
3775  }
3776 
3777  $id = $this->getId();
3778  if ( !$id ) {
3779  return;
3780  }
3781 
3782  $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
3783  $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
3784 
3785  // We also need to clear here the "you have new message" notification for the own
3786  // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3787  }
3788 
3794  public function getExperienceLevel() {
3795  global $wgLearnerEdits,
3799 
3800  if ( $this->isAnon() ) {
3801  return false;
3802  }
3803 
3804  $editCount = $this->getEditCount();
3805  $registration = $this->getRegistration();
3806  $now = time();
3807  $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3808  $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3809 
3810  if ( $editCount < $wgLearnerEdits ||
3811  $registration > $learnerRegistration ) {
3812  return 'newcomer';
3813  }
3814 
3815  if ( $editCount > $wgExperiencedUserEdits &&
3816  $registration <= $experiencedRegistration
3817  ) {
3818  return 'experienced';
3819  }
3820 
3821  return 'learner';
3822  }
3823 
3832  public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3833  $this->load();
3834  if ( $this->mId == 0 ) {
3835  return;
3836  }
3837 
3838  $session = $this->getRequest()->getSession();
3839  if ( $request && $session->getRequest() !== $request ) {
3840  $session = $session->sessionWithRequest( $request );
3841  }
3842  $delay = $session->delaySave();
3843 
3844  if ( !$session->getUser()->equals( $this ) ) {
3845  if ( !$session->canSetUser() ) {
3847  ->warning( __METHOD__ .
3848  ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3849  );
3850  return;
3851  }
3852  $session->setUser( $this );
3853  }
3854 
3855  $session->setRememberUser( $rememberMe );
3856  if ( $secure !== null ) {
3857  $session->setForceHTTPS( $secure );
3858  }
3859 
3860  $session->persist();
3861 
3862  ScopedCallback::consume( $delay );
3863  }
3864 
3868  public function logout() {
3869  // Avoid PHP 7.1 warning of passing $this by reference
3870  $user = $this;
3871  if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
3872  $this->doLogout();
3873  }
3874  }
3875 
3880  public function doLogout() {
3881  $session = $this->getRequest()->getSession();
3882  if ( !$session->canSetUser() ) {
3884  ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3885  $error = 'immutable';
3886  } elseif ( !$session->getUser()->equals( $this ) ) {
3888  ->warning( __METHOD__ .
3889  ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3890  );
3891  // But we still may as well make this user object anon
3892  $this->clearInstanceCache( 'defaults' );
3893  $error = 'wronguser';
3894  } else {
3895  $this->clearInstanceCache( 'defaults' );
3896  $delay = $session->delaySave();
3897  $session->unpersist(); // Clear cookies (T127436)
3898  $session->setLoggedOutTimestamp( time() );
3899  $session->setUser( new User );
3900  $session->set( 'wsUserID', 0 ); // Other code expects this
3901  $session->resetAllTokens();
3902  ScopedCallback::consume( $delay );
3903  $error = false;
3904  }
3905  \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3906  'event' => 'logout',
3907  'successful' => $error === false,
3908  'status' => $error ?: 'success',
3909  ] );
3910  }
3911 
3916  public function saveSettings() {
3917  if ( wfReadOnly() ) {
3918  // @TODO: caller should deal with this instead!
3919  // This should really just be an exception.
3921  null,
3922  "Could not update user with ID '{$this->mId}'; DB is read-only."
3923  ) );
3924  return;
3925  }
3926 
3927  $this->load();
3928  if ( $this->mId == 0 ) {
3929  return; // anon
3930  }
3931 
3932  // Get a new user_touched that is higher than the old one.
3933  // This will be used for a CAS check as a last-resort safety
3934  // check against race conditions and replica DB lag.
3935  $newTouched = $this->newTouchedTimestamp();
3936 
3937  $dbw = wfGetDB( DB_MASTER );
3938  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
3939  $dbw->update( 'user',
3940  [ /* SET */
3941  'user_name' => $this->mName,
3942  'user_real_name' => $this->mRealName,
3943  'user_email' => $this->mEmail,
3944  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3945  'user_touched' => $dbw->timestamp( $newTouched ),
3946  'user_token' => strval( $this->mToken ),
3947  'user_email_token' => $this->mEmailToken,
3948  'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3949  ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
3950  'user_id' => $this->mId,
3951  ] ), $fname
3952  );
3953 
3954  if ( !$dbw->affectedRows() ) {
3955  // Maybe the problem was a missed cache update; clear it to be safe
3956  $this->clearSharedCache( 'refresh' );
3957  // User was changed in the meantime or loaded with stale data
3958  $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
3959  LoggerFactory::getInstance( 'preferences' )->warning(
3960  "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
3961  [ 'user_id' => $this->mId, 'db_flag' => $from ]
3962  );
3963  throw new MWException( "CAS update failed on user_touched. " .
3964  "The version of the user to be saved is older than the current version."
3965  );
3966  }
3967 
3968  $dbw->update(
3969  'actor',
3970  [ 'actor_name' => $this->mName ],
3971  [ 'actor_user' => $this->mId ],
3972  $fname
3973  );
3974  } );
3975 
3976  $this->mTouched = $newTouched;
3977  $this->saveOptions();
3978 
3979  Hooks::run( 'UserSaveSettings', [ $this ] );
3980  $this->clearSharedCache( 'changed' );
3981  $this->getUserPage()->purgeSquid();
3982  }
3983 
3990  public function idForName( $flags = 0 ) {
3991  $s = trim( $this->getName() );
3992  if ( $s === '' ) {
3993  return 0;
3994  }
3995 
3996  $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
3997  ? wfGetDB( DB_MASTER )
3998  : wfGetDB( DB_REPLICA );
3999 
4000  $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4001  ? [ 'LOCK IN SHARE MODE' ]
4002  : [];
4003 
4004  $id = $db->selectField( 'user',
4005  'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4006 
4007  return (int)$id;
4008  }
4009 
4025  public static function createNew( $name, $params = [] ) {
4026  foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4027  if ( isset( $params[$field] ) ) {
4028  wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4029  unset( $params[$field] );
4030  }
4031  }
4032 
4033  $user = new User;
4034  $user->load();
4035  $user->setToken(); // init token
4036  if ( isset( $params['options'] ) ) {
4037  $user->mOptions = $params['options'] + (array)$user->mOptions;
4038  unset( $params['options'] );
4039  }
4040  $dbw = wfGetDB( DB_MASTER );
4041 
4042  $noPass = PasswordFactory::newInvalidPassword()->toString();
4043 
4044  $fields = [
4045  'user_name' => $name,
4046  'user_password' => $noPass,
4047  'user_newpassword' => $noPass,
4048  'user_email' => $user->mEmail,
4049  'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4050  'user_real_name' => $user->mRealName,
4051  'user_token' => strval( $user->mToken ),
4052  'user_registration' => $dbw->timestamp( $user->mRegistration ),
4053  'user_editcount' => 0,
4054  'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4055  ];
4056  foreach ( $params as $name => $value ) {
4057  $fields["user_$name"] = $value;
4058  }
4059 
4060  return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
4061  $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4062  if ( $dbw->affectedRows() ) {
4063  $newUser = self::newFromId( $dbw->insertId() );
4064  $newUser->mName = $fields['user_name'];
4065  $newUser->updateActorId( $dbw );
4066  // Load the user from master to avoid replica lag
4067  $newUser->load( self::READ_LATEST );
4068  } else {
4069  $newUser = null;
4070  }
4071  return $newUser;
4072  } );
4073  }
4074 
4101  public function addToDatabase() {
4102  $this->load();
4103  if ( !$this->mToken ) {
4104  $this->setToken(); // init token
4105  }
4106 
4107  if ( !is_string( $this->mName ) ) {
4108  throw new RuntimeException( "User name field is not set." );
4109  }
4110 
4111  $this->mTouched = $this->newTouchedTimestamp();
4112 
4113  $dbw = wfGetDB( DB_MASTER );
4114  $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
4115  $noPass = PasswordFactory::newInvalidPassword()->toString();
4116  $dbw->insert( 'user',
4117  [
4118  'user_name' => $this->mName,
4119  'user_password' => $noPass,
4120  'user_newpassword' => $noPass,
4121  'user_email' => $this->mEmail,
4122  'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4123  'user_real_name' => $this->mRealName,
4124  'user_token' => strval( $this->mToken ),
4125  'user_registration' => $dbw->timestamp( $this->mRegistration ),
4126  'user_editcount' => 0,
4127  'user_touched' => $dbw->timestamp( $this->mTouched ),
4128  ], $fname,
4129  [ 'IGNORE' ]
4130  );
4131  if ( !$dbw->affectedRows() ) {
4132  // Use locking reads to bypass any REPEATABLE-READ snapshot.
4133  $this->mId = $dbw->selectField(
4134  'user',
4135  'user_id',
4136  [ 'user_name' => $this->mName ],
4137  $fname,
4138  [ 'LOCK IN SHARE MODE' ]
4139  );
4140  $loaded = false;
4141  if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
4142  $loaded = true;
4143  }
4144  if ( !$loaded ) {
4145  throw new MWException( $fname . ": hit a key conflict attempting " .
4146  "to insert user '{$this->mName}' row, but it was not present in select!" );
4147  }
4148  return Status::newFatal( 'userexists' );
4149  }
4150  $this->mId = $dbw->insertId();
4151  self::$idCacheByName[$this->mName] = $this->mId;
4152  $this->updateActorId( $dbw );
4153 
4154  return Status::newGood();
4155  } );
4156  if ( !$status->isGood() ) {
4157  return $status;
4158  }
4159 
4160  // Clear instance cache other than user table data and actor, which is already accurate
4161  $this->clearInstanceCache();
4162 
4163  $this->saveOptions();
4164  return Status::newGood();
4165  }
4166 
4171  private function updateActorId( IDatabase $dbw ) {
4172  $dbw->insert(
4173  'actor',
4174  [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4175  __METHOD__
4176  );
4177  $this->mActorId = (int)$dbw->insertId();
4178  }
4179 
4185  public function spreadAnyEditBlock() {
4186  if ( $this->isLoggedIn() && $this->getBlock() ) {
4187  return $this->spreadBlock();
4188  }
4189 
4190  return false;
4191  }
4192 
4198  protected function spreadBlock() {
4199  wfDebug( __METHOD__ . "()\n" );
4200  $this->load();
4201  if ( $this->mId == 0 ) {
4202  return false;
4203  }
4204 
4205  $userblock = DatabaseBlock::newFromTarget( $this->getName() );
4206  if ( !$userblock ) {
4207  return false;
4208  }
4209 
4210  return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4211  }
4212 
4217  public function isBlockedFromCreateAccount() {
4218  $this->getBlockedStatus();
4219  if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4220  return $this->mBlock;
4221  }
4222 
4223  # T15611: if the IP address the user is trying to create an account from is
4224  # blocked with createaccount disabled, prevent new account creation there even
4225  # when the user is logged in
4226  if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4227  $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
4228  null, $this->getRequest()->getIP()
4229  );
4230  }
4231  return $this->mBlockedFromCreateAccount instanceof AbstractBlock
4232  && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4233  ? $this->mBlockedFromCreateAccount
4234  : false;
4235  }
4236 
4241  public function isBlockedFromEmailuser() {
4242  $this->getBlockedStatus();
4243  return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4244  }
4245 
4252  public function isBlockedFromUpload() {
4253  $this->getBlockedStatus();
4254  return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4255  }
4256 
4261  public function isAllowedToCreateAccount() {
4262  return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4263  }
4264 
4270  public function getUserPage() {
4271  return Title::makeTitle( NS_USER, $this->getName() );
4272  }
4273 
4279  public function getTalkPage() {
4280  $title = $this->getUserPage();
4281  return $title->getTalkPage();
4282  }
4283 
4289  public function isNewbie() {
4290  return !$this->isAllowed( 'autoconfirmed' );
4291  }
4292 
4299  public function checkPassword( $password ) {
4300  wfDeprecated( __METHOD__, '1.27' );
4301 
4302  $manager = AuthManager::singleton();
4303  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4304  $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4305  [
4306  'username' => $this->getName(),
4307  'password' => $password,
4308  ]
4309  );
4310  $res = $manager->beginAuthentication( $reqs, 'null:' );
4311  switch ( $res->status ) {
4312  case AuthenticationResponse::PASS:
4313  return true;
4314  case AuthenticationResponse::FAIL:
4315  // Hope it's not a PreAuthenticationProvider that failed...
4316  LoggerFactory::getInstance( 'authentication' )
4317  ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4318  return false;
4319  default:
4320  throw new BadMethodCallException(
4321  'AuthManager returned a response unsupported by ' . __METHOD__
4322  );
4323  }
4324  }
4325 
4334  public function checkTemporaryPassword( $plaintext ) {
4335  wfDeprecated( __METHOD__, '1.27' );
4336  // Can't check the temporary password individually.
4337  return $this->checkPassword( $plaintext );
4338  }
4339 
4351  public function getEditTokenObject( $salt = '', $request = null ) {
4352  if ( $this->isAnon() ) {
4353  return new LoggedOutEditToken();
4354  }
4355 
4356  if ( !$request ) {
4357  $request = $this->getRequest();
4358  }
4359  return $request->getSession()->getToken( $salt );
4360  }
4361 
4375  public function getEditToken( $salt = '', $request = null ) {
4376  return $this->getEditTokenObject( $salt, $request )->toString();
4377  }
4378 
4391  public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4392  return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4393  }
4394 
4405  public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4406  $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4407  return $this->matchEditToken( $val, $salt, $request, $maxage );
4408  }
4409 
4417  public function sendConfirmationMail( $type = 'created' ) {
4418  global $wgLang;
4419  $expiration = null; // gets passed-by-ref and defined in next line.
4420  $token = $this->confirmationToken( $expiration );
4421  $url = $this->confirmationTokenUrl( $token );
4422  $invalidateURL = $this->invalidationTokenUrl( $token );
4423  $this->saveSettings();
4424 
4425  if ( $type == 'created' || $type === false ) {
4426  $message = 'confirmemail_body';
4427  $type = 'created';
4428  } elseif ( $type === true ) {
4429  $message = 'confirmemail_body_changed';
4430  $type = 'changed';
4431  } else {
4432  // Messages: confirmemail_body_changed, confirmemail_body_set
4433  $message = 'confirmemail_body_' . $type;
4434  }
4435 
4436  $mail = [
4437  'subject' => wfMessage( 'confirmemail_subject' )->text(),
4438  'body' => wfMessage( $message,
4439  $this->getRequest()->getIP(),
4440  $this->getName(),
4441  $url,
4442  $wgLang->userTimeAndDate( $expiration, $this ),
4443  $invalidateURL,
4444  $wgLang->userDate( $expiration, $this ),
4445  $wgLang->userTime( $expiration, $this ) )->text(),
4446  'from' => null,
4447  'replyTo' => null,
4448  ];
4449  $info = [
4450  'type' => $type,
4451  'ip' => $this->getRequest()->getIP(),
4452  'confirmURL' => $url,
4453  'invalidateURL' => $invalidateURL,
4454  'expiration' => $expiration
4455  ];
4456 
4457  Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4458  return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4459  }
4460 
4472  public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4473  global $wgPasswordSender;
4474 
4475  if ( $from instanceof User ) {
4476  $sender = MailAddress::newFromUser( $from );
4477  } else {
4478  $sender = new MailAddress( $wgPasswordSender,
4479  wfMessage( 'emailsender' )->inContentLanguage()->text() );
4480  }
4481  $to = MailAddress::newFromUser( $this );
4482 
4483  return UserMailer::send( $to, $sender, $subject, $body, [
4484  'replyTo' => $replyto,
4485  ] );
4486  }
4487 
4498  protected function confirmationToken( &$expiration ) {
4500  $now = time();
4501  $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4502  $expiration = wfTimestamp( TS_MW, $expires );
4503  $this->load();
4504  $token = MWCryptRand::generateHex( 32 );
4505  $hash = md5( $token );
4506  $this->mEmailToken = $hash;
4507  $this->mEmailTokenExpires = $expiration;
4508  return $token;
4509  }
4510 
4516  protected function confirmationTokenUrl( $token ) {
4517  return $this->getTokenUrl( 'ConfirmEmail', $token );
4518  }
4519 
4525  protected function invalidationTokenUrl( $token ) {
4526  return $this->getTokenUrl( 'InvalidateEmail', $token );
4527  }
4528 
4543  protected function getTokenUrl( $page, $token ) {
4544  // Hack to bypass localization of 'Special:'
4545  $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4546  return $title->getCanonicalURL();
4547  }
4548 
4556  public function confirmEmail() {
4557  // Check if it's already confirmed, so we don't touch the database
4558  // and fire the ConfirmEmailComplete hook on redundant confirmations.
4559  if ( !$this->isEmailConfirmed() ) {
4561  Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4562  }
4563  return true;
4564  }
4565 
4573  public function invalidateEmail() {
4574  $this->load();
4575  $this->mEmailToken = null;
4576  $this->mEmailTokenExpires = null;
4577  $this->setEmailAuthenticationTimestamp( null );
4578  $this->mEmail = '';
4579  Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4580  return true;
4581  }
4582 
4587  public function setEmailAuthenticationTimestamp( $timestamp ) {
4588  $this->load();
4589  $this->mEmailAuthenticated = $timestamp;
4590  Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4591  }
4592 
4598  public function canSendEmail() {
4600  if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4601  return false;
4602  }
4603  $canSend = $this->isEmailConfirmed();
4604  // Avoid PHP 7.1 warning of passing $this by reference
4605  $user = $this;
4606  Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4607  return $canSend;
4608  }
4609 
4615  public function canReceiveEmail() {
4616  return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4617  }
4618 
4629  public function isEmailConfirmed() {
4630  global $wgEmailAuthentication;
4631  $this->load();
4632  // Avoid PHP 7.1 warning of passing $this by reference
4633  $user = $this;
4634  $confirmed = true;
4635  if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4636  if ( $this->isAnon() ) {
4637  return false;
4638  }
4639  if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4640  return false;
4641  }
4643  return false;
4644  }
4645  return true;
4646  }
4647 
4648  return $confirmed;
4649  }
4650 
4655  public function isEmailConfirmationPending() {
4656  global $wgEmailAuthentication;
4657  return $wgEmailAuthentication &&
4658  !$this->isEmailConfirmed() &&
4659  $this->mEmailToken &&
4660  $this->mEmailTokenExpires > wfTimestamp();
4661  }
4662 
4670  public function getRegistration() {
4671  if ( $this->isAnon() ) {
4672  return false;
4673  }
4674  $this->load();
4675  return $this->mRegistration;
4676  }
4677 
4684  public function getFirstEditTimestamp() {
4685  return $this->getEditTimestamp( true );
4686  }
4687 
4695  public function getLatestEditTimestamp() {
4696  return $this->getEditTimestamp( false );
4697  }
4698 
4706  private function getEditTimestamp( $first ) {
4707  if ( $this->getId() == 0 ) {
4708  return false; // anons
4709  }
4710  $dbr = wfGetDB( DB_REPLICA );
4711  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4712  $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4713  ? 'revactor_timestamp' : 'rev_timestamp';
4714  $sortOrder = $first ? 'ASC' : 'DESC';
4715  $time = $dbr->selectField(
4716  [ 'revision' ] + $actorWhere['tables'],
4717  $tsField,
4718  [ $actorWhere['conds'] ],
4719  __METHOD__,
4720  [ 'ORDER BY' => "$tsField $sortOrder" ],
4721  $actorWhere['joins']
4722  );
4723  if ( !$time ) {
4724  return false; // no edits
4725  }
4726  return wfTimestamp( TS_MW, $time );
4727  }
4728 
4738  public static function getGroupPermissions( $groups ) {
4739  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4740  }
4741 
4751  public static function getGroupsWithPermission( $role ) {
4752  return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4753  }
4754 
4770  public static function groupHasPermission( $group, $role ) {
4771  return MediaWikiServices::getInstance()->getPermissionManager()
4772  ->groupHasPermission( $group, $role );
4773  }
4774 
4792  public static function isEveryoneAllowed( $right ) {
4793  wfDeprecated( __METHOD__, '1.34' );
4794  return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4795  }
4796 
4803  public static function getAllGroups() {
4805  return array_values( array_diff(
4806  array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4807  self::getImplicitGroups()
4808  ) );
4809  }
4810 
4818  public static function getAllRights() {
4819  wfDeprecated( __METHOD__, '1.34' );
4820  return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4821  }
4822 
4829  public static function getImplicitGroups() {
4830  global $wgImplicitGroups;
4831  return $wgImplicitGroups;
4832  }
4833 
4844  public static function changeableByGroup( $group ) {
4846 
4847  $groups = [
4848  'add' => [],
4849  'remove' => [],
4850  'add-self' => [],
4851  'remove-self' => []
4852  ];
4853 
4854  if ( empty( $wgAddGroups[$group] ) ) {
4855  // Don't add anything to $groups
4856  } elseif ( $wgAddGroups[$group] === true ) {
4857  // You get everything
4858  $groups['add'] = self::getAllGroups();
4859  } elseif ( is_array( $wgAddGroups[$group] ) ) {
4860  $groups['add'] = $wgAddGroups[$group];
4861  }
4862 
4863  // Same thing for remove
4864  if ( empty( $wgRemoveGroups[$group] ) ) {
4865  // Do nothing
4866  } elseif ( $wgRemoveGroups[$group] === true ) {
4867  $groups['remove'] = self::getAllGroups();
4868  } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4869  $groups['remove'] = $wgRemoveGroups[$group];
4870  }
4871 
4872  // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4873  if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4874  foreach ( $wgGroupsAddToSelf as $key => $value ) {
4875  if ( is_int( $key ) ) {
4876  $wgGroupsAddToSelf['user'][] = $value;
4877  }
4878  }
4879  }
4880 
4881  if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4882  foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4883  if ( is_int( $key ) ) {
4884  $wgGroupsRemoveFromSelf['user'][] = $value;
4885  }
4886  }
4887  }
4888 
4889  // Now figure out what groups the user can add to him/herself
4890  if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4891  // Do nothing
4892  } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4893  // No idea WHY this would be used, but it's there
4894  $groups['add-self'] = self::getAllGroups();
4895  } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4896  $groups['add-self'] = $wgGroupsAddToSelf[$group];
4897  }
4898 
4899  if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4900  // Do nothing
4901  } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4902  $groups['remove-self'] = self::getAllGroups();
4903  } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4904  $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4905  }
4906 
4907  return $groups;
4908  }
4909 
4917  public function changeableGroups() {
4918  if ( $this->isAllowed( 'userrights' ) ) {
4919  // This group gives the right to modify everything (reverse-
4920  // compatibility with old "userrights lets you change
4921  // everything")
4922  // Using array_merge to make the groups reindexed
4923  $all = array_merge( self::getAllGroups() );
4924  return [
4925  'add' => $all,
4926  'remove' => $all,
4927  'add-self' => [],
4928  'remove-self' => []
4929  ];
4930  }
4931 
4932  // Okay, it's not so simple, we will have to go through the arrays
4933  $groups = [
4934  'add' => [],
4935  'remove' => [],
4936  'add-self' => [],
4937  'remove-self' => []
4938  ];
4939  $addergroups = $this->getEffectiveGroups();
4940 
4941  foreach ( $addergroups as $addergroup ) {
4942  $groups = array_merge_recursive(
4943  $groups, $this->changeableByGroup( $addergroup )
4944  );
4945  $groups['add'] = array_unique( $groups['add'] );
4946  $groups['remove'] = array_unique( $groups['remove'] );
4947  $groups['add-self'] = array_unique( $groups['add-self'] );
4948  $groups['remove-self'] = array_unique( $groups['remove-self'] );
4949  }
4950  return $groups;
4951  }
4952 
4956  public function incEditCount() {
4957  if ( $this->isAnon() ) {
4958  return; // sanity
4959  }
4960 
4962  new UserEditCountUpdate( $this, 1 ),
4964  );
4965  }
4966 
4972  public function setEditCountInternal( $count ) {
4973  $this->mEditCount = $count;
4974  }
4975 
4983  public function initEditCountInternal( IDatabase $dbr ) {
4984  // Pull from a replica DB to be less cruel to servers
4985  // Accuracy isn't the point anyway here
4986  $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4987  $count = (int)$dbr->selectField(
4988  [ 'revision' ] + $actorWhere['tables'],
4989  'COUNT(*)',
4990  [ $actorWhere['conds'] ],
4991  __METHOD__,
4992  [],
4993  $actorWhere['joins']
4994  );
4995 
4996  $dbw = wfGetDB( DB_MASTER );
4997  $dbw->update(
4998  'user',
4999  [ 'user_editcount' => $count ],
5000  [
5001  'user_id' => $this->getId(),
5002  'user_editcount IS NULL OR user_editcount < ' . (int)$count
5003  ],
5004  __METHOD__
5005  );
5006 
5007  return $count;
5008  }
5009 
5017  public static function getRightDescription( $right ) {
5018  $key = "right-$right";
5019  $msg = wfMessage( $key );
5020  return $msg->isDisabled() ? $right : $msg->text();
5021  }
5022 
5030  public static function getGrantName( $grant ) {
5031  $key = "grant-$grant";
5032  $msg = wfMessage( $key );
5033  return $msg->isDisabled() ? $grant : $msg->text();
5034  }
5035 
5056  public function addNewUserLogEntry( $action = false, $reason = '' ) {
5057  return true; // disabled
5058  }
5059 
5065  protected function loadOptions( $data = null ) {
5066  $this->load();
5067 
5068  if ( $this->mOptionsLoaded ) {
5069  return;
5070  }
5071 
5072  $this->mOptions = self::getDefaultOptions();
5073 
5074  if ( !$this->getId() ) {
5075  // For unlogged-in users, load language/variant options from request.
5076  // There's no need to do it for logged-in users: they can set preferences,
5077  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5078  // so don't override user's choice (especially when the user chooses site default).
5079  $factory = MediaWikiServices::getInstance()->getLanguageConverterFactory();
5080  $variant = $factory->getLanguageConverter()->getDefaultVariant();
5081  $this->mOptions['variant'] = $variant;
5082  $this->mOptions['language'] = $variant;
5083  $this->mOptionsLoaded = true;
5084  return;
5085  }
5086 
5087  // Maybe load from the object
5088  if ( $this->mOptionOverrides !== null ) {
5089  wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5090  foreach ( $this->mOptionOverrides as $key => $value ) {
5091  $this->mOptions[$key] = $value;
5092  }
5093  } else {
5094  if ( !is_array( $data ) ) {
5095  wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5096  // Load from database
5097  $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5098  ? wfGetDB( DB_MASTER )
5099  : wfGetDB( DB_REPLICA );
5100 
5101  $res = $dbr->select(
5102  'user_properties',
5103  [ 'up_property', 'up_value' ],
5104  [ 'up_user' => $this->getId() ],
5105  __METHOD__
5106  );
5107 
5108  $this->mOptionOverrides = [];
5109  $data = [];
5110  foreach ( $res as $row ) {
5111  // Convert '0' to 0. PHP's boolean conversion considers them both
5112  // false, but e.g. JavaScript considers the former as true.
5113  // @todo: T54542 Somehow determine the desired type (string/int/bool)
5114  // and convert all values here.
5115  if ( $row->up_value === '0' ) {
5116  $row->up_value = 0;
5117  }
5118  $data[$row->up_property] = $row->up_value;
5119  }
5120  }
5121 
5122  foreach ( $data as $property => $value ) {
5123  $this->mOptionOverrides[$property] = $value;
5124  $this->mOptions[$property] = $value;
5125  }
5126  }
5127 
5128  // Replace deprecated language codes
5129  $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5130  $this->mOptions['language']
5131  );
5132 
5133  $this->mOptionsLoaded = true;
5134 
5135  Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5136  }
5137 
5143  protected function saveOptions() {
5144  $this->loadOptions();
5145 
5146  // Not using getOptions(), to keep hidden preferences in database
5147  $saveOptions = $this->mOptions;
5148 
5149  // Allow hooks to abort, for instance to save to a global profile.
5150  // Reset options to default state before saving.
5151  if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5152  return;
5153  }
5154 
5155  $userId = $this->getId();
5156 
5157  $insert_rows = []; // all the new preference rows
5158  foreach ( $saveOptions as $key => $value ) {
5159  // Don't bother storing default values
5160  $defaultOption = self::getDefaultOption( $key );
5161  if ( ( $defaultOption === null && $value !== false && $value !== null )
5162  || $value != $defaultOption
5163  ) {
5164  $insert_rows[] = [
5165  'up_user' => $userId,
5166  'up_property' => $key,
5167  'up_value' => $value,
5168  ];
5169  }
5170  }
5171 
5172  $dbw = wfGetDB( DB_MASTER );
5173 
5174  $res = $dbw->select( 'user_properties',
5175  [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5176 
5177  // Find prior rows that need to be removed or updated. These rows will
5178  // all be deleted (the latter so that INSERT IGNORE applies the new values).
5179  $keysDelete = [];
5180  foreach ( $res as $row ) {
5181  if ( !isset( $saveOptions[$row->up_property] )
5182  || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5183  ) {
5184  $keysDelete[] = $row->up_property;
5185  }
5186  }
5187 
5188  if ( count( $keysDelete ) ) {
5189  // Do the DELETE by PRIMARY KEY for prior rows.
5190  // In the past a very large portion of calls to this function are for setting
5191  // 'rememberpassword' for new accounts (a preference that has since been removed).
5192  // Doing a blanket per-user DELETE for new accounts with no rows in the table
5193  // caused gap locks on [max user ID,+infinity) which caused high contention since
5194  // updates would pile up on each other as they are for higher (newer) user IDs.
5195  // It might not be necessary these days, but it shouldn't hurt either.
5196  $dbw->delete( 'user_properties',
5197  [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5198  }
5199  // Insert the new preference rows
5200  $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5201  }
5202 
5209  public static function selectFields() {
5210  wfDeprecated( __METHOD__, '1.31' );
5211  return [
5212  'user_id',
5213  'user_name',
5214  'user_real_name',
5215  'user_email',
5216  'user_touched',
5217  'user_token',
5218  'user_email_authenticated',
5219  'user_email_token',
5220  'user_email_token_expires',
5221  'user_registration',
5222  'user_editcount',
5223  ];
5224  }
5225 
5235  public static function getQueryInfo() {
5236  $ret = [
5237  'tables' => [ 'user', 'user_actor' => 'actor' ],
5238  'fields' => [
5239  'user_id',
5240  'user_name',
5241  'user_real_name',
5242  'user_email',
5243  'user_touched',
5244  'user_token',
5245  'user_email_authenticated',
5246  'user_email_token',
5247  'user_email_token_expires',
5248  'user_registration',
5249  'user_editcount',
5250  'user_actor.actor_id',
5251  ],
5252  'joins' => [
5253  'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
5254  ],
5255  ];
5256 
5257  return $ret;
5258  }
5259 
5267  static function newFatalPermissionDeniedStatus( $permission ) {
5268  global $wgLang;
5269 
5270  $groups = [];
5271  foreach ( MediaWikiServices::getInstance()
5273  ->getGroupsWithPermission( $permission ) as $group ) {
5274  $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5275  }
5276 
5277  if ( $groups ) {
5278  return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5279  }
5280 
5281  return Status::newFatal( 'badaccess-group0' );
5282  }
5283 
5293  public function getInstanceForUpdate() {
5294  if ( !$this->getId() ) {
5295  return null; // anon
5296  }
5297 
5298  $user = self::newFromId( $this->getId() );
5299  if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5300  return null;
5301  }
5302 
5303  return $user;
5304  }
5305 
5313  public function equals( UserIdentity $user ) {
5314  // XXX it's not clear whether central ID providers are supposed to obey this
5315  return $this->getName() === $user->getName();
5316  }
5317 
5323  public function isAllowUsertalk() {
5324  return $this->mAllowUsertalk;
5325  }
5326 
5327 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1656
User\saveOptions
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition: User.php:5143
$wgHiddenPrefs
$wgHiddenPrefs
An array of preferences to not show for the user.
Definition: DefaultSettings.php:5200
User\$mNewtalk
int bool $mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition: User.php:178
LanguageCode\replaceDeprecatedCodes
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date,...
Definition: LanguageCode.php:161
User\updateNewtalk
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition: User.php:2408
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:408
User\load
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:323
User\getNewtalk
getNewtalk()
Check if the user has new messages.
Definition: User.php:2287
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:49
User\inDnsBlacklist
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition: User.php:1748
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:559
Wikimedia\Rdbms\IDatabase\affectedRows
affectedRows()
Get the number of rows affected by the last write query.
User\confirmationTokenUrl
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition: User.php:4516
User\__set
__set( $name, $value)
Definition: User.php:277
$wgProxyList
$wgProxyList
Big list of banned IP addresses.
Definition: DefaultSettings.php:6291
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:2863
User\$mToken
string $mToken
Definition: User.php:133
User\$mBlockedby
string int $mBlockedby
Definition: User.php:187
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:266
User\resetGetDefaultOptionsForTestsOnly
static resetGetDefaultOptionsForTestsOnly()
Reset the process cache of default user options.
Definition: User.php:1600
User\isValidPassword
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1057
User\getId
getId()
Get the user's ID.
Definition: User.php:2158
User\useFilePatrol
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:3606
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2322
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:3501
User\$mBlockedFromCreateAccount
AbstractBlock bool $mBlockedFromCreateAccount
Definition: User.php:230
User\$mBlockreason
string $mBlockreason
TODO: This should be removed when User::BlockedFor and AbstractBlock::getReason are hard deprecated.
Definition: User.php:195
User\getTokenUrl
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition: User.php:4543
User\isRegistered
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3485
User\getActorId
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition: User.php:2226
User\$mImplicitGroups
array $mImplicitGroups
Definition: User.php:199
User\isLocallyBlockedProxy
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1796
User\$mCacheVars
static string[] $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition: User.php:94
$wgRevokePermissions
$wgRevokePermissions
Permission keys revoked from users in each group.
Definition: DefaultSettings.php:5560
User\resetTokenFromOption
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition: User.php:3023
User\isBlockedFrom
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2042
User\loadFromUserObject
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition: User.php:1421
WikiMap\isCurrentWikiId
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:321
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:137
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5267
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:4351
$wgShowUpdatedMarker
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
Definition: DefaultSettings.php:7297
User\isBot
isBot()
Definition: User.php:3509
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:2486
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9215
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:3381
User\spreadBlock
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition: User.php:4198
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:71
User\incEditCount
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition: User.php:4956
User\newFromSession
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:694
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:115
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:3080
User\isEmailConfirmationPending
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition: User.php:4655
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:2071
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2958
true
return true
Definition: router.php:92
User\getOptions
getOptions( $flags=0)
Get all user's options.
Definition: User.php:2915
User\$mTouched
string $mTouched
TS_MW timestamp from the DB.
Definition: User.php:129
User\__construct
__construct()
Lightweight constructor for an anonymous user.
Definition: User.php:249
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1809
User\getToken
getToken( $forceCreation=true)
Get the user's current token.
Definition: User.php:2710
User\spreadAnyEditBlock
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition: User.php:4185
User\getNewMessageRevisionId
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition: User.php:2366
User\$mAllowUsertalk
bool $mAllowUsertalk
Definition: User.php:227
$wgEmailAuthentication
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
Definition: DefaultSettings.php:1837
User\$mOptions
array $mOptions
Definition: User.php:214
User\loadOptions
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition: User.php:5065
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:5131
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:85
Autopromote\getAutopromoteGroups
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
Definition: Autopromote.php:38
User\setEmailWithConfirmation
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition: User.php:2806
$wgEnableUserEmail
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
Definition: DefaultSettings.php:1725
User\$mHideName
bool $mHideName
Definition: User.php:212
User\getStubThreshold
getStubThreshold()
Get the user preferred stub threshold.
Definition: User.php:3250
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1107
User\$mLocked
bool $mLocked
Definition: User.php:205
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:535
User\loadFromRow
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition: User.php:1309
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1201
User\setEmailAuthenticationTimestamp
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition: User.php:4587
User\$mEmail
string $mEmail
Definition: User.php:127
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:4270
User\$mOptionOverrides
array $mOptionOverrides
Definition: User.php:147
User\getGroups
getGroups()
Get the list of explicit group memberships this user has.
Definition: User.php:3279
$s
$s
Definition: mergeMessageFileList.php:185
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:591
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1067
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3594
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:3210
User\isSafeToLoad
isSafeToLoad()
Test if it's safe to load this User object.
Definition: User.php:306
User\setEmail
setEmail( $str)
Set the user's e-mail address.
Definition: User.php:2789
User\idForName
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition: User.php:3990
$success
$success
Definition: NoLocalSettings.php:42
User\isValidUserName
static isValidUserName( $name)
Is the input a valid username?
Definition: User.php:976
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
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:4417
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4770
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2779
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:1869
UserEditCountUpdate
Handles increment the edit count for a given set of users.
Definition: UserEditCountUpdate.php:29
$res
$res
Definition: testCompression.php:54
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:3585
$base
$base
Definition: generateLocalAutoload.php:11
WikiMap\getCurrentWikiId
static getCurrentWikiId()
Definition: WikiMap.php:303
User\invalidateEmail
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition: User.php:4573
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:716
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:7190
User\loadGroups
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition: User.php:1431
User\$mHash
string $mHash
Definition: User.php:189
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:7206
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:995
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:960
User\deleteNewtalk
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition: User.php:2433
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:139
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:66
MediaWiki\User\UserIdentity\getActorId
getActorId()
User\getRights
getRights()
Get the permissions this user has.
Definition: User.php:3269
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4025
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3619
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:3328
User\INVALID_TOKEN
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition: User.php:63
User\setPassword
setPassword( $str)
Set the password and reset the random token.
Definition: User.php:2619
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1612
User\getInstanceForUpdate
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition: User.php:5293
$dbr
$dbr
Definition: testCompression.php:52
IDBAccessObject\READ_LOCKING
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:64
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Specify the difference engine to use.
Definition: DefaultSettings.php:9214
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:757
Wikimedia\Rdbms\IDatabase\update
update( $table, $set, $conds, $fname=__METHOD__, $options=[])
Update all rows in a table that match a given condition.
UserGroupMembership\getMembershipsForUser
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
Definition: UserGroupMembership.php:311
Revision
Definition: Revision.php:40
NS_MAIN
const NS_MAIN
Definition: Defines.php:69
User\addGroup
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition: User.php:3416
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:32
MWExceptionHandler\logException
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).
Definition: MWExceptionHandler.php:635
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:4391
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2769
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:54
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:4279
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:4101
Wikimedia\Rdbms\IDatabase\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
User\invalidateCache
invalidateCache()
Immediately touch the user data cache for this account.
Definition: User.php:2531
User\setInternalPassword
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition: User.php:2632
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:4525
User\isLocked
isLocked()
Check if user account is locked.
Definition: User.php:2131
User\$mDatePreference
string $mDatePreference
Definition: User.php:180
$wgAuthenticationTokenVersion
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
Definition: DefaultSettings.php:5238
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1033
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
User\checkPasswordValidity
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1083
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:94
User\confirmEmail
confirmEmail()
Mark the e-mail address confirmed.
Definition: User.php:4556
User\setItemLoaded
setItemLoaded( $item)
Set that an item has been loaded.
Definition: User.php:1208
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:376
User\blockedFor
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2062
getPermissionManager
getPermissionManager()
User\setNewtalk
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition: User.php:2453
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2500
User\isAllowedToCreateAccount
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition: User.php:4261
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:3868
User\confirmationToken
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition: User.php:4498
User\initEditCountInternal
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition: User.php:4983
User\getCacheKey
getCacheKey(WANObjectCache $cache)
Definition: User.php:449
$wgLang
$wgLang
Definition: Setup.php:776
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1825
User\getImplicitGroups
static getImplicitGroups()
Get a list of implicit groups TODO: Should we deprecate this? It's trivial, but we don't want to enco...
Definition: User.php:4829
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2146
UserGroupMembership\newFromRow
static newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
Definition: UserGroupMembership.php:93
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:4241
User\$mBlock
AbstractBlock null $mBlock
Definition: User.php:224
User\loadDefaults
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition: User.php:1159
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:948
User\validateCache
validateCache( $timestamp)
Validate the cache for this account.
Definition: User.php:2563
User\getEffectiveGroups
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3305
User\isPingLimitable
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1844
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:70
User\removeGroup
removeGroup( $group)
Remove the user from the given group.
Definition: User.php:3451
User\canReceiveEmail
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition: User.php:4615
$wgImplicitGroups
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
Definition: DefaultSettings.php:5565
User\$mOptionsLoaded
bool $mOptionsLoaded
Whether the cache variables have been loaded.
Definition: User.php:154
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:4289
User\touch
touch()
Update the "touched" timestamp for the user.
Definition: User.php:2548
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:36
User\clearInstanceCache
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1562
$wgEnableEmail
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
Definition: DefaultSettings.php:1719
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:80
User\TOKEN_LENGTH
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition: User.php:58
$wgDefaultSkin
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
Definition: DefaultSettings.php:3349
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:595
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1838
User\$mQuickTouched
string $mQuickTouched
TS_MW timestamp from cache.
Definition: User.php:131
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:616
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2026
User\setName
setName( $str)
Set the user name.
Definition: User.php:2215
DB_MASTER
const DB_MASTER
Definition: defines.php:26
User\$mRealName
string $mRealName
Definition: User.php:124
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:3165
UserArray\newFromIDs
static newFromIDs( $ids)
Definition: UserArray.php:42
User\$mGroupMemberships
UserGroupMembership[] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition: User.php:145
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:7217
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:913
User\__get
& __get( $name)
Definition: User.php:260
$wgRemoveGroups
$wgRemoveGroups
Definition: DefaultSettings.php:5814
User\clearNotification
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition: User.php:3691
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:3916
User\addNewUserLogEntry
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition: User.php:5056
User\$defOpt
static array null $defOpt
Is the user an IP range?
Definition: User.php:1590
User\isAllowedAll
isAllowedAll(... $permissions)
Definition: User.php:3560
User\$defOptLang
static string null $defOptLang
Is the user an IP range?
Definition: User.php:1592
$wgFullyInitialised
foreach( $wgExtensionFunctions as $func) if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition: Setup.php:827
User\getFirstEditTimestamp
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4684
$wgAutopromoteOnceLogInRC
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
Definition: DefaultSettings.php:5785
User\GETOPTIONS_EXCLUDE_DEFAULTS
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition: User.php:75
User\setRealName
setRealName( $str)
Set the user's real name.
Definition: User.php:2871
User\getNewMessageLinks
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition: User.php:2325
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:621
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:72
User\getFormerGroups
getFormerGroups()
Returns the groups the user has belonged to.
Definition: User.php:3357
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:864
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
User\isAllowedAny
isAllowedAny(... $permissions)
Check if user is allowed to access a feature / make an action.
Definition: User.php:3548
User\loadFromSession
loadFromSession()
Load user data from the session.
Definition: User.php:1219
$wgRateLimits
$wgRateLimits
Simple rate limiter options to brake edit floods.
Definition: DefaultSettings.php:5976
User\isBlockedGlobally
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2084
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2887
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:1734
Wikimedia\Rdbms\IDatabase\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
User\getTouched
getTouched()
Get the user touched timestamp.
Definition: User.php:2575
User\$mId
int $mId
Cache variables.
Definition: User.php:118
User\isSystemUser
isSystemUser()
Get whether the user is a system user.
Definition: User.php:3529
User\getGlobalBlock
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition: User.php:2098
User\__toString
__toString()
Definition: User.php:256
MediaWiki\Block\SystemBlock
System blocks are temporary blocks that are created on enforcement (e.g.
Definition: SystemBlock.php:33
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
CannotCreateActorException
Exception thrown when an actor can't be created.
Definition: CannotCreateActorException.php:28
User\checkAndSetTouched
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition: User.php:1524
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:118
User\getRealName
getRealName()
Get the user's real name.
Definition: User.php:2859
User\updateActorId
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition: User.php:4171
User\setCookies
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition: User.php:3832
User\getGroupPermissions
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4738
User\changeableGroups
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition: User.php:4917
User\clearAllNotifications
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition: User.php:3765
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition: User.php:3652
User\VERSION
const VERSION
Version number to tag cached versions of serialized User objects.
Definition: User.php:69
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:4405
$wgAddGroups
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
Definition: DefaultSettings.php:5809
User\checkPassword
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition: User.php:4299
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4803
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3674
User\getAllRights
static getAllRights()
Get a list of all available permissions.
Definition: User.php:4818
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:36
User\isBlockedFromUpload
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition: User.php:4252
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:451
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:5235
User\blockedBy
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2051
User\$mLoadedItems
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition: User.php:159
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:9212
User\getDBTouched
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2597
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $rows, $fname=__METHOD__, $options=[])
Insert the given row(s) into a table.
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:86
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:43
User\getEditTimestamp
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition: User.php:4706
$context
$context
Definition: load.php:43
User\changeableByGroup
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition: User.php:4844
$wgUseEnotif
$wgUseEnotif
Definition: Setup.php:427
Autopromote\getAutopromoteOnceGroups
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
Definition: Autopromote.php:66
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2178
User\getExperienceLevel
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3794
$wgUserEmailConfirmationTokenExpiry
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
Definition: DefaultSettings.php:1769
User\getRegistration
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4670
PasswordFactory\newInvalidPassword
static newInvalidPassword()
Create an InvalidPassword.
Definition: PasswordFactory.php:242
User\isEveryoneAllowed
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4792
MediaWiki\User\UserIdentity\getId
getId()
MediaWiki\Block\AbstractBlock\appliesToRight
appliesToRight( $right)
Determine whether the block prevents a given right.
Definition: AbstractBlock.php:267
User\getRightDescription
static getRightDescription( $right)
Get the description of a given right.
Definition: User.php:5017
User\changeAuthenticationData
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition: User.php:2683
$cache
$cache
Definition: mcc.php:33
$wgRateLimitsExcludedIPs
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
Definition: DefaultSettings.php:6058
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3493
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:4334
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:122
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2279
User\purge
static purge( $dbDomain, $userId)
Definition: User.php:438
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2753
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:884
$wgGroupPermissions
$wgGroupPermissions
Permission keys given to users in each group.
Definition: DefaultSettings.php:5408
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:38
User\resetIdByNameCache
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:926
User\$mEmailTokenExpires
string $mEmailTokenExpires
Definition: User.php:139
User\findUsersByGroup
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1006
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:1127
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4629
User\$mGlobalBlock
AbstractBlock $mGlobalBlock
Definition: User.php:203
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5323
User\getBlockedStatus
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition: User.php:1670
User\$idCacheByName
static int[] $idCacheByName
Definition: User.php:236
User\getGrantName
static getGrantName( $grant)
Get the name of a given grant.
Definition: User.php:5030
UserCache\singleton
static singleton()
Definition: UserCache.php:34
User\clearSharedCache
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition: User.php:2505
User\isBlocked
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition: User.php:2015
User\newFromActorId
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:574
$keys
$keys
Definition: testCompression.php:69
NS_USER
const NS_USER
Definition: Defines.php:71
User\loadFromCache
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition: User.php:472
$wgLearnerMemberSince
$wgLearnerMemberSince
Specify the difference engine to use.
Definition: DefaultSettings.php:9213
User\addAutopromoteOnceGroups
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition: User.php:1455
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:4472
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:2995
User\$mEmailToken
string $mEmailToken
Definition: User.php:137
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:38
Wikimedia\Rdbms\IDatabase\timestampOrNull
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
$wgGroupsRemoveFromSelf
$wgGroupsRemoveFromSelf
Definition: DefaultSettings.php:5593
User\equals
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5313
User\checkNewtalk
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition: User.php:2393
User\loadFromDatabase
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition: User.php:1256
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:1094
User\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5209
User\$mName
string $mName
Definition: User.php:120
User\$mRegistration
string $mRegistration
Definition: User.php:141
MediaWiki\Block\AbstractBlock
Definition: AbstractBlock.php:36
$wgPasswordPolicy
$wgPasswordPolicy
Password policy for the wiki.
Definition: DefaultSettings.php:4766
User\newFromConfirmationCode
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition: User.php:670
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:85
User\$mRequest
WebRequest $mRequest
Definition: User.php:217
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:642
User\isWatched
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition: User.php:3636
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:343
User\getBoolOption
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition: User.php:2946
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
$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:5588
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:992
User\$mFormerGroups
array $mFormerGroups
Definition: User.php:201
User\makeUpdateConditions
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition: User.php:1506
$wgPasswordSender
$wgPasswordSender
Sender email address for e-mail notifications.
Definition: DefaultSettings.php:1705
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4217
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:4375
User\requiresHTTPS
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition: User.php:3230
User\isItemLoaded
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition: User.php:1198
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:125
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:2974
User\$queryFlagsUsed
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition: User.php:233
User\whoIsReal
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition: User.php:874
ExternalUserNames\isExternal
static isExternal( $username)
Tells whether the username is external or not.
Definition: ExternalUserNames.php:137
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2187
$wgSecureLogin
$wgSecureLogin
This is to let user authenticate using https when they come from http.
Definition: DefaultSettings.php:5226
User\doLogout
doLogout()
Clear the user's session, and reset the instance cache.
Definition: User.php:3880
User\getGroupMemberships
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition: User.php:3292
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:4598
User\isCreatableName
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition: User.php:1047
Wikimedia\Rdbms\IDatabase\delete
delete( $table, $conds, $fname=__METHOD__)
Delete all rows in a table that match a condition.
$wgNamespacesToBeSearchedDefault
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
Definition: DefaultSettings.php:6863
User\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: User.php:460
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
User\getLatestEditTimestamp
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4695
User\$mEffectiveGroups
array $mEffectiveGroups
Definition: User.php:197
User\setEditCountInternal
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition: User.php:4972
User\setPasswordInternal
setPasswordInternal( $str)
Actually set the password and such.
Definition: User.php:2645
$wgDisableAnonTalk
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
Definition: DefaultSettings.php:7303
UserGroupMembership
Represents a "user group membership" – a specific instance of a user belonging to a group.
Definition: UserGroupMembership.php:37
User\$mEditCount
int $mEditCount
Definition: User.php:143
User\$mEmailAuthenticated
string $mEmailAuthenticated
Definition: User.php:135
User\getGroupsWithPermission
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4751
User\$mFrom
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition: User.php:172
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3576
User\listOptionKinds
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition: User.php:3057
$type
$type
Definition: testCompression.php:50
User\trackBlockWithCookie
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition: User.php:1243