MediaWiki REL1_35
User.php
Go to the documentation of this file.
1<?php
28use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
37use Wikimedia\IPSet;
38use Wikimedia\IPUtils;
42use Wikimedia\ScopedCallback;
43
59#[AllowDynamicProperties]
61 use ProtectedHookAccessorTrait;
62
66 public const TOKEN_LENGTH = 32;
67
71 public const INVALID_TOKEN = '*** INVALID ***';
72
77 private const VERSION = 16;
78
84 public const GETOPTIONS_EXCLUDE_DEFAULTS = UserOptionsLookup::EXCLUDE_DEFAULTS;
85
89 public const CHECK_USER_RIGHTS = true;
90
94 public const IGNORE_USER_RIGHTS = false;
95
103 protected static $mCacheVars = [
104 // user table
105 'mId',
106 'mName',
107 'mRealName',
108 'mEmail',
109 'mTouched',
110 'mToken',
111 'mEmailAuthenticated',
112 'mEmailToken',
113 'mEmailTokenExpires',
114 'mRegistration',
115 'mEditCount',
116 // actor table
117 'mActorId',
118 ];
119
121 // @{
123 public $mId;
125 public $mName;
127 protected $mActorId;
130
132 public $mEmail;
134 public $mTouched;
136 protected $mQuickTouched;
138 protected $mToken;
142 protected $mEmailToken;
146 protected $mRegistration;
148 protected $mEditCount;
149 // @}
150
151 // @{
155 protected $mLoadedItems = [];
156 // @}
157
168 public $mFrom;
169
183 protected $mHash;
189 protected $mBlockreason;
191 protected $mGlobalBlock;
193 protected $mLocked;
201
203 private $mRequest;
204
210 public $mBlock;
211
214
217
219 protected $queryFlagsUsed = self::READ_NORMAL;
220
222 public static $idCacheByName = [];
223
237 public function __construct() {
238 $this->clearInstanceCache( 'defaults' );
239 }
240
244 public function __toString() {
245 return (string)$this->getName();
246 }
247
248 public function &__get( $name ) {
249 // A shortcut for $mRights deprecation phase
250 if ( $name === 'mRights' ) {
251 $copy = $this->getRights();
252 return $copy;
253 } elseif ( $name === 'mOptions' ) {
254 wfDeprecated( 'User::$mOptions', '1.35' );
255 $options = $this->getOptions();
256 return $options;
257 } elseif ( !property_exists( $this, $name ) ) {
258 // T227688 - do not break $u->foo['bar'] = 1
259 wfLogWarning( 'tried to get non-existent property' );
260 $this->$name = null;
261 return $this->$name;
262 } else {
263 wfLogWarning( 'tried to get non-visible property' );
264 $null = null;
265 return $null;
266 }
267 }
268
269 public function __set( $name, $value ) {
270 // A shortcut for $mRights deprecation phase, only known legitimate use was for
271 // testing purposes, other uses seem bad in principle
272 if ( $name === 'mRights' ) {
273 MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
274 $this,
275 $value === null ? [] : $value
276 );
277 } elseif ( $name === 'mOptions' ) {
278 wfDeprecated( 'User::$mOptions', '1.35' );
279 MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $this );
280 foreach ( $value as $key => $val ) {
281 $this->setOption( $key, $val );
282 }
283 } elseif ( !property_exists( $this, $name ) ) {
284 $this->$name = $value;
285 } else {
286 wfLogWarning( 'tried to set non-visible property' );
287 }
288 }
289
304 public function isSafeToLoad() {
305 global $wgFullyInitialised;
306
307 // The user is safe to load if:
308 // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
309 // * mLoadedItems === true (already loaded)
310 // * mFrom !== 'session' (sessions not involved at all)
311
312 return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
313 $this->mLoadedItems === true || $this->mFrom !== 'session';
314 }
315
321 public function load( $flags = self::READ_NORMAL ) {
322 global $wgFullyInitialised;
323
324 if ( $this->mLoadedItems === true ) {
325 return;
326 }
327
328 // Set it now to avoid infinite recursion in accessors
329 $oldLoadedItems = $this->mLoadedItems;
330 $this->mLoadedItems = true;
331 $this->queryFlagsUsed = $flags;
332
333 // If this is called too early, things are likely to break.
334 if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
335 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
336 ->warning( 'User::loadFromSession called before the end of Setup.php', [
337 'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
338 ] );
339 $this->loadDefaults();
340 $this->mLoadedItems = $oldLoadedItems;
341 return;
342 }
343
344 switch ( $this->mFrom ) {
345 case 'defaults':
346 $this->loadDefaults();
347 break;
348 case 'id':
349 // Make sure this thread sees its own changes, if the ID isn't 0
350 if ( $this->mId != 0 ) {
351 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
352 if ( $lb->hasOrMadeRecentMasterChanges() ) {
353 $flags |= self::READ_LATEST;
354 $this->queryFlagsUsed = $flags;
355 }
356 }
357
358 $this->loadFromId( $flags );
359 break;
360 case 'actor':
361 case 'name':
362 // Make sure this thread sees its own changes
363 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
364 if ( $lb->hasOrMadeRecentMasterChanges() ) {
365 $flags |= self::READ_LATEST;
366 $this->queryFlagsUsed = $flags;
367 }
368
369 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
370 $row = wfGetDB( $index )->selectRow(
371 'actor',
372 [ 'actor_id', 'actor_user', 'actor_name' ],
373 $this->mFrom === 'name' ? [ 'actor_name' => $this->mName ] : [ 'actor_id' => $this->mActorId ],
374 __METHOD__,
375 $options
376 );
377
378 if ( !$row ) {
379 // Ugh.
380 $this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
381 } elseif ( $row->actor_user ) {
382 $this->mId = $row->actor_user;
383 $this->loadFromId( $flags );
384 } else {
385 $this->loadDefaults( $row->actor_name, $row->actor_id );
386 }
387 break;
388 case 'session':
389 if ( !$this->loadFromSession() ) {
390 // Loading from session failed. Load defaults.
391 $this->loadDefaults();
392 }
393 $this->getHookRunner()->onUserLoadAfterLoadFromSession( $this );
394 break;
395 default:
396 throw new UnexpectedValueException(
397 "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
398 }
399 }
400
406 public function loadFromId( $flags = self::READ_NORMAL ) {
407 if ( $this->mId == 0 ) {
408 // Anonymous users are not in the database (don't need cache)
409 $this->loadDefaults();
410 return false;
411 }
412
413 // Try cache (unless this needs data from the master DB).
414 // NOTE: if this thread called saveSettings(), the cache was cleared.
415 $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
416 if ( $latest ) {
417 if ( !$this->loadFromDatabase( $flags ) ) {
418 // Can't load from ID
419 return false;
420 }
421 } else {
422 $this->loadFromCache();
423 }
424
425 $this->mLoadedItems = true;
426 $this->queryFlagsUsed = $flags;
427
428 return true;
429 }
430
436 public static function purge( $dbDomain, $userId ) {
437 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
438 $key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
439 $cache->delete( $key );
440 }
441
447 protected function getCacheKey( WANObjectCache $cache ) {
448 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
449
450 return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
451 }
452
459 $id = $this->getId();
460
461 return $id ? [ $this->getCacheKey( $cache ) ] : [];
462 }
463
470 protected function loadFromCache() {
471 global $wgFullyInitialised;
472
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, $wgFullyInitialised ) {
478 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
479 wfDebug( "User: cache miss for user {$this->mId}" );
480
481 $this->loadFromDatabase( self::READ_NORMAL );
482
483 $data = [];
484 foreach ( self::$mCacheVars as $name ) {
485 $data[$name] = $this->$name;
486 }
487
488 $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
489
490 if ( $wgFullyInitialised ) {
491 $groupMemberships = MediaWikiServices::getInstance()
492 ->getUserGroupManager()
493 ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
494
495 // if a user group membership is about to expire, the cache needs to
496 // expire at that time (T163691)
497 foreach ( $groupMemberships as $ugm ) {
498 if ( $ugm->getExpiry() ) {
499 $secondsUntilExpiry =
500 wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
501
502 if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
503 $ttl = $secondsUntilExpiry;
504 }
505 }
506 }
507 }
508
509 return $data;
510 },
511 [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
512 );
513
514 // Restore from cache
515 foreach ( self::$mCacheVars as $name ) {
516 $this->$name = $data[$name];
517 }
518
519 return true;
520 }
521
523 // @{
524
541 public static function newFromName( $name, $validate = 'valid' ) {
542 if ( $validate === true ) {
543 $validate = 'valid';
544 }
545 $name = self::getCanonicalName( $name, $validate );
546 if ( $name === false ) {
547 return false;
548 }
549
550 // Create unloaded user object
551 $u = new User;
552 $u->mName = $name;
553 $u->mFrom = 'name';
554 $u->setItemLoaded( 'name' );
555
556 return $u;
557 }
558
565 public static function newFromId( $id ) {
566 $u = new User;
567 $u->mId = $id;
568 $u->mFrom = 'id';
569 $u->setItemLoaded( 'id' );
570 return $u;
571 }
572
580 public static function newFromActorId( $id ) {
581 $u = new User;
582 $u->mActorId = $id;
583 $u->mFrom = 'actor';
584 $u->setItemLoaded( 'actor' );
585 return $u;
586 }
587
597 public static function newFromIdentity( UserIdentity $identity ) {
598 return MediaWikiServices::getInstance()
599 ->getUserFactory()
600 ->newFromUserIdentity( $identity );
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 __METHOD__
683 );
684
685 return $id ? self::newFromId( $id ) : null;
686 }
687
695 public static function newFromSession( WebRequest $request = null ) {
696 $user = new User;
697 $user->mFrom = 'session';
698 $user->mRequest = $request;
699 return $user;
700 }
701
717 public static function newFromRow( $row, $data = null ) {
718 $user = new User;
719 $user->loadFromRow( $row, $data );
720 return $user;
721 }
722
758 public static function newSystemUser( $name, $options = [] ) {
759 $options += [
760 'validate' => 'valid',
761 'create' => true,
762 'steal' => false,
763 ];
764
765 $name = self::getCanonicalName( $name, $options['validate'] );
766 if ( $name === false ) {
767 return null;
768 }
769
771 $userQuery = self::getQueryInfo();
772 $row = $dbr->selectRow(
773 $userQuery['tables'],
774 $userQuery['fields'],
775 [ 'user_name' => $name ],
776 __METHOD__,
777 [],
778 $userQuery['joins']
779 );
780 if ( !$row ) {
781 // Try the master database...
782 $dbw = wfGetDB( DB_MASTER );
783 $row = $dbw->selectRow(
784 $userQuery['tables'],
785 $userQuery['fields'],
786 [ 'user_name' => $name ],
787 __METHOD__,
788 [],
789 $userQuery['joins']
790 );
791 }
792
793 if ( !$row ) {
794 // No user. Create it?
795 // @phan-suppress-next-line PhanImpossibleCondition
796 if ( !$options['create'] ) {
797 // No.
798 return null;
799 }
800
801 // If it's a reserved user that had an anonymous actor created for it at
802 // some point, we need special handling.
803 if ( !self::isValidUserName( $name ) || self::isUsableName( $name ) ) {
804 // Not reserved, so just create it.
805 return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
806 }
807
808 // It is reserved. Check for an anonymous actor row.
809 $dbw = wfGetDB( DB_MASTER );
810 return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $name ) {
811 $row = $dbw->selectRow(
812 'actor',
813 [ 'actor_id' ],
814 [ 'actor_name' => $name, 'actor_user' => null ],
815 $fname,
816 [ 'FOR UPDATE' ]
817 );
818 if ( !$row ) {
819 // No anonymous actor.
820 return self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
821 }
822
823 // There is an anonymous actor. Delete the actor row so we can create the user,
824 // then restore the old actor_id so as to not break existing references.
825 // @todo If MediaWiki ever starts using foreign keys for `actor`, this will break things.
826 $dbw->delete( 'actor', [ 'actor_id' => $row->actor_id ], $fname );
827 $user = self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] );
828 $dbw->update(
829 'actor',
830 [ 'actor_id' => $row->actor_id ],
831 [ 'actor_id' => $user->getActorId() ],
832 $fname
833 );
834 $user->clearInstanceCache( 'id' );
835 $user->invalidateCache();
836 return $user;
837 } );
838 }
839
840 $user = self::newFromRow( $row );
841
842 if ( !$user->isSystemUser() ) {
843 // User exists. Steal it?
844 // @phan-suppress-next-line PhanRedundantCondition
845 if ( !$options['steal'] ) {
846 return null;
847 }
848
849 MediaWikiServices::getInstance()->getAuthManager()->revokeAccessForUser( $name );
850
851 $user->invalidateEmail();
852 $user->mToken = self::INVALID_TOKEN;
853 $user->saveSettings();
854 SessionManager::singleton()->preventSessionsForUser( $user->getName() );
855 }
856
857 return $user;
858 }
859
860 // @}
861
867 public static function whoIs( $id ) {
868 return UserCache::singleton()->getProp( $id, 'name' );
869 }
870
877 public static function whoIsReal( $id ) {
878 return UserCache::singleton()->getProp( $id, 'real_name' );
879 }
880
887 public static function idFromName( $name, $flags = self::READ_NORMAL ) {
888 // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
889 $name = (string)$name;
890 $nt = Title::makeTitleSafe( NS_USER, $name );
891 if ( $nt === null ) {
892 // Illegal name
893 return null;
894 }
895
896 if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
897 return self::$idCacheByName[$name] === null ? null : (int)self::$idCacheByName[$name];
898 }
899
900 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
901 $db = wfGetDB( $index );
902
903 $s = $db->selectRow(
904 'user',
905 [ 'user_id' ],
906 [ 'user_name' => $nt->getText() ],
907 __METHOD__,
908 $options
909 );
910
911 if ( $s === false ) {
912 $result = null;
913 } else {
914 $result = (int)$s->user_id;
915 }
916
917 if ( count( self::$idCacheByName ) >= 1000 ) {
918 self::$idCacheByName = [];
919 }
920
921 self::$idCacheByName[$name] = $result;
922
923 return $result;
924 }
925
929 public static function resetIdByNameCache() {
930 self::$idCacheByName = [];
931 }
932
951 public static function isIP( $name ) {
952 return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
953 || IPUtils::isIPv6( $name );
954 }
955
963 public function isIPRange() {
964 return IPUtils::isValidRange( $this->mName );
965 }
966
979 public static function isValidUserName( $name ) {
980 return MediaWikiServices::getInstance()->getUserNameUtils()->isValid( $name );
981 }
982
995 public static function isUsableName( $name ) {
996 return MediaWikiServices::getInstance()->getUserNameUtils()->isUsable( $name );
997 }
998
1009 public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1010 if ( $groups === [] ) {
1011 return UserArrayFromResult::newFromIDs( [] );
1012 }
1013
1014 $groups = array_unique( (array)$groups );
1015 $limit = min( 5000, $limit );
1016
1017 $conds = [ 'ug_group' => $groups ];
1018 if ( $after !== null ) {
1019 $conds[] = 'ug_user > ' . (int)$after;
1020 }
1021
1022 $dbr = wfGetDB( DB_REPLICA );
1023 $ids = $dbr->selectFieldValues(
1024 'user_groups',
1025 'ug_user',
1026 $conds,
1027 __METHOD__,
1028 [
1029 'DISTINCT' => true,
1030 'ORDER BY' => 'ug_user',
1031 'LIMIT' => $limit,
1032 ]
1033 ) ?: [];
1034 return UserArray::newFromIDs( $ids );
1035 }
1036
1050 public static function isCreatableName( $name ) {
1051 return MediaWikiServices::getInstance()->getUserNameUtils()->isCreatable( $name );
1052 }
1053
1060 public function isValidPassword( $password ) {
1061 // simple boolean wrapper for checkPasswordValidity
1062 return $this->checkPasswordValidity( $password )->isGood();
1063 }
1064
1086 public function checkPasswordValidity( $password ) {
1087 global $wgPasswordPolicy;
1088
1089 $upp = new UserPasswordPolicy(
1090 $wgPasswordPolicy['policies'],
1091 $wgPasswordPolicy['checks']
1092 );
1093
1094 $status = Status::newGood( [] );
1095 $result = false; // init $result to false for the internal checks
1096
1097 if ( !$this->getHookRunner()->onIsValidPassword( $password, $result, $this ) ) {
1098 $status->error( $result );
1099 return $status;
1100 }
1101
1102 if ( $result === false ) {
1103 $status->merge( $upp->checkUserPassword( $this, $password ), true );
1104 return $status;
1105 }
1106
1107 if ( $result === true ) {
1108 return $status;
1109 }
1110
1111 $status->error( $result );
1112 return $status; // the isValidPassword hook set a string $result and returned true
1113 }
1114
1130 public static function getCanonicalName( $name, $validate = 'valid' ) {
1131 // Backwards compatibility with strings / false
1132 $validationLevels = [
1133 'valid' => UserNameUtils::RIGOR_VALID,
1134 'usable' => UserNameUtils::RIGOR_USABLE,
1135 'creatable' => UserNameUtils::RIGOR_CREATABLE
1136 ];
1137
1138 if ( $validate === false ) {
1139 $validation = UserNameUtils::RIGOR_NONE;
1140 } elseif ( array_key_exists( $validate, $validationLevels ) ) {
1141 $validation = $validationLevels[ $validate ];
1142 } else {
1143 // Not a recognized value, probably a test for unsupported validation
1144 // levels, regardless, just pass it along
1145 $validation = $validate;
1146 }
1147
1148 return MediaWikiServices::getInstance()
1149 ->getUserNameUtils()
1150 ->getCanonical( (string)$name, $validation );
1151 }
1152
1162 public function loadDefaults( $name = false, $actorId = null ) {
1163 $this->mId = 0;
1164 $this->mName = $name;
1165 $this->mActorId = $actorId;
1166 $this->mRealName = '';
1167 $this->mEmail = '';
1168
1169 $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1170 ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1171 if ( $loggedOut !== 0 ) {
1172 $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1173 } else {
1174 $this->mTouched = '1'; # Allow any pages to be cached
1175 }
1176
1177 $this->mToken = null; // Don't run cryptographic functions till we need a token
1178 $this->mEmailAuthenticated = null;
1179 $this->mEmailToken = '';
1180 $this->mEmailTokenExpires = null;
1181 $this->mRegistration = wfTimestamp( TS_MW );
1182
1183 $this->getHookRunner()->onUserLoadDefaults( $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 $this->getHookRunner()->onUserLoadFromDatabase( $this, $s );
1281
1282 if ( $s !== false ) {
1283 // Initialise user table data
1284 $this->loadFromRow( $s );
1285 $this->getEditCount(); // revalidation for nulls
1286 return true;
1287 }
1288
1289 // Invalid user_id
1290 $this->mId = 0;
1291 $this->loadDefaults();
1292
1293 return false;
1294 }
1295
1308 protected function loadFromRow( $row, $data = null ) {
1309 if ( !is_object( $row ) ) {
1310 throw new InvalidArgumentException( '$row must be an object' );
1311 }
1312
1313 $all = true;
1314
1315 if ( isset( $row->actor_id ) ) {
1316 $this->mActorId = (int)$row->actor_id;
1317 if ( $this->mActorId !== 0 ) {
1318 $this->mFrom = 'actor';
1319 }
1320 $this->setItemLoaded( 'actor' );
1321 } else {
1322 $all = false;
1323 }
1324
1325 if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1326 $this->mName = $row->user_name;
1327 $this->mFrom = 'name';
1328 $this->setItemLoaded( 'name' );
1329 } else {
1330 $all = false;
1331 }
1332
1333 if ( isset( $row->user_real_name ) ) {
1334 $this->mRealName = $row->user_real_name;
1335 $this->setItemLoaded( 'realname' );
1336 } else {
1337 $all = false;
1338 }
1339
1340 if ( isset( $row->user_id ) ) {
1341 $this->mId = intval( $row->user_id );
1342 if ( $this->mId !== 0 ) {
1343 $this->mFrom = 'id';
1344 }
1345 $this->setItemLoaded( 'id' );
1346 } else {
1347 $all = false;
1348 }
1349
1350 if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1351 self::$idCacheByName[$row->user_name] = $row->user_id;
1352 }
1353
1354 if ( isset( $row->user_editcount ) ) {
1355 $this->mEditCount = $row->user_editcount;
1356 } else {
1357 $all = false;
1358 }
1359
1360 if ( isset( $row->user_touched ) ) {
1361 $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1362 } else {
1363 $all = false;
1364 }
1365
1366 if ( isset( $row->user_token ) ) {
1367 // The definition for the column is binary(32), so trim the NULs
1368 // that appends. The previous definition was char(32), so trim
1369 // spaces too.
1370 $this->mToken = rtrim( $row->user_token, " \0" );
1371 if ( $this->mToken === '' ) {
1372 $this->mToken = null;
1373 }
1374 } else {
1375 $all = false;
1376 }
1377
1378 if ( isset( $row->user_email ) ) {
1379 $this->mEmail = $row->user_email;
1380 $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1381 $this->mEmailToken = $row->user_email_token;
1382 $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1383 $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1384 } else {
1385 $all = false;
1386 }
1387
1388 if ( $all ) {
1389 $this->mLoadedItems = true;
1390 }
1391
1392 if ( is_array( $data ) ) {
1393
1394 if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1395 MediaWikiServices::getInstance()
1396 ->getUserGroupManager()
1397 ->loadGroupMembershipsFromArray(
1398 $this,
1399 $data['user_groups'],
1400 $this->queryFlagsUsed
1401 );
1402 }
1403 if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1404 MediaWikiServices::getInstance()
1405 ->getUserOptionsManager()
1406 ->loadUserOptions( $this, $this->queryFlagsUsed, $data['user_properties'] );
1407 }
1408 }
1409 }
1410
1416 protected function loadFromUserObject( $user ) {
1417 $user->load();
1418 foreach ( self::$mCacheVars as $var ) {
1419 $this->$var = $user->$var;
1420 }
1421 }
1422
1438 public function addAutopromoteOnceGroups( $event ) {
1439 return MediaWikiServices::getInstance()
1440 ->getUserGroupManager()
1441 ->addUserToAutopromoteOnceGroups( $this, $event );
1442 }
1443
1453 protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1454 if ( $this->mTouched ) {
1455 // CAS check: only update if the row wasn't changed sicne it was loaded.
1456 $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1457 }
1458
1459 return $conditions;
1460 }
1461
1472 public function checkAndSetTouched() {
1473 $this->load();
1474
1475 if ( !$this->mId ) {
1476 return false; // anon
1477 }
1478
1479 // Get a new user_touched that is higher than the old one
1480 $newTouched = $this->newTouchedTimestamp();
1481
1482 $dbw = wfGetDB( DB_MASTER );
1483 $dbw->update( 'user',
1484 [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1485 $this->makeUpdateConditions( $dbw, [
1486 'user_id' => $this->mId,
1487 ] ),
1488 __METHOD__
1489 );
1490 $success = ( $dbw->affectedRows() > 0 );
1491
1492 if ( $success ) {
1493 $this->mTouched = $newTouched;
1494 $this->clearSharedCache( 'changed' );
1495 } else {
1496 // Clears on failure too since that is desired if the cache is stale
1497 $this->clearSharedCache( 'refresh' );
1498 }
1499
1500 return $success;
1501 }
1502
1510 public function clearInstanceCache( $reloadFrom = false ) {
1511 global $wgFullyInitialised;
1512
1513 $this->mDatePreference = null;
1514 $this->mBlockedby = -1; # Unset
1515 $this->mHash = false;
1516 $this->mEditCount = null;
1517
1518 if ( $wgFullyInitialised && $this->mFrom ) {
1519 $services = MediaWikiServices::getInstance();
1520 $services->getPermissionManager()->invalidateUsersRightsCache( $this );
1521 $services->getUserOptionsManager()->clearUserOptionsCache( $this );
1522 $services->getTalkPageNotificationManager()->clearInstanceCache( $this );
1523 $services->getUserGroupManager()->clearCache( $this );
1524 $services->getUserEditTracker()->clearUserEditCache( $this );
1525 }
1526
1527 if ( $reloadFrom ) {
1528 $this->mLoadedItems = [];
1529 $this->mFrom = $reloadFrom;
1530 }
1531 }
1532
1540 public static function getDefaultOptions() {
1541 return MediaWikiServices::getInstance()
1542 ->getUserOptionsLookup()
1543 ->getDefaultOptions();
1544 }
1545
1553 public static function getDefaultOption( $opt ) {
1554 return MediaWikiServices::getInstance()
1555 ->getUserOptionsLookup()
1556 ->getDefaultOption( $opt );
1557 }
1558
1570 private function getBlockedStatus( $fromReplica = true, $disableIpBlockExemptChecking = false ) {
1571 if ( $this->mBlockedby != -1 ) {
1572 return;
1573 }
1574
1575 wfDebug( __METHOD__ . ": checking blocked status for " . $this->getName() );
1576
1577 // Initialize data...
1578 // Otherwise something ends up stomping on $this->mBlockedby when
1579 // things get lazy-loaded later, causing false positive block hits
1580 // due to -1 !== 0. Probably session-related... Nothing should be
1581 // overwriting mBlockedby, surely?
1582 $this->load();
1583
1584 // TODO: Block checking shouldn't really be done from the User object. Block
1585 // checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
1586 // which need more knowledge of the request context than the User should have.
1587 // Since we do currently check blocks from the User, we have to do the following
1588 // here:
1589 // - Check if this is the user associated with the main request
1590 // - If so, pass the relevant request information to the block manager
1591 $request = null;
1592
1593 // The session user is set up towards the end of Setup.php. Until then,
1594 // assume it's a logged-out user.
1595 $sessionUser = RequestContext::getMain()->getUser();
1596 $globalUserName = $sessionUser->isSafeToLoad()
1597 ? $sessionUser->getName()
1598 : IPUtils::sanitizeIP( $sessionUser->getRequest()->getIP() );
1599
1600 if ( $this->getName() === $globalUserName ) {
1601 // This is the global user, so we need to pass the request
1602 $request = $this->getRequest();
1603 }
1604
1605 $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
1606 $this,
1607 $request,
1608 $fromReplica,
1609 $disableIpBlockExemptChecking
1610 );
1611
1612 if ( $block ) {
1613 $this->mBlock = $block;
1614 $this->mBlockedby = $block->getByName();
1615 $this->mBlockreason = $block->getReason();
1616 $this->mHideName = $block->getHideName();
1617 $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1618 } else {
1619 $this->mBlock = null;
1620 $this->mBlockedby = '';
1621 $this->mBlockreason = '';
1622 $this->mHideName = 0;
1623 $this->mAllowUsertalk = false;
1624 }
1625 }
1626
1635 public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1636 wfDeprecated( __METHOD__, '1.34' );
1637 return MediaWikiServices::getInstance()->getBlockManager()
1638 ->isDnsBlacklisted( $ip, $checkWhitelist );
1639 }
1640
1649 public function inDnsBlacklist( $ip, $bases ) {
1650 wfDeprecated( __METHOD__, '1.34' );
1651
1652 $found = false;
1653 // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1654 if ( IPUtils::isIPv4( $ip ) ) {
1655 // Reverse IP, T23255
1656 $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1657
1658 foreach ( (array)$bases as $base ) {
1659 // Make hostname
1660 // If we have an access key, use that too (ProjectHoneypot, etc.)
1661 $basename = $base;
1662 if ( is_array( $base ) ) {
1663 if ( count( $base ) >= 2 ) {
1664 // Access key is 1, base URL is 0
1665 $host = "{$base[1]}.$ipReversed.{$base[0]}";
1666 } else {
1667 $host = "$ipReversed.{$base[0]}";
1668 }
1669 $basename = $base[0];
1670 } else {
1671 $host = "$ipReversed.$base";
1672 }
1673
1674 // Send query
1675 $ipList = gethostbynamel( $host );
1676
1677 if ( $ipList ) {
1678 wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1679 $found = true;
1680 break;
1681 }
1682
1683 wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1684 }
1685 }
1686
1687 return $found;
1688 }
1689
1697 public static function isLocallyBlockedProxy( $ip ) {
1698 wfDeprecated( __METHOD__, '1.34' );
1699
1700 global $wgProxyList;
1701
1702 if ( !$wgProxyList ) {
1703 return false;
1704 }
1705
1706 if ( !is_array( $wgProxyList ) ) {
1707 // Load values from the specified file
1708 $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1709 }
1710
1711 $proxyListIPSet = new IPSet( $wgProxyList );
1712 return $proxyListIPSet->match( $ip );
1713 }
1714
1720 public function isPingLimitable() {
1722 if ( IPUtils::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1723 // No other good way currently to disable rate limits
1724 // for specific IPs. :P
1725 // But this is a crappy hack and should die.
1726 return false;
1727 }
1728 return !$this->isAllowed( 'noratelimit' );
1729 }
1730
1747 public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1748 $logger = \MediaWiki\Logger\LoggerFactory::getInstance( 'ratelimit' );
1749
1750 // Call the 'PingLimiter' hook
1751 $result = false;
1752 if ( !$this->getHookRunner()->onPingLimiter( $this, $action, $result, $incrBy ) ) {
1753 return $result;
1754 }
1755
1756 global $wgRateLimits;
1757 if ( !isset( $wgRateLimits[$action] ) ) {
1758 return false;
1759 }
1760
1761 $limits = array_merge(
1762 [ '&can-bypass' => true ],
1763 $wgRateLimits[$action]
1764 );
1765
1766 // Some groups shouldn't trigger the ping limiter, ever
1767 if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1768 return false;
1769 }
1770
1771 $logger->debug( __METHOD__ . ": limiting $action rate for {$this->getName()}" );
1772
1773 $keys = [];
1774 $id = $this->getId();
1775 $isNewbie = $this->isNewbie();
1776 $cache = ObjectCache::getLocalClusterInstance();
1777
1778 if ( $id == 0 ) {
1779 // "shared anon" limit, for all anons combined
1780 if ( isset( $limits['anon'] ) ) {
1781 $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1782 }
1783 } else {
1784 // "global per name" limit, across sites
1785 if ( isset( $limits['user-global'] ) ) {
1786 $lookup = CentralIdLookup::factoryNonLocal();
1787
1788 $centralId = $lookup
1789 ? $lookup->centralIdFromLocalUser( $this, CentralIdLookup::AUDIENCE_RAW )
1790 : 0;
1791
1792 if ( $centralId ) {
1793 // We don't have proper realms, use provider ID.
1794 $realm = $lookup->getProviderId();
1795
1796 $globalKey = $cache->makeGlobalKey( 'limiter', $action, 'user-global',
1797 $realm, $centralId );
1798 } else {
1799 // Fall back to a local key for a local ID
1800 $globalKey = $cache->makeKey( 'limiter', $action, 'user-global',
1801 'local', $id );
1802 }
1803 $keys[$globalKey] = $limits['user-global'];
1804 }
1805 }
1806
1807 if ( $isNewbie ) {
1808 // "per ip" limit for anons and newbie users
1809 if ( isset( $limits['ip'] ) ) {
1810 $ip = $this->getRequest()->getIP();
1811 $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
1812 }
1813 // "per subnet" limit for anons and newbie users
1814 if ( isset( $limits['subnet'] ) ) {
1815 $ip = $this->getRequest()->getIP();
1816 $subnet = IPUtils::getSubnet( $ip );
1817 if ( $subnet !== false ) {
1818 $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
1819 }
1820 }
1821 }
1822
1823 // determine the "per user account" limit
1824 $userLimit = false;
1825 if ( $id !== 0 && isset( $limits['user'] ) ) {
1826 // default limit for logged-in users
1827 $userLimit = $limits['user'];
1828 }
1829 // limits for newbie logged-in users (overrides all the normal user limits)
1830 if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1831 $userLimit = $limits['newbie'];
1832 } else {
1833 // Check for group-specific limits
1834 // If more than one group applies, use the highest allowance (if higher than the default)
1835 foreach ( $this->getGroups() as $group ) {
1836 if ( isset( $limits[$group] ) ) {
1837 if ( $userLimit === false
1838 || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1839 ) {
1840 $userLimit = $limits[$group];
1841 }
1842 }
1843 }
1844 }
1845
1846 // Set the user limit key
1847 if ( $userLimit !== false ) {
1848 // phan is confused because &can-bypass's value is a bool, so it assumes
1849 // that $userLimit is also a bool here.
1850 // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1851 list( $max, $period ) = $userLimit;
1852 $logger->debug( __METHOD__ . ": effective user limit: $max in {$period}s" );
1853 $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
1854 }
1855
1856 // ip-based limits for all ping-limitable users
1857 if ( isset( $limits['ip-all'] ) ) {
1858 $ip = $this->getRequest()->getIP();
1859 // ignore if user limit is more permissive
1860 if ( $isNewbie || $userLimit === false
1861 || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1862 $keys[$cache->makeGlobalKey( 'limiter', $action, 'ip-all', $ip )] = $limits['ip-all'];
1863 }
1864 }
1865
1866 // subnet-based limits for all ping-limitable users
1867 if ( isset( $limits['subnet-all'] ) ) {
1868 $ip = $this->getRequest()->getIP();
1869 $subnet = IPUtils::getSubnet( $ip );
1870 if ( $subnet !== false ) {
1871 // ignore if user limit is more permissive
1872 if ( $isNewbie || $userLimit === false
1873 || $limits['ip-all'][0] / $limits['ip-all'][1]
1874 > $userLimit[0] / $userLimit[1] ) {
1875 $keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet-all', $subnet )] = $limits['subnet-all'];
1876 }
1877 }
1878 }
1879
1880 // XXX: We may want to use $cache->getCurrentTime() here, but that would make it
1881 // harder to test for T246991. Also $cache->getCurrentTime() is documented
1882 // as being for testing only, so it apparently should not be called here.
1883 $now = MWTimestamp::time();
1884 $clockFudge = 3; // avoid log spam when a clock is slightly off
1885
1886 $triggered = false;
1887 foreach ( $keys as $key => $limit ) {
1888
1889 // Do the update in a merge callback, for atomicity.
1890 // To use merge(), we need to explicitly track the desired expiry timestamp.
1891 // This tracking was introduced to investigate T246991. Once it is no longer needed,
1892 // we could go back to incrWithInit(), though that has more potential for race
1893 // conditions between the get() and incrWithInit() calls.
1894 $cache->merge(
1895 $key,
1896 function ( $cache, $key, $data, &$expiry )
1897 use ( $action, $logger, &$triggered, $now, $clockFudge, $limit, $incrBy )
1898 {
1899 // phan is confused because &can-bypass's value is a bool, so it assumes
1900 // that $userLimit is also a bool here.
1901 // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
1902 list( $max, $period ) = $limit;
1903
1904 $expiry = $now + (int)$period;
1905 $count = 0;
1906
1907 // Already pinged?
1908 if ( $data ) {
1909 // NOTE: in order to investigate T246991, we write the expiry time
1910 // into the payload, along with the count.
1911 $fields = explode( '|', $data );
1912 $storedCount = (int)( $fields[0] ?? 0 );
1913 $storedExpiry = (int)( $fields[1] ?? PHP_INT_MAX );
1914
1915 // Found a stale entry. This should not happen!
1916 if ( $storedExpiry < ( $now + $clockFudge ) ) {
1917 $logger->info(
1918 'User::pingLimiter: '
1919 . 'Stale rate limit entry, cache key failed to expire (T246991)',
1920 [
1921 'action' => $action,
1922 'user' => $this->getName(),
1923 'limit' => $max,
1924 'period' => $period,
1925 'count' => $storedCount,
1926 'key' => $key,
1927 'expiry' => MWTimestamp::convert( TS_DB, $storedExpiry ),
1928 ]
1929 );
1930 } else {
1931 // NOTE: We'll keep the original expiry when bumping counters,
1932 // resulting in a kind of fixed-window throttle.
1933 $expiry = min( $storedExpiry, $now + (int)$period );
1934 $count = $storedCount;
1935 }
1936 }
1937
1938 // Limit exceeded!
1939 if ( $count >= $max ) {
1940 if ( !$triggered ) {
1941 $logger->info(
1942 'User::pingLimiter: User tripped rate limit',
1943 [
1944 'action' => $action,
1945 'user' => $this->getName(),
1946 'ip' => $this->getRequest()->getIP(),
1947 'limit' => $max,
1948 'period' => $period,
1949 'count' => $count,
1950 'key' => $key
1951 ]
1952 );
1953 }
1954
1955 $triggered = true;
1956 }
1957
1958 $count += $incrBy;
1959 $data = "$count|$expiry";
1960 return $data;
1961 }
1962 );
1963 }
1964
1965 return $triggered;
1966 }
1967
1979 public function isBlocked( $fromReplica = true ) {
1980 return $this->getBlock( $fromReplica ) instanceof AbstractBlock;
1981 }
1982
1991 public function getBlock( $fromReplica = true, $disableIpBlockExemptChecking = false ) {
1992 $this->getBlockedStatus( $fromReplica, $disableIpBlockExemptChecking );
1993 return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
1994 }
1995
2007 public function isBlockedFrom( $title, $fromReplica = false ) {
2008 return MediaWikiServices::getInstance()->getPermissionManager()
2009 ->isBlockedFrom( $this, $title, $fromReplica );
2010 }
2011
2016 public function blockedBy() {
2017 $this->getBlockedStatus();
2018 return $this->mBlockedby;
2019 }
2020
2027 public function blockedFor() {
2028 $this->getBlockedStatus();
2029 return $this->mBlockreason;
2030 }
2031
2036 public function getBlockId() {
2037 $this->getBlockedStatus();
2038 return ( $this->mBlock ? $this->mBlock->getId() : false );
2039 }
2040
2049 public function isBlockedGlobally( $ip = '' ) {
2050 return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
2051 }
2052
2063 public function getGlobalBlock( $ip = '' ) {
2064 if ( $this->mGlobalBlock !== null ) {
2065 return $this->mGlobalBlock ?: null;
2066 }
2067 // User is already an IP?
2068 if ( IPUtils::isIPAddress( $this->getName() ) ) {
2069 $ip = $this->getName();
2070 } elseif ( !$ip ) {
2071 $ip = $this->getRequest()->getIP();
2072 }
2073 $blocked = false;
2074 $block = null;
2075 $this->getHookRunner()->onUserIsBlockedGlobally( $this, $ip, $blocked, $block );
2076
2077 if ( $blocked && $block === null ) {
2078 // back-compat: UserIsBlockedGlobally didn't have $block param first
2079 $block = new SystemBlock( [
2080 'address' => $ip,
2081 'systemBlock' => 'global-block'
2082 ] );
2083 }
2084
2085 $this->mGlobalBlock = $blocked ? $block : false;
2086 return $this->mGlobalBlock ?: null;
2087 }
2088
2094 public function isLocked() {
2095 if ( $this->mLocked !== null ) {
2096 return $this->mLocked;
2097 }
2098 // Reset for hook
2099 $this->mLocked = false;
2100 $this->getHookRunner()->onUserIsLocked( $this, $this->mLocked );
2101 return $this->mLocked;
2102 }
2103
2109 public function isHidden() {
2110 if ( $this->mHideName !== null ) {
2111 return (bool)$this->mHideName;
2112 }
2113 $this->getBlockedStatus();
2114 return (bool)$this->mHideName;
2115 }
2116
2121 public function getId() {
2122 if ( $this->mId === null && $this->mName !== null &&
2123 ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) )
2124 ) {
2125 // Special case, we know the user is anonymous
2126 return 0;
2127 }
2128
2129 if ( !$this->isItemLoaded( 'id' ) ) {
2130 // Don't load if this was initialized from an ID
2131 $this->load();
2132 }
2133
2134 return (int)$this->mId;
2135 }
2136
2141 public function setId( $v ) {
2142 $this->mId = $v;
2143 $this->clearInstanceCache( 'id' );
2144 }
2145
2150 public function getName() {
2151 if ( $this->isItemLoaded( 'name', 'only' ) ) {
2152 // Special case optimisation
2153 return $this->mName;
2154 }
2155
2156 $this->load();
2157 if ( $this->mName === false ) {
2158 // Clean up IPs
2159 $this->mName = IPUtils::sanitizeIP( $this->getRequest()->getIP() );
2160 }
2161
2162 return $this->mName;
2163 }
2164
2178 public function setName( $str ) {
2179 $this->load();
2180 $this->mName = $str;
2181 }
2182
2189 public function getActorId( IDatabase $dbw = null ) {
2190 if ( !$this->isItemLoaded( 'actor' ) ) {
2191 $this->load();
2192 }
2193
2194 if ( !$this->mActorId && $dbw ) {
2195 $migration = MediaWikiServices::getInstance()->getActorMigration();
2196 $this->mActorId = $migration->getNewActorId( $dbw, $this );
2197
2198 $this->invalidateCache();
2199 $this->setItemLoaded( 'actor' );
2200 }
2201
2202 return (int)$this->mActorId;
2203 }
2204
2209 public function getTitleKey() {
2210 return str_replace( ' ', '_', $this->getName() );
2211 }
2212
2218 public function getNewtalk() {
2219 wfDeprecated( __METHOD__, '1.35' );
2220 return MediaWikiServices::getInstance()
2221 ->getTalkPageNotificationManager()
2222 ->userHasNewMessages( $this );
2223 }
2224
2241 public function getNewMessageLinks() {
2242 wfDeprecated( __METHOD__, '1.35' );
2243 $talks = [];
2244 if ( !$this->getHookRunner()->onUserRetrieveNewTalks( $this, $talks ) ) {
2245 return $talks;
2246 }
2247
2248 $services = MediaWikiServices::getInstance();
2249 $userHasNewMessages = $services->getTalkPageNotificationManager()
2250 ->userHasNewMessages( $this );
2251 if ( !$userHasNewMessages ) {
2252 return [];
2253 }
2254 $utp = $this->getTalkPage();
2255 $timestamp = $services->getTalkPageNotificationManager()
2256 ->getLatestSeenMessageTimestamp( $this );
2257 $rev = null;
2258 if ( $timestamp ) {
2259 $revRecord = $services->getRevisionLookup()
2260 ->getRevisionByTimestamp( $utp, $timestamp );
2261 if ( $revRecord ) {
2262 $rev = new Revision( $revRecord );
2263 }
2264 }
2265 return [
2266 [
2267 'wiki' => WikiMap::getCurrentWikiId(),
2268 'link' => $utp->getLocalURL(),
2269 'rev' => $rev
2270 ]
2271 ];
2272 }
2273
2280 public function getNewMessageRevisionId() {
2281 wfDeprecated( __METHOD__, '1.35' );
2282 $newMessageRevisionId = null;
2283 $newMessageLinks = $this->getNewMessageLinks();
2284
2285 // Note: getNewMessageLinks() never returns more than a single link
2286 // and it is always for the same wiki, but we double-check here in
2287 // case that changes some time in the future.
2288 if ( $newMessageLinks && count( $newMessageLinks ) === 1
2289 && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2290 && $newMessageLinks[0]['rev']
2291 ) {
2293 $newMessageRevision = $newMessageLinks[0]['rev'];
2294 $newMessageRevisionId = $newMessageRevision->getId();
2295 }
2296
2297 return $newMessageRevisionId;
2298 }
2299
2308 public function setNewtalk( $val, $curRev = null ) {
2309 wfDeprecated( __METHOD__, '1.35' );
2310 if ( $curRev && $curRev instanceof Revision ) {
2311 $curRev = $curRev->getRevisionRecord();
2312 }
2313 if ( $val ) {
2314 MediaWikiServices::getInstance()
2315 ->getTalkPageNotificationManager()
2316 ->setUserHasNewMessages( $this, $curRev );
2317 } else {
2318 MediaWikiServices::getInstance()
2319 ->getTalkPageNotificationManager()
2320 ->removeUserHasNewMessages( $this );
2321 }
2322 }
2323
2330 private function newTouchedTimestamp() {
2331 $time = time();
2332 if ( $this->mTouched ) {
2333 $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2334 }
2335
2336 return wfTimestamp( TS_MW, $time );
2337 }
2338
2349 public function clearSharedCache( $mode = 'refresh' ) {
2350 if ( !$this->getId() ) {
2351 return;
2352 }
2353
2354 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2355 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2356 $key = $this->getCacheKey( $cache );
2357
2358 if ( $mode === 'refresh' ) {
2359 $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2360 } else {
2361 $lb->getConnectionRef( DB_MASTER )->onTransactionPreCommitOrIdle(
2362 function () use ( $cache, $key ) {
2363 $cache->delete( $key );
2364 },
2365 __METHOD__
2366 );
2367 }
2368 }
2369
2375 public function invalidateCache() {
2376 $this->touch();
2377 $this->clearSharedCache( 'changed' );
2378 }
2379
2392 public function touch() {
2393 $id = $this->getId();
2394 if ( $id ) {
2395 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2396 $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2397 $cache->touchCheckKey( $key );
2398 $this->mQuickTouched = null;
2399 }
2400 }
2401
2407 public function validateCache( $timestamp ) {
2408 return ( $timestamp >= $this->getTouched() );
2409 }
2410
2419 public function getTouched() {
2420 $this->load();
2421
2422 if ( $this->mId ) {
2423 if ( $this->mQuickTouched === null ) {
2424 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2425 $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2426
2427 $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2428 }
2429
2430 return max( $this->mTouched, $this->mQuickTouched );
2431 }
2432
2433 return $this->mTouched;
2434 }
2435
2441 public function getDBTouched() {
2442 $this->load();
2443
2444 return $this->mTouched;
2445 }
2446
2459 public function changeAuthenticationData( array $data ) {
2460 $manager = MediaWikiServices::getInstance()->getAuthManager();
2461 $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2462 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2463
2464 $status = Status::newGood( 'ignored' );
2465 foreach ( $reqs as $req ) {
2466 $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2467 }
2468 if ( $status->getValue() === 'ignored' ) {
2469 $status->warning( 'authenticationdatachange-ignored' );
2470 }
2471
2472 if ( $status->isGood() ) {
2473 foreach ( $reqs as $req ) {
2474 $manager->changeAuthenticationData( $req );
2475 }
2476 }
2477 return $status;
2478 }
2479
2486 public function getToken( $forceCreation = true ) {
2488
2489 $this->load();
2490 if ( !$this->mToken && $forceCreation ) {
2491 $this->setToken();
2492 }
2493
2494 if ( !$this->mToken ) {
2495 // The user doesn't have a token, return null to indicate that.
2496 return null;
2497 }
2498
2499 if ( $this->mToken === self::INVALID_TOKEN ) {
2500 // We return a random value here so existing token checks are very
2501 // likely to fail.
2502 return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2503 }
2504
2505 if ( $wgAuthenticationTokenVersion === null ) {
2506 // $wgAuthenticationTokenVersion not in use, so return the raw secret
2507 return $this->mToken;
2508 }
2509
2510 // $wgAuthenticationTokenVersion in use, so hmac it.
2511 $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2512
2513 // The raw hash can be overly long. Shorten it up.
2514 $len = max( 32, self::TOKEN_LENGTH );
2515 if ( strlen( $ret ) < $len ) {
2516 // Should never happen, even md5 is 128 bits
2517 throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2518 }
2519
2520 return substr( $ret, -$len );
2521 }
2522
2529 public function setToken( $token = false ) {
2530 $this->load();
2531 if ( $this->mToken === self::INVALID_TOKEN ) {
2532 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
2533 ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2534 } elseif ( !$token ) {
2535 $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2536 } else {
2537 $this->mToken = $token;
2538 }
2539 }
2540
2545 public function getEmail() {
2546 $this->load();
2547 $this->getHookRunner()->onUserGetEmail( $this, $this->mEmail );
2548 return $this->mEmail;
2549 }
2550
2556 $this->load();
2557 $this->getHookRunner()->onUserGetEmailAuthenticationTimestamp(
2558 $this, $this->mEmailAuthenticated );
2560 }
2561
2566 public function setEmail( $str ) {
2567 $this->load();
2568 if ( $str == $this->mEmail ) {
2569 return;
2570 }
2571 $this->invalidateEmail();
2572 $this->mEmail = $str;
2573 $this->getHookRunner()->onUserSetEmail( $this, $this->mEmail );
2574 }
2575
2583 public function setEmailWithConfirmation( $str ) {
2585
2586 if ( !$wgEnableEmail ) {
2587 return Status::newFatal( 'emaildisabled' );
2588 }
2589
2590 $oldaddr = $this->getEmail();
2591 if ( $str === $oldaddr ) {
2592 return Status::newGood( true );
2593 }
2594
2595 $type = $oldaddr != '' ? 'changed' : 'set';
2596 $notificationResult = null;
2597
2598 if ( $wgEmailAuthentication && $type === 'changed' ) {
2599 // Send the user an email notifying the user of the change in registered
2600 // email address on their previous email address
2601 $change = $str != '' ? 'changed' : 'removed';
2602 $notificationResult = $this->sendMail(
2603 wfMessage( 'notificationemail_subject_' . $change )->text(),
2604 wfMessage( 'notificationemail_body_' . $change,
2605 $this->getRequest()->getIP(),
2606 $this->getName(),
2607 $str )->text()
2608 );
2609 }
2610
2611 $this->setEmail( $str );
2612
2613 if ( $str !== '' && $wgEmailAuthentication ) {
2614 // Send a confirmation request to the new address if needed
2615 $result = $this->sendConfirmationMail( $type );
2616
2617 if ( $notificationResult !== null ) {
2618 $result->merge( $notificationResult );
2619 }
2620
2621 if ( $result->isGood() ) {
2622 // Say to the caller that a confirmation and notification mail has been sent
2623 $result->value = 'eauth';
2624 }
2625 } else {
2626 $result = Status::newGood( true );
2627 }
2628
2629 return $result;
2630 }
2631
2636 public function getRealName() {
2637 if ( !$this->isItemLoaded( 'realname' ) ) {
2638 $this->load();
2639 }
2640
2641 return $this->mRealName;
2642 }
2643
2648 public function setRealName( $str ) {
2649 $this->load();
2650 $this->mRealName = $str;
2651 }
2652
2665 public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2666 if ( $oname === null ) {
2667 return null; // b/c
2668 }
2669 return MediaWikiServices::getInstance()
2670 ->getUserOptionsLookup()
2671 ->getOption( $this, $oname, $defaultOverride, $ignoreHidden );
2672 }
2673
2683 public function getOptions( $flags = 0 ) {
2684 return MediaWikiServices::getInstance()
2685 ->getUserOptionsLookup()
2686 ->getOptions( $this, $flags );
2687 }
2688
2697 public function getBoolOption( $oname ) {
2698 return MediaWikiServices::getInstance()
2699 ->getUserOptionsLookup()
2700 ->getBoolOption( $this, $oname );
2701 }
2702
2712 public function getIntOption( $oname, $defaultOverride = 0 ) {
2713 if ( $oname === null ) {
2714 return null; // b/c
2715 }
2716 return MediaWikiServices::getInstance()
2717 ->getUserOptionsLookup()
2718 ->getIntOption( $this, $oname, $defaultOverride );
2719 }
2720
2730 public function setOption( $oname, $val ) {
2731 MediaWikiServices::getInstance()
2732 ->getUserOptionsManager()
2733 ->setOption( $this, $oname, $val );
2734 }
2735
2746 public function getTokenFromOption( $oname ) {
2747 global $wgHiddenPrefs;
2748
2749 $id = $this->getId();
2750 if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2751 return false;
2752 }
2753
2754 $token = $this->getOption( $oname );
2755 if ( !$token ) {
2756 // Default to a value based on the user token to avoid space
2757 // wasted on storing tokens for all users. When this option
2758 // is set manually by the user, only then is it stored.
2759 $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2760 }
2761
2762 return $token;
2763 }
2764
2774 public function resetTokenFromOption( $oname ) {
2775 global $wgHiddenPrefs;
2776 if ( in_array( $oname, $wgHiddenPrefs ) ) {
2777 return false;
2778 }
2779
2780 $token = MWCryptRand::generateHex( 40 );
2781 $this->setOption( $oname, $token );
2782 return $token;
2783 }
2784
2809 public static function listOptionKinds() {
2810 return MediaWikiServices::getInstance()
2811 ->getUserOptionsManager()
2812 ->listOptionKinds();
2813 }
2814
2828 public function getOptionKinds( IContextSource $context, $options = null ) {
2829 return MediaWikiServices::getInstance()
2830 ->getUserOptionsManager()
2831 ->getOptionKinds( $this, $context, $options );
2832 }
2833
2849 public function resetOptions(
2850 $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
2851 IContextSource $context = null
2852 ) {
2853 MediaWikiServices::getInstance()
2854 ->getUserOptionsManager()
2855 ->resetOptions(
2856 $this,
2857 $context ?? RequestContext::getMain(),
2858 $resetKinds
2859 );
2860 }
2861
2866 public function getDatePreference() {
2867 // Important migration for old data rows
2868 if ( $this->mDatePreference === null ) {
2869 global $wgLang;
2870 $value = $this->getOption( 'date' );
2871 $map = $wgLang->getDatePreferenceMigrationMap();
2872 if ( isset( $map[$value] ) ) {
2873 $value = $map[$value];
2874 }
2875 $this->mDatePreference = $value;
2876 }
2878 }
2879
2886 public function requiresHTTPS() {
2888 if ( $wgForceHTTPS ) {
2889 return true;
2890 }
2891 if ( !$wgSecureLogin ) {
2892 return false;
2893 }
2894 $https = $this->getBoolOption( 'prefershttps' );
2895 $this->getHookRunner()->onUserRequiresHTTPS( $this, $https );
2896 if ( $https ) {
2897 $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
2898 }
2899
2900 return $https;
2901 }
2902
2908 public function getStubThreshold() {
2909 global $wgMaxArticleSize; # Maximum article size, in Kb
2910 $threshold = $this->getIntOption( 'stubthreshold' );
2911 if ( $threshold > $wgMaxArticleSize * 1024 ) {
2912 // If they have set an impossible value, disable the preference
2913 // so we can use the parser cache again.
2914 $threshold = 0;
2915 }
2916 return $threshold;
2917 }
2918
2927 public function getRights() {
2928 return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
2929 }
2930
2939 public function getGroups() {
2940 return MediaWikiServices::getInstance()
2941 ->getUserGroupManager()
2942 ->getUserGroups( $this, $this->queryFlagsUsed );
2943 }
2944
2954 public function getGroupMemberships() {
2955 return MediaWikiServices::getInstance()
2956 ->getUserGroupManager()
2957 ->getUserGroupMemberships( $this, $this->queryFlagsUsed );
2958 }
2959
2970 public function getEffectiveGroups( $recache = false ) {
2971 return MediaWikiServices::getInstance()
2972 ->getUserGroupManager()
2973 ->getUserEffectiveGroups( $this, $this->queryFlagsUsed, $recache );
2974 }
2975
2986 public function getAutomaticGroups( $recache = false ) {
2987 return MediaWikiServices::getInstance()
2988 ->getUserGroupManager()
2989 ->getUserImplicitGroups( $this, $this->queryFlagsUsed, $recache );
2990 }
2991
3003 public function getFormerGroups() {
3004 return MediaWikiServices::getInstance()
3005 ->getUserGroupManager()
3006 ->getUserFormerGroups( $this, $this->queryFlagsUsed );
3007 }
3008
3013 public function getEditCount() {
3014 if ( !$this->getId() ) {
3015 return null;
3016 }
3017
3018 if ( $this->mEditCount === null ) {
3019 $this->mEditCount = MediaWikiServices::getInstance()
3020 ->getUserEditTracker()
3021 ->getUserEditCount( $this );
3022 }
3023 return (int)$this->mEditCount;
3024 }
3025
3039 public function addGroup( $group, $expiry = null ) {
3040 return MediaWikiServices::getInstance()
3041 ->getUserGroupManager()
3042 ->addUserToGroup( $this, $group, $expiry, true );
3043 }
3044
3054 public function removeGroup( $group ) {
3055 return MediaWikiServices::getInstance()
3056 ->getUserGroupManager()
3057 ->removeUserFromGroup( $this, $group );
3058 }
3059
3069 public function isRegistered() {
3070 return $this->getId() != 0;
3071 }
3072
3079 public function isLoggedIn() {
3080 return $this->isRegistered();
3081 }
3082
3087 public function isAnon() {
3088 return !$this->isRegistered();
3089 }
3090
3095 public function isBot() {
3096 if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3097 return true;
3098 }
3099
3100 $isBot = false;
3101 $this->getHookRunner()->onUserIsBot( $this, $isBot );
3102
3103 return $isBot;
3104 }
3105
3115 public function isSystemUser() {
3116 $this->load();
3117 if ( $this->mEmail || $this->mToken !== self::INVALID_TOKEN ||
3118 MediaWikiServices::getInstance()->getAuthManager()->userCanAuthenticate( $this->mName )
3119 ) {
3120 return false;
3121 }
3122 return true;
3123 }
3124
3134 public function isAllowedAny( ...$permissions ) {
3135 return MediaWikiServices::getInstance()
3136 ->getPermissionManager()
3137 ->userHasAnyRight( $this, ...$permissions );
3138 }
3139
3146 public function isAllowedAll( ...$permissions ) {
3147 return MediaWikiServices::getInstance()
3148 ->getPermissionManager()
3149 ->userHasAllRights( $this, ...$permissions );
3150 }
3151
3162 public function isAllowed( $action = '' ) {
3163 return MediaWikiServices::getInstance()->getPermissionManager()
3164 ->userHasRight( $this, $action );
3165 }
3166
3171 public function useRCPatrol() {
3172 global $wgUseRCPatrol;
3173 return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3174 }
3175
3180 public function useNPPatrol() {
3182 return (
3184 && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3185 );
3186 }
3187
3192 public function useFilePatrol() {
3194 return (
3196 && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3197 );
3198 }
3199
3205 public function getRequest() {
3206 if ( $this->mRequest ) {
3207 return $this->mRequest;
3208 }
3209
3210 global $wgRequest;
3211 return $wgRequest;
3212 }
3213
3222 public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3223 if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3224 return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3225 }
3226 return false;
3227 }
3228
3239 public function isTempWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ): bool {
3240 if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3241 return MediaWikiServices::getInstance()->getWatchedItemStore()
3242 ->isTempWatched( $this, $title );
3243 }
3244 return false;
3245 }
3246
3256 public function addWatch(
3257 $title,
3258 $checkRights = self::CHECK_USER_RIGHTS,
3259 ?string $expiry = null
3260 ) {
3261 if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3262 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3263 $store->addWatch( $this, $title->getSubjectPage(), $expiry );
3264 $store->addWatch( $this, $title->getTalkPage(), $expiry );
3265 }
3266 $this->invalidateCache();
3267 }
3268
3276 public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3277 if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3278 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3279 $store->removeWatch( $this, $title->getSubjectPage() );
3280 $store->removeWatch( $this, $title->getTalkPage() );
3281 }
3282 $this->invalidateCache();
3283 }
3284
3296 public function clearNotification( &$title, $oldid = 0 ) {
3297 MediaWikiServices::getInstance()
3298 ->getWatchlistNotificationManager()
3299 ->clearTitleUserNotifications( $this, $title, $oldid );
3300 }
3301
3311 public function clearAllNotifications() {
3312 wfDeprecated( __METHOD__, '1.35' );
3313 MediaWikiServices::getInstance()
3314 ->getWatchlistNotificationManager()
3315 ->clearAllUserNotifications( $this );
3316 }
3317
3323 public function getExperienceLevel() {
3324 global $wgLearnerEdits,
3328
3329 if ( $this->isAnon() ) {
3330 return false;
3331 }
3332
3333 $editCount = $this->getEditCount();
3334 $registration = $this->getRegistration();
3335 $now = time();
3336 $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3337 $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3338
3339 if ( $editCount < $wgLearnerEdits ||
3340 $registration > $learnerRegistration ) {
3341 return 'newcomer';
3342 }
3343
3344 if ( $editCount > $wgExperiencedUserEdits &&
3345 $registration <= $experiencedRegistration
3346 ) {
3347 return 'experienced';
3348 }
3349
3350 return 'learner';
3351 }
3352
3361 public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3362 $this->load();
3363 if ( $this->mId == 0 ) {
3364 return;
3365 }
3366
3367 $session = $this->getRequest()->getSession();
3368 if ( $request && $session->getRequest() !== $request ) {
3369 $session = $session->sessionWithRequest( $request );
3370 }
3371 $delay = $session->delaySave();
3372
3373 if ( !$session->getUser()->equals( $this ) ) {
3374 if ( !$session->canSetUser() ) {
3375 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3376 ->warning( __METHOD__ .
3377 ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3378 );
3379 return;
3380 }
3381 $session->setUser( $this );
3382 }
3383
3384 $session->setRememberUser( $rememberMe );
3385 if ( $secure !== null ) {
3386 $session->setForceHTTPS( $secure );
3387 }
3388
3389 $session->persist();
3390
3391 ScopedCallback::consume( $delay );
3392 }
3393
3397 public function logout() {
3398 // Avoid PHP 7.1 warning of passing $this by reference
3399 $user = $this;
3400 if ( $this->getHookRunner()->onUserLogout( $user ) ) {
3401 $this->doLogout();
3402 }
3403 }
3404
3409 public function doLogout() {
3410 $session = $this->getRequest()->getSession();
3411 if ( !$session->canSetUser() ) {
3412 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3413 ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3414 $error = 'immutable';
3415 } elseif ( !$session->getUser()->equals( $this ) ) {
3416 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3417 ->warning( __METHOD__ .
3418 ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3419 );
3420 // But we still may as well make this user object anon
3421 $this->clearInstanceCache( 'defaults' );
3422 $error = 'wronguser';
3423 } else {
3424 $this->clearInstanceCache( 'defaults' );
3425 $delay = $session->delaySave();
3426 $session->unpersist(); // Clear cookies (T127436)
3427 $session->setLoggedOutTimestamp( time() );
3428 $session->setUser( new User );
3429 $session->set( 'wsUserID', 0 ); // Other code expects this
3430 $session->resetAllTokens();
3431 ScopedCallback::consume( $delay );
3432 $error = false;
3433 }
3434 \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3435 'event' => 'logout',
3436 'successful' => $error === false,
3437 'status' => $error ?: 'success',
3438 ] );
3439 }
3440
3445 public function saveSettings() {
3446 if ( wfReadOnly() ) {
3447 // @TODO: caller should deal with this instead!
3448 // This should really just be an exception.
3449 MWExceptionHandler::logException( new DBExpectedError(
3450 null,
3451 "Could not update user with ID '{$this->mId}'; DB is read-only."
3452 ) );
3453 return;
3454 }
3455
3456 $this->load();
3457 if ( $this->mId == 0 ) {
3458 return; // anon
3459 }
3460
3461 // Get a new user_touched that is higher than the old one.
3462 // This will be used for a CAS check as a last-resort safety
3463 // check against race conditions and replica DB lag.
3464 $newTouched = $this->newTouchedTimestamp();
3465
3466 $dbw = wfGetDB( DB_MASTER );
3467 $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
3468 $dbw->update( 'user',
3469 [ /* SET */
3470 'user_name' => $this->mName,
3471 'user_real_name' => $this->mRealName,
3472 'user_email' => $this->mEmail,
3473 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3474 'user_touched' => $dbw->timestamp( $newTouched ),
3475 'user_token' => strval( $this->mToken ),
3476 'user_email_token' => $this->mEmailToken,
3477 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3478 ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
3479 'user_id' => $this->mId,
3480 ] ), $fname
3481 );
3482
3483 if ( !$dbw->affectedRows() ) {
3484 // Maybe the problem was a missed cache update; clear it to be safe
3485 $this->clearSharedCache( 'refresh' );
3486 // User was changed in the meantime or loaded with stale data
3487 $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
3488 LoggerFactory::getInstance( 'preferences' )->warning(
3489 "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
3490 [ 'user_id' => $this->mId, 'db_flag' => $from ]
3491 );
3492 throw new MWException( "CAS update failed on user_touched. " .
3493 "The version of the user to be saved is older than the current version."
3494 );
3495 }
3496
3497 $dbw->update(
3498 'actor',
3499 [ 'actor_name' => $this->mName ],
3500 [ 'actor_user' => $this->mId ],
3501 $fname
3502 );
3503 } );
3504
3505 $this->mTouched = $newTouched;
3506 MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3507
3508 $this->getHookRunner()->onUserSaveSettings( $this );
3509 $this->clearSharedCache( 'changed' );
3510 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3511 $hcu->purgeTitleUrls( $this->getUserPage(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
3512 }
3513
3520 public function idForName( $flags = 0 ) {
3521 $s = trim( $this->getName() );
3522 if ( $s === '' ) {
3523 return 0;
3524 }
3525
3526 $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
3527 ? wfGetDB( DB_MASTER )
3528 : wfGetDB( DB_REPLICA );
3529
3530 $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
3531 ? [ 'LOCK IN SHARE MODE' ]
3532 : [];
3533
3534 $id = $db->selectField( 'user',
3535 'user_id', [ 'user_name' => $s ], __METHOD__, $options );
3536
3537 return (int)$id;
3538 }
3539
3555 public static function createNew( $name, $params = [] ) {
3556 foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
3557 if ( isset( $params[$field] ) ) {
3558 wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
3559 unset( $params[$field] );
3560 }
3561 }
3562
3563 $user = new User;
3564 $user->load();
3565 $user->setToken(); // init token
3566 if ( isset( $params['options'] ) ) {
3567 MediaWikiServices::getInstance()
3568 ->getUserOptionsManager()
3569 ->loadUserOptions( $user, $user->queryFlagsUsed, $params['options'] );
3570 unset( $params['options'] );
3571 }
3572 $dbw = wfGetDB( DB_MASTER );
3573
3574 $noPass = PasswordFactory::newInvalidPassword()->toString();
3575
3576 $fields = [
3577 'user_name' => $name,
3578 'user_password' => $noPass,
3579 'user_newpassword' => $noPass,
3580 'user_email' => $user->mEmail,
3581 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3582 'user_real_name' => $user->mRealName,
3583 'user_token' => strval( $user->mToken ),
3584 'user_registration' => $dbw->timestamp( $user->mRegistration ),
3585 'user_editcount' => 0,
3586 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3587 ];
3588 foreach ( $params as $name => $value ) {
3589 $fields["user_$name"] = $value;
3590 }
3591
3592 return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
3593 $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
3594 if ( $dbw->affectedRows() ) {
3595 $newUser = self::newFromId( $dbw->insertId() );
3596 $newUser->mName = $fields['user_name'];
3597 $newUser->updateActorId( $dbw );
3598 // Load the user from master to avoid replica lag
3599 $newUser->load( self::READ_LATEST );
3600 } else {
3601 $newUser = null;
3602 }
3603 return $newUser;
3604 } );
3605 }
3606
3633 public function addToDatabase() {
3634 $this->load();
3635 if ( !$this->mToken ) {
3636 $this->setToken(); // init token
3637 }
3638
3639 if ( !is_string( $this->mName ) ) {
3640 throw new RuntimeException( "User name field is not set." );
3641 }
3642
3643 $this->mTouched = $this->newTouchedTimestamp();
3644
3645 $dbw = wfGetDB( DB_MASTER );
3646 $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
3647 $noPass = PasswordFactory::newInvalidPassword()->toString();
3648 $dbw->insert( 'user',
3649 [
3650 'user_name' => $this->mName,
3651 'user_password' => $noPass,
3652 'user_newpassword' => $noPass,
3653 'user_email' => $this->mEmail,
3654 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3655 'user_real_name' => $this->mRealName,
3656 'user_token' => strval( $this->mToken ),
3657 'user_registration' => $dbw->timestamp( $this->mRegistration ),
3658 'user_editcount' => 0,
3659 'user_touched' => $dbw->timestamp( $this->mTouched ),
3660 ], $fname,
3661 [ 'IGNORE' ]
3662 );
3663 if ( !$dbw->affectedRows() ) {
3664 // Use locking reads to bypass any REPEATABLE-READ snapshot.
3665 $this->mId = $dbw->selectField(
3666 'user',
3667 'user_id',
3668 [ 'user_name' => $this->mName ],
3669 $fname,
3670 [ 'LOCK IN SHARE MODE' ]
3671 );
3672 $loaded = false;
3673 if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
3674 $loaded = true;
3675 }
3676 if ( !$loaded ) {
3677 throw new MWException( $fname . ": hit a key conflict attempting " .
3678 "to insert user '{$this->mName}' row, but it was not present in select!" );
3679 }
3680 return Status::newFatal( 'userexists' );
3681 }
3682 $this->mId = $dbw->insertId();
3683 self::$idCacheByName[$this->mName] = $this->mId;
3684 $this->updateActorId( $dbw );
3685
3686 return Status::newGood();
3687 } );
3688 if ( !$status->isGood() ) {
3689 return $status;
3690 }
3691
3692 // Clear instance cache other than user table data and actor, which is already accurate
3693 $this->clearInstanceCache();
3694
3695 MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
3696 return Status::newGood();
3697 }
3698
3703 private function updateActorId( IDatabase $dbw ) {
3704 $dbw->insert(
3705 'actor',
3706 [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
3707 __METHOD__
3708 );
3709 $this->mActorId = (int)$dbw->insertId();
3710 }
3711
3717 public function spreadAnyEditBlock() {
3718 if ( $this->isLoggedIn() && $this->getBlock() ) {
3719 return $this->spreadBlock();
3720 }
3721
3722 return false;
3723 }
3724
3730 protected function spreadBlock() {
3731 wfDebug( __METHOD__ . "()" );
3732 $this->load();
3733 if ( $this->mId == 0 ) {
3734 return false;
3735 }
3736
3737 $userblock = DatabaseBlock::newFromTarget( $this->getName() );
3738 if ( !$userblock ) {
3739 return false;
3740 }
3741
3742 return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
3743 }
3744
3749 public function isBlockedFromCreateAccount() {
3750 $this->getBlockedStatus();
3751 if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
3752 return $this->mBlock;
3753 }
3754
3755 # T15611: if the IP address the user is trying to create an account from is
3756 # blocked with createaccount disabled, prevent new account creation there even
3757 # when the user is logged in
3758 if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
3759 $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
3760 null, $this->getRequest()->getIP()
3761 );
3762 }
3763 return $this->mBlockedFromCreateAccount instanceof AbstractBlock
3764 && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
3765 ? $this->mBlockedFromCreateAccount
3766 : false;
3767 }
3768
3773 public function isBlockedFromEmailuser() {
3774 $this->getBlockedStatus();
3775 return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
3776 }
3777
3784 public function isBlockedFromUpload() {
3785 $this->getBlockedStatus();
3786 return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
3787 }
3788
3793 public function isAllowedToCreateAccount() {
3794 return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
3795 }
3796
3802 public function getUserPage() {
3803 return Title::makeTitle( NS_USER, $this->getName() );
3804 }
3805
3811 public function getTalkPage() {
3812 $title = $this->getUserPage();
3813 return $title->getTalkPage();
3814 }
3815
3821 public function isNewbie() {
3822 return !$this->isAllowed( 'autoconfirmed' );
3823 }
3824
3836 public function getEditTokenObject( $salt = '', $request = null ) {
3837 if ( $this->isAnon() ) {
3838 return new LoggedOutEditToken();
3839 }
3840
3841 if ( !$request ) {
3842 $request = $this->getRequest();
3843 }
3844 return $request->getSession()->getToken( $salt );
3845 }
3846
3860 public function getEditToken( $salt = '', $request = null ) {
3861 return $this->getEditTokenObject( $salt, $request )->toString();
3862 }
3863
3876 public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
3877 return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
3878 }
3879
3890 public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
3891 $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
3892 return $this->matchEditToken( $val, $salt, $request, $maxage );
3893 }
3894
3902 public function sendConfirmationMail( $type = 'created' ) {
3903 global $wgLang;
3904 $expiration = null; // gets passed-by-ref and defined in next line.
3905 $token = $this->confirmationToken( $expiration );
3906 $url = $this->confirmationTokenUrl( $token );
3907 $invalidateURL = $this->invalidationTokenUrl( $token );
3908 $this->saveSettings();
3909
3910 if ( $type == 'created' || $type === false ) {
3911 $message = 'confirmemail_body';
3912 $type = 'created';
3913 } elseif ( $type === true ) {
3914 $message = 'confirmemail_body_changed';
3915 $type = 'changed';
3916 } else {
3917 // Messages: confirmemail_body_changed, confirmemail_body_set
3918 $message = 'confirmemail_body_' . $type;
3919 }
3920
3921 $mail = [
3922 'subject' => wfMessage( 'confirmemail_subject' )->text(),
3923 'body' => wfMessage( $message,
3924 $this->getRequest()->getIP(),
3925 $this->getName(),
3926 $url,
3927 $wgLang->userTimeAndDate( $expiration, $this ),
3928 $invalidateURL,
3929 $wgLang->userDate( $expiration, $this ),
3930 $wgLang->userTime( $expiration, $this ) )->text(),
3931 'from' => null,
3932 'replyTo' => null,
3933 ];
3934 $info = [
3935 'type' => $type,
3936 'ip' => $this->getRequest()->getIP(),
3937 'confirmURL' => $url,
3938 'invalidateURL' => $invalidateURL,
3939 'expiration' => $expiration
3940 ];
3941
3942 $this->getHookRunner()->onUserSendConfirmationMail( $this, $mail, $info );
3943 return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
3944 }
3945
3957 public function sendMail( $subject, $body, $from = null, $replyto = null ) {
3958 global $wgPasswordSender;
3959
3960 if ( $from instanceof User ) {
3961 $sender = MailAddress::newFromUser( $from );
3962 } else {
3963 $sender = new MailAddress( $wgPasswordSender,
3964 wfMessage( 'emailsender' )->inContentLanguage()->text() );
3965 }
3966 $to = MailAddress::newFromUser( $this );
3967
3968 return UserMailer::send( $to, $sender, $subject, $body, [
3969 'replyTo' => $replyto,
3970 ] );
3971 }
3972
3983 protected function confirmationToken( &$expiration ) {
3985 $now = time();
3986 $expires = $now + $wgUserEmailConfirmationTokenExpiry;
3987 $expiration = wfTimestamp( TS_MW, $expires );
3988 $this->load();
3989 $token = MWCryptRand::generateHex( 32 );
3990 $hash = md5( $token );
3991 $this->mEmailToken = $hash;
3992 $this->mEmailTokenExpires = $expiration;
3993 return $token;
3994 }
3995
4001 protected function confirmationTokenUrl( $token ) {
4002 return $this->getTokenUrl( 'ConfirmEmail', $token );
4003 }
4004
4010 protected function invalidationTokenUrl( $token ) {
4011 return $this->getTokenUrl( 'InvalidateEmail', $token );
4012 }
4013
4028 protected function getTokenUrl( $page, $token ) {
4029 // Hack to bypass localization of 'Special:'
4030 $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4031 return $title->getCanonicalURL();
4032 }
4033
4041 public function confirmEmail() {
4042 // Check if it's already confirmed, so we don't touch the database
4043 // and fire the ConfirmEmailComplete hook on redundant confirmations.
4044 if ( !$this->isEmailConfirmed() ) {
4045 $this->setEmailAuthenticationTimestamp( wfTimestampNow() );
4046 $this->getHookRunner()->onConfirmEmailComplete( $this );
4047 }
4048 return true;
4049 }
4050
4058 public function invalidateEmail() {
4059 $this->load();
4060 $this->mEmailToken = null;
4061 $this->mEmailTokenExpires = null;
4062 $this->setEmailAuthenticationTimestamp( null );
4063 $this->mEmail = '';
4064 $this->getHookRunner()->onInvalidateEmailComplete( $this );
4065 return true;
4066 }
4067
4072 public function setEmailAuthenticationTimestamp( $timestamp ) {
4073 $this->load();
4074 $this->mEmailAuthenticated = $timestamp;
4075 $this->getHookRunner()->onUserSetEmailAuthenticationTimestamp(
4076 $this, $this->mEmailAuthenticated );
4077 }
4078
4084 public function canSendEmail() {
4086 if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4087 return false;
4088 }
4089 $canSend = $this->isEmailConfirmed();
4090 $this->getHookRunner()->onUserCanSendEmail( $this, $canSend );
4091 return $canSend;
4092 }
4093
4099 public function canReceiveEmail() {
4100 return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4101 }
4102
4113 public function isEmailConfirmed() {
4115 $this->load();
4116 // Avoid PHP 7.1 warning of passing $this by reference
4117 $user = $this;
4118 $confirmed = true;
4119 if ( $this->getHookRunner()->onEmailConfirmed( $user, $confirmed ) ) {
4120 if ( $this->isAnon() ) {
4121 return false;
4122 }
4123 if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4124 return false;
4125 }
4126 if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
4127 return false;
4128 }
4129 return true;
4130 }
4131
4132 return $confirmed;
4133 }
4134
4139 public function isEmailConfirmationPending() {
4141 return $wgEmailAuthentication &&
4142 !$this->isEmailConfirmed() &&
4143 $this->mEmailToken &&
4144 $this->mEmailTokenExpires > wfTimestamp();
4145 }
4146
4154 public function getRegistration() {
4155 if ( $this->isAnon() ) {
4156 return false;
4157 }
4158 $this->load();
4159 return $this->mRegistration;
4160 }
4161
4168 public function getFirstEditTimestamp() {
4169 return MediaWikiServices::getInstance()
4170 ->getUserEditTracker()
4171 ->getFirstEditTimestamp( $this );
4172 }
4173
4181 public function getLatestEditTimestamp() {
4182 return MediaWikiServices::getInstance()
4183 ->getUserEditTracker()
4184 ->getLatestEditTimestamp( $this );
4185 }
4186
4196 public static function getGroupPermissions( $groups ) {
4197 return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
4198 }
4199
4209 public static function getGroupsWithPermission( $role ) {
4210 return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
4211 }
4212
4228 public static function groupHasPermission( $group, $role ) {
4229 return MediaWikiServices::getInstance()->getPermissionManager()
4230 ->groupHasPermission( $group, $role );
4231 }
4232
4250 public static function isEveryoneAllowed( $right ) {
4251 wfDeprecated( __METHOD__, '1.34' );
4252 return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
4253 }
4254
4262 public static function getAllGroups() {
4263 return MediaWikiServices::getInstance()
4264 ->getUserGroupManager()
4265 ->listAllGroups();
4266 }
4267
4275 public static function getAllRights() {
4276 wfDeprecated( __METHOD__, '1.34' );
4277 return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
4278 }
4279
4285 public static function getImplicitGroups() {
4286 return MediaWikiServices::getInstance()
4287 ->getUserGroupManager()
4288 ->listAllImplicitGroups();
4289 }
4290
4301 public static function changeableByGroup( $group ) {
4303
4304 $groups = [
4305 'add' => [],
4306 'remove' => [],
4307 'add-self' => [],
4308 'remove-self' => []
4309 ];
4310
4311 if ( empty( $wgAddGroups[$group] ) ) {
4312 // Don't add anything to $groups
4313 } elseif ( $wgAddGroups[$group] === true ) {
4314 // You get everything
4315 $groups['add'] = self::getAllGroups();
4316 } elseif ( is_array( $wgAddGroups[$group] ) ) {
4317 $groups['add'] = $wgAddGroups[$group];
4318 }
4319
4320 // Same thing for remove
4321 if ( empty( $wgRemoveGroups[$group] ) ) {
4322 // Do nothing
4323 } elseif ( $wgRemoveGroups[$group] === true ) {
4324 $groups['remove'] = self::getAllGroups();
4325 } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4326 $groups['remove'] = $wgRemoveGroups[$group];
4327 }
4328
4329 // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4330 if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4331 foreach ( $wgGroupsAddToSelf as $key => $value ) {
4332 if ( is_int( $key ) ) {
4333 $wgGroupsAddToSelf['user'][] = $value;
4334 }
4335 }
4336 }
4337
4338 if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4339 foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4340 if ( is_int( $key ) ) {
4341 $wgGroupsRemoveFromSelf['user'][] = $value;
4342 }
4343 }
4344 }
4345
4346 // Now figure out what groups the user can add to him/herself
4347 if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4348 // Do nothing
4349 } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4350 // No idea WHY this would be used, but it's there
4351 $groups['add-self'] = self::getAllGroups();
4352 } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4353 $groups['add-self'] = $wgGroupsAddToSelf[$group];
4354 }
4355
4356 if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4357 // Do nothing
4358 } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4359 $groups['remove-self'] = self::getAllGroups();
4360 } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4361 $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4362 }
4363
4364 return $groups;
4365 }
4366
4374 public function changeableGroups() {
4375 if ( $this->isAllowed( 'userrights' ) ) {
4376 // This group gives the right to modify everything (reverse-
4377 // compatibility with old "userrights lets you change
4378 // everything")
4379 // Using array_merge to make the groups reindexed
4380 $all = array_merge( self::getAllGroups() );
4381 return [
4382 'add' => $all,
4383 'remove' => $all,
4384 'add-self' => [],
4385 'remove-self' => []
4386 ];
4387 }
4388
4389 // Okay, it's not so simple, we will have to go through the arrays
4390 $groups = [
4391 'add' => [],
4392 'remove' => [],
4393 'add-self' => [],
4394 'remove-self' => []
4395 ];
4396 $addergroups = $this->getEffectiveGroups();
4397
4398 foreach ( $addergroups as $addergroup ) {
4399 $groups = array_merge_recursive(
4400 $groups, $this->changeableByGroup( $addergroup )
4401 );
4402 $groups['add'] = array_unique( $groups['add'] );
4403 $groups['remove'] = array_unique( $groups['remove'] );
4404 $groups['add-self'] = array_unique( $groups['add-self'] );
4405 $groups['remove-self'] = array_unique( $groups['remove-self'] );
4406 }
4407 return $groups;
4408 }
4409
4413 public function incEditCount() {
4414 if ( $this->isAnon() ) {
4415 return; // sanity
4416 }
4417
4418 DeferredUpdates::addUpdate(
4419 new UserEditCountUpdate( $this, 1 ),
4420 DeferredUpdates::POSTSEND
4421 );
4422 }
4423
4429 public function setEditCountInternal( $count ) {
4430 $this->mEditCount = $count;
4431 }
4432
4441 return MediaWikiServices::getInstance()
4442 ->getUserEditTracker()
4443 ->initializeUserEditCount( $this );
4444 }
4445
4453 public static function getRightDescription( $right ) {
4454 $key = "right-$right";
4455 $msg = wfMessage( $key );
4456 return $msg->isDisabled() ? $right : $msg->text();
4457 }
4458
4466 public static function getGrantName( $grant ) {
4467 $key = "grant-$grant";
4468 $msg = wfMessage( $key );
4469 return $msg->isDisabled() ? $grant : $msg->text();
4470 }
4471
4492 public function addNewUserLogEntry( $action = false, $reason = '' ) {
4493 return true; // disabled
4494 }
4495
4505 public static function getQueryInfo() {
4506 $ret = [
4507 'tables' => [ 'user', 'user_actor' => 'actor' ],
4508 'fields' => [
4509 'user_id',
4510 'user_name',
4511 'user_real_name',
4512 'user_email',
4513 'user_touched',
4514 'user_token',
4515 'user_email_authenticated',
4516 'user_email_token',
4517 'user_email_token_expires',
4518 'user_registration',
4519 'user_editcount',
4520 'user_actor.actor_id',
4521 ],
4522 'joins' => [
4523 'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
4524 ],
4525 ];
4526
4527 return $ret;
4528 }
4529
4537 public static function newFatalPermissionDeniedStatus( $permission ) {
4538 global $wgLang;
4539
4540 $groups = [];
4541 foreach ( MediaWikiServices::getInstance()
4543 ->getGroupsWithPermission( $permission ) as $group ) {
4544 $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
4545 }
4546
4547 if ( $groups ) {
4548 return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
4549 }
4550
4551 return Status::newFatal( 'badaccess-group0' );
4552 }
4553
4563 public function getInstanceForUpdate() {
4564 if ( !$this->getId() ) {
4565 return null; // anon
4566 }
4567
4568 $user = self::newFromId( $this->getId() );
4569 if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
4570 return null;
4571 }
4572
4573 return $user;
4574 }
4575
4583 public function equals( UserIdentity $user ) {
4584 // XXX it's not clear whether central ID providers are supposed to obey this
4585 return $this->getName() === $user->getName();
4586 }
4587
4593 public function isAllowUsertalk() {
4594 return $this->mAllowUsertalk;
4595 }
4596}
getPermissionManager()
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
$wgGroupsRemoveFromSelf
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
$wgRemoveGroups
$wgMaxArticleSize
Maximum article size in kilobytes.
$wgLearnerMemberSince
Specify the difference engine to use.
$wgProxyList
Big list of banned IP addresses.
$wgHiddenPrefs
An array of preferences to not show for the user.
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
$wgPasswordPolicy
Password policy for the wiki.
$wgExperiencedUserMemberSince
Specify the difference engine to use.
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
$wgLearnerEdits
The following variables define 3 user experience levels:
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
$wgSecureLogin
This is to let user authenticate using https when they come from http.
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
$wgExperiencedUserEdits
Specify the difference engine to use.
bool $wgForceHTTPS
If this is true, when an insecure HTTP request is received, always redirect to HTTPS.
$wgEmailAuthentication
Require email authentication before sending mail to an email address.
$wgPasswordSender
Sender email address for e-mail notifications.
$wgRateLimits
Simple rate limiter options to brake edit floods.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
foreach( $wgExtensionFunctions as $func) if(!defined('MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition Setup.php:832
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:643
$wgLang
Definition Setup.php:781
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition Setup.php:85
Value object representing a logged-out user's edit token.
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
MediaWiki exception.
Stores a single person's name and email address.
This serves as the entry point to the authentication system.
This is a value object for authentication requests.
appliesToRight( $right)
Determine whether the block prevents a given right.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
System blocks are temporary blocks that are created on enforcement (e.g.
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Page revision base class.
This serves as the entry point to the MediaWiki session handling system.
Value object representing a CSRF token.
Definition Token.php:32
UserNameUtils service.
Provides access to user options.
static newFromIDs( $ids)
Definition UserArray.php:42
static singleton()
Definition UserCache.php:34
Handles increment the edit count for a given set of users.
Check if a user's password complies with any password policies that apply to that user,...
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
loadFromSession()
Load user data from the session.
Definition User.php:1219
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition User.php:4429
string $mEmailToken
Definition User.php:142
string $mToken
Definition User.php:138
string $mTouched
TS_MW timestamp from the DB.
Definition User.php:134
logout()
Log this user out.
Definition User.php:3397
getOptions( $flags=0)
Get all user's options.
Definition User.php:2683
getRequest()
Get the WebRequest object to use with this object.
Definition User.php:3205
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2150
addToDatabase()
Add this existing user object to the database.
Definition User.php:3633
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition User.php:2886
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition User.php:3703
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition User.php:3323
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition User.php:4250
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition User.php:2849
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition User.php:877
invalidateCache()
Immediately touch the user data cache for this account.
Definition User.php:2375
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition User.php:2555
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition User.php:3773
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition User.php:4505
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition User.php:1050
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition User.php:4168
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:2828
AbstractBlock null $mBlock
Definition User.php:210
static purge( $dbDomain, $userId)
Definition User.php:436
int null $mActorId
Definition User.php:127
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition User.php:4301
const VERSION
Version number to tag cached versions of serialized User objects.
Definition User.php:77
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:3836
static getAllGroups()
Return the set of defined explicit groups.
Definition User.php:4262
bool $mAllowUsertalk
Definition User.php:213
string $mEmailTokenExpires
Definition User.php:144
isAllowUsertalk()
Checks if usertalk is allowed.
Definition User.php:4593
string $mFrom
Initialization data source if mLoadedItems!==true.
Definition User.php:168
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition User.php:3361
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:3860
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition User.php:1635
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition User.php:219
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition User.php:4583
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition User.php:4028
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition User.php:3039
getBlockedStatus( $fromReplica=true, $disableIpBlockExemptChecking=false)
Get blocking information.
Definition User.php:1570
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition User.php:3069
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2049
string $mQuickTouched
TS_MW timestamp from cache.
Definition User.php:136
const INVALID_TOKEN
An invalid string value for the user_token field.
Definition User.php:71
isSafeToLoad()
Test if it's safe to load this User object.
Definition User.php:304
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition User.php:4228
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition User.php:4113
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition User.php:3717
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition User.php:3192
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition User.php:406
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition User.php:3162
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition User.php:2441
setName( $str)
Set the user name.
Definition User.php:2178
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition User.php:2459
getId()
Get the user's ID.
Definition User.php:2121
getRealName()
Get the user's real name.
Definition User.php:2636
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition User.php:2697
getRegistration()
Get the timestamp of account creation.
Definition User.php:4154
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition User.php:1697
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2063
string $mRealName
Definition User.php:129
isAllowedAny(... $permissions)
Check if user is allowed to access a feature / make an action.
Definition User.php:3134
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition User.php:1198
getMutableCacheKeys(WANObjectCache $cache)
Definition User.php:458
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition User.php:2746
isNewbie()
Determine whether the user is a newbie.
Definition User.php:3821
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition User.php:3296
string int $mBlockedby
Definition User.php:181
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition User.php:929
loadFromDatabase( $flags=self::READ_LATEST)
Load user data from the database.
Definition User.php:1256
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition User.php:4010
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition User.php:3180
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition User.php:2712
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition User.php:1510
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition User.php:4099
string $mEmail
Definition User.php:132
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition User.php:1243
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition User.php:2665
touch()
Update the "touched" timestamp for the user.
Definition User.php:2392
isPingLimitable()
Is this user subject to rate limiting?
Definition User.php:1720
static string[] $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition User.php:103
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition User.php:1130
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition User.php:717
getToken( $forceCreation=true)
Get the user's current token.
Definition User.php:2486
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:565
array bool $mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition User.php:155
confirmEmail()
Mark the e-mail address confirmed.
Definition User.php:4041
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition User.php:4209
setId( $v)
Set the user and reload all fields according to a given ID.
Definition User.php:2141
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition User.php:4196
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition User.php:1540
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition User.php:1009
isSystemUser()
Get whether the user is a system user.
Definition User.php:3115
bool $mLocked
Definition User.php:193
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition User.php:2970
isHidden()
Check if user account is hidden.
Definition User.php:2109
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition User.php:670
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition User.php:2583
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition User.php:2330
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition User.php:1649
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition User.php:1416
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition User.php:995
static getDefaultOption( $opt)
Get a given default option value.
Definition User.php:1553
const IGNORE_USER_RIGHTS
Definition User.php:94
getDatePreference()
Get the user's preferred date format.
Definition User.php:2866
string $mHash
Definition User.php:183
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition User.php:1086
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition User.php:3520
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition User.php:2954
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition User.php:3890
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition User.php:3222
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition User.php:1308
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition User.php:616
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition User.php:4001
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition User.php:3793
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition User.php:695
static getAllRights()
Get a list of all available permissions.
Definition User.php:4275
getNewtalk()
Check if the user has new messages.
Definition User.php:2218
getGroups()
Get the list of explicit group memberships this user has.
Definition User.php:2939
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition User.php:4492
validateCache( $timestamp)
Validate the cache for this account.
Definition User.php:2407
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition User.php:3171
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition User.php:1453
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition User.php:867
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition User.php:4058
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition User.php:4072
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:758
setOption( $oname, $val)
Set the given option for a user.
Definition User.php:2730
static getRightDescription( $right)
Get the description of a given right.
Definition User.php:4453
isAllowedAll(... $permissions)
Definition User.php:3146
getEditCount()
Get the user's edit count.
Definition User.php:3013
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition User.php:2189
const CHECK_USER_RIGHTS
Definition User.php:89
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition User.php:1438
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition User.php:2349
isValidPassword( $password)
Is the input a valid password for this user?
Definition User.php:1060
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition User.php:3749
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition User.php:3730
getFormerGroups()
Returns the groups the user has belonged to.
Definition User.php:3003
setRealName( $str)
Set the user's real name.
Definition User.php:2648
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition User.php:4181
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition User.php:597
AbstractBlock $mGlobalBlock
Definition User.php:191
int $mId
Cache variables.
Definition User.php:123
getTitleKey()
Get the user's name escaped by underscores.
Definition User.php:2209
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:951
getTouched()
Get the user touched timestamp.
Definition User.php:2419
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition User.php:3957
isLocked()
Check if user account is locked.
Definition User.php:2094
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition User.php:1472
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition User.php:3276
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition User.php:4139
__toString()
Definition User.php:244
getUserPage()
Get this user's personal page title.
Definition User.php:3802
isIPRange()
Is the user an IP range?
Definition User.php:963
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition User.php:3983
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition User.php:4084
getBlockId()
If user is blocked, return the ID for the block.
Definition User.php:2036
isBot()
Definition User.php:3095
string $mRegistration
Definition User.php:146
__construct()
Lightweight constructor for an anonymous user.
Definition User.php:237
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition User.php:2308
getStubThreshold()
Get the user preferred stub threshold.
Definition User.php:2908
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition User.php:1747
string $mDatePreference
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition User.php:174
static isValidUserName( $name)
Is the input a valid username?
Definition User.php:979
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition User.php:3902
getTalkPage()
Get this user's talk page title.
Definition User.php:3811
isTempWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check if the article is temporarily watched.
Definition User.php:3239
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition User.php:3256
isLoggedIn()
Get whether the user is registered.
Definition User.php:3079
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition User.php:887
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition User.php:470
getCacheKey(WANObjectCache $cache)
Definition User.php:447
saveSettings()
Save this user's settings into the database.
Definition User.php:3445
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition User.php:2280
static getGrantName( $grant)
Get the name of a given grant.
Definition User.php:4466
getEmail()
Get the user's e-mail address.
Definition User.php:2545
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition User.php:4537
static int[] $idCacheByName
Definition User.php:222
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition User.php:2986
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition User.php:3784
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition User.php:321
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition User.php:2016
getRights()
Get the permissions this user has.
Definition User.php:2927
& __get( $name)
Definition User.php:248
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition User.php:3876
string $mEmailAuthenticated
Definition User.php:140
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition User.php:1162
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition User.php:84
const TOKEN_LENGTH
Number of characters required for the user_token field.
Definition User.php:66
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition User.php:2529
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition User.php:3555
getBlock( $fromReplica=true, $disableIpBlockExemptChecking=false)
Get the block affecting the user, or null if the user is not blocked.
Definition User.php:1991
doLogout()
Clear the user's session, and reset the instance cache.
Definition User.php:3409
setItemLoaded( $item)
Set that an item has been loaded.
Definition User.php:1208
AbstractBlock bool $mBlockedFromCreateAccount
Definition User.php:216
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition User.php:4413
blockedFor()
If user is blocked, return the specified reason for the block.
Definition User.php:2027
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition User.php:2809
int $mEditCount
Definition User.php:148
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition User.php:2774
bool $mHideName
Definition User.php:200
static getImplicitGroups()
Get a list of implicit groups.
Definition User.php:4285
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition User.php:4374
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition User.php:1979
string $mName
Definition User.php:125
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition User.php:2241
isAnon()
Get whether the user is anonymous.
Definition User.php:3087
setEmail( $str)
Set the user's e-mail address.
Definition User.php:2566
string $mBlockreason
TODO: This should be removed when User::BlockedFor and AbstractBlock::getReason are hard deprecated.
Definition User.php:189
__set( $name, $value)
Definition User.php:269
WebRequest $mRequest
Definition User.php:203
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition User.php:4563
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition User.php:2007
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition User.php:580
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition User.php:3311
initEditCountInternal(IDatabase $dbr)
Initialize user_editcount from data out of the revision table.
Definition User.php:4440
removeGroup( $group)
Remove the user from the given group.
Definition User.php:3054
Multi-datacenter aware caching interface.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Base class for the more common types of database errors.
Relational database abstraction object.
Definition Database.php:50
const NS_USER
Definition Defines.php:72
const NS_MAIN
Definition Defines.php:70
Interface for objects which can provide a MediaWiki context on request.
Interface for database access objects.
Interface for objects representing user identity.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
affectedRows()
Get the number of rows affected by the last write query.
delete( $table, $conds, $fname=__METHOD__)
Delete all rows in a table that match a condition.
update( $table, $set, $conds, $fname=__METHOD__, $options=[])
Update all rows in a table that match a given condition.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
insert( $table, $rows, $fname=__METHOD__, $options=[])
Insert the given row(s) into a table.
insertId()
Get the inserted value of an auto-increment row.
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:29
return true
Definition router.php:92