MediaWiki REL1_33
User.php
Go to the documentation of this file.
1<?php
31use Wikimedia\Assert\Assert;
32use Wikimedia\IPSet;
33use Wikimedia\ScopedCallback;
37
52 const TOKEN_LENGTH = 32;
53
57 const INVALID_TOKEN = '*** INVALID ***';
58
62 const VERSION = 13;
63
69
73 const CHECK_USER_RIGHTS = true;
74
78 const IGNORE_USER_RIGHTS = false;
79
86 protected static $mCacheVars = [
87 // user table
88 'mId',
89 'mName',
90 'mRealName',
91 'mEmail',
92 'mTouched',
93 'mToken',
94 'mEmailAuthenticated',
95 'mEmailToken',
96 'mEmailTokenExpires',
97 'mRegistration',
98 'mEditCount',
99 // user_groups table
100 'mGroupMemberships',
101 // user_properties table
102 'mOptionOverrides',
103 // actor table
104 'mActorId',
105 ];
106
113 protected static $mCoreRights = [
114 'apihighlimits',
115 'applychangetags',
116 'autoconfirmed',
117 'autocreateaccount',
118 'autopatrol',
119 'bigdelete',
120 'block',
121 'blockemail',
122 'bot',
123 'browsearchive',
124 'changetags',
125 'createaccount',
126 'createpage',
127 'createtalk',
128 'delete',
129 'deletechangetags',
130 'deletedhistory',
131 'deletedtext',
132 'deletelogentry',
133 'deleterevision',
134 'edit',
135 'editcontentmodel',
136 'editinterface',
137 'editprotected',
138 'editmyoptions',
139 'editmyprivateinfo',
140 'editmyusercss',
141 'editmyuserjson',
142 'editmyuserjs',
143 'editmywatchlist',
144 'editsemiprotected',
145 'editsitecss',
146 'editsitejson',
147 'editsitejs',
148 'editusercss',
149 'edituserjson',
150 'edituserjs',
151 'hideuser',
152 'import',
153 'importupload',
154 'ipblock-exempt',
155 'managechangetags',
156 'markbotedits',
157 'mergehistory',
158 'minoredit',
159 'move',
160 'movefile',
161 'move-categorypages',
162 'move-rootuserpages',
163 'move-subpages',
164 'nominornewtalk',
165 'noratelimit',
166 'override-export-depth',
167 'pagelang',
168 'patrol',
169 'patrolmarks',
170 'protect',
171 'purge',
172 'read',
173 'reupload',
174 'reupload-own',
175 'reupload-shared',
176 'rollback',
177 'sendemail',
178 'siteadmin',
179 'suppressionlog',
180 'suppressredirect',
181 'suppressrevision',
182 'unblockself',
183 'undelete',
184 'unwatchedpages',
185 'upload',
186 'upload_by_url',
187 'userrights',
188 'userrights-interwiki',
189 'viewmyprivateinfo',
190 'viewmywatchlist',
191 'viewsuppressed',
192 'writeapi',
193 ];
194
198 protected static $mAllRights = false;
199
201 // @{
203 public $mId;
205 public $mName;
207 protected $mActorId;
210
212 public $mEmail;
214 public $mTouched;
216 protected $mQuickTouched;
218 protected $mToken;
222 protected $mEmailToken;
226 protected $mRegistration;
228 protected $mEditCount;
233 // @}
234
238 // @{
240
244 protected $mLoadedItems = [];
245 // @}
246
257 public $mFrom;
258
262 protected $mNewtalk;
268 protected $mHash;
270 public $mRights;
272 protected $mBlockreason;
278 protected $mFormerGroups;
280 protected $mGlobalBlock;
282 protected $mLocked;
286 public $mOptions;
287
289 private $mRequest;
290
292 public $mBlock;
293
296
299
301 protected $queryFlagsUsed = self::READ_NORMAL;
302
303 public static $idCacheByName = [];
304
316 public function __construct() {
317 $this->clearInstanceCache( 'defaults' );
318 }
319
323 public function __toString() {
324 return (string)$this->getName();
325 }
326
341 public function isSafeToLoad() {
342 global $wgFullyInitialised;
343
344 // The user is safe to load if:
345 // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
346 // * mLoadedItems === true (already loaded)
347 // * mFrom !== 'session' (sessions not involved at all)
348
349 return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
350 $this->mLoadedItems === true || $this->mFrom !== 'session';
351 }
352
358 public function load( $flags = self::READ_NORMAL ) {
359 global $wgFullyInitialised;
360
361 if ( $this->mLoadedItems === true ) {
362 return;
363 }
364
365 // Set it now to avoid infinite recursion in accessors
366 $oldLoadedItems = $this->mLoadedItems;
367 $this->mLoadedItems = true;
368 $this->queryFlagsUsed = $flags;
369
370 // If this is called too early, things are likely to break.
371 if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
372 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
373 ->warning( 'User::loadFromSession called before the end of Setup.php', [
374 'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
375 ] );
376 $this->loadDefaults();
377 $this->mLoadedItems = $oldLoadedItems;
378 return;
379 }
380
381 switch ( $this->mFrom ) {
382 case 'defaults':
383 $this->loadDefaults();
384 break;
385 case 'name':
386 // Make sure this thread sees its own changes
387 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
388 if ( $lb->hasOrMadeRecentMasterChanges() ) {
389 $flags |= self::READ_LATEST;
390 $this->queryFlagsUsed = $flags;
391 }
392
393 $this->mId = self::idFromName( $this->mName, $flags );
394 if ( !$this->mId ) {
395 // Nonexistent user placeholder object
396 $this->loadDefaults( $this->mName );
397 } else {
398 $this->loadFromId( $flags );
399 }
400 break;
401 case 'id':
402 // Make sure this thread sees its own changes, if the ID isn't 0
403 if ( $this->mId != 0 ) {
404 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
405 if ( $lb->hasOrMadeRecentMasterChanges() ) {
406 $flags |= self::READ_LATEST;
407 $this->queryFlagsUsed = $flags;
408 }
409 }
410
411 $this->loadFromId( $flags );
412 break;
413 case 'actor':
414 // Make sure this thread sees its own changes
415 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
416 if ( $lb->hasOrMadeRecentMasterChanges() ) {
417 $flags |= self::READ_LATEST;
418 $this->queryFlagsUsed = $flags;
419 }
420
421 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
422 $row = wfGetDB( $index )->selectRow(
423 'actor',
424 [ 'actor_user', 'actor_name' ],
425 [ 'actor_id' => $this->mActorId ],
426 __METHOD__,
428 );
429
430 if ( !$row ) {
431 // Ugh.
432 $this->loadDefaults();
433 } elseif ( $row->actor_user ) {
434 $this->mId = $row->actor_user;
435 $this->loadFromId( $flags );
436 } else {
437 $this->loadDefaults( $row->actor_name );
438 }
439 break;
440 case 'session':
441 if ( !$this->loadFromSession() ) {
442 // Loading from session failed. Load defaults.
443 $this->loadDefaults();
444 }
445 Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
446 break;
447 default:
448 throw new UnexpectedValueException(
449 "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
450 }
451 }
452
458 public function loadFromId( $flags = self::READ_NORMAL ) {
459 if ( $this->mId == 0 ) {
460 // Anonymous users are not in the database (don't need cache)
461 $this->loadDefaults();
462 return false;
463 }
464
465 // Try cache (unless this needs data from the master DB).
466 // NOTE: if this thread called saveSettings(), the cache was cleared.
467 $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
468 if ( $latest ) {
469 if ( !$this->loadFromDatabase( $flags ) ) {
470 // Can't load from ID
471 return false;
472 }
473 } else {
474 $this->loadFromCache();
475 }
476
477 $this->mLoadedItems = true;
478 $this->queryFlagsUsed = $flags;
479
480 return true;
481 }
482
488 public static function purge( $wikiId, $userId ) {
489 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
490 $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
491 $cache->delete( $key );
492 }
493
499 protected function getCacheKey( WANObjectCache $cache ) {
500 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
501
502 return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
503 }
504
511 $id = $this->getId();
512
513 return $id ? [ $this->getCacheKey( $cache ) ] : [];
514 }
515
522 protected function loadFromCache() {
523 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
524 $data = $cache->getWithSetCallback(
525 $this->getCacheKey( $cache ),
526 $cache::TTL_HOUR,
527 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
528 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
529 wfDebug( "User: cache miss for user {$this->mId}\n" );
530
531 $this->loadFromDatabase( self::READ_NORMAL );
532 $this->loadGroups();
533 $this->loadOptions();
534
535 $data = [];
536 foreach ( self::$mCacheVars as $name ) {
537 $data[$name] = $this->$name;
538 }
539
540 $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
541
542 // if a user group membership is about to expire, the cache needs to
543 // expire at that time (T163691)
544 foreach ( $this->mGroupMemberships as $ugm ) {
545 if ( $ugm->getExpiry() ) {
546 $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
547 if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
548 $ttl = $secondsUntilExpiry;
549 }
550 }
551 }
552
553 return $data;
554 },
555 [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
556 );
557
558 // Restore from cache
559 foreach ( self::$mCacheVars as $name ) {
560 $this->$name = $data[$name];
561 }
562
563 return true;
564 }
565
567 // @{
568
585 public static function newFromName( $name, $validate = 'valid' ) {
586 if ( $validate === true ) {
587 $validate = 'valid';
588 }
589 $name = self::getCanonicalName( $name, $validate );
590 if ( $name === false ) {
591 return false;
592 }
593
594 // Create unloaded user object
595 $u = new User;
596 $u->mName = $name;
597 $u->mFrom = 'name';
598 $u->setItemLoaded( 'name' );
599
600 return $u;
601 }
602
609 public static function newFromId( $id ) {
610 $u = new User;
611 $u->mId = $id;
612 $u->mFrom = 'id';
613 $u->setItemLoaded( 'id' );
614 return $u;
615 }
616
624 public static function newFromActorId( $id ) {
626
627 // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
628 // but it does little harm and might be needed for write callers loading a User.
630 throw new BadMethodCallException(
631 'Cannot use ' . __METHOD__
632 . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
633 );
634 }
635
636 $u = new User;
637 $u->mActorId = $id;
638 $u->mFrom = 'actor';
639 $u->setItemLoaded( 'actor' );
640 return $u;
641 }
642
652 public static function newFromIdentity( UserIdentity $identity ) {
653 if ( $identity instanceof User ) {
654 return $identity;
655 }
656
657 return self::newFromAnyId(
658 $identity->getId() === 0 ? null : $identity->getId(),
659 $identity->getName() === '' ? null : $identity->getName(),
660 $identity->getActorId() === 0 ? null : $identity->getActorId()
661 );
662 }
663
676 public static function newFromAnyId( $userId, $userName, $actorId ) {
678
679 $user = new User;
680 $user->mFrom = 'defaults';
681
682 // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
683 // but it does little harm and might be needed for write callers loading a User.
684 if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
685 $user->mActorId = (int)$actorId;
686 if ( $user->mActorId !== 0 ) {
687 $user->mFrom = 'actor';
688 }
689 $user->setItemLoaded( 'actor' );
690 }
691
692 if ( $userName !== null && $userName !== '' ) {
693 $user->mName = $userName;
694 $user->mFrom = 'name';
695 $user->setItemLoaded( 'name' );
696 }
697
698 if ( $userId !== null ) {
699 $user->mId = (int)$userId;
700 if ( $user->mId !== 0 ) {
701 $user->mFrom = 'id';
702 }
703 $user->setItemLoaded( 'id' );
704 }
705
706 if ( $user->mFrom === 'defaults' ) {
707 throw new InvalidArgumentException(
708 'Cannot create a user with no name, no ID, and no actor ID'
709 );
710 }
711
712 return $user;
713 }
714
726 public static function newFromConfirmationCode( $code, $flags = 0 ) {
727 $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
728 ? wfGetDB( DB_MASTER )
729 : wfGetDB( DB_REPLICA );
730
731 $id = $db->selectField(
732 'user',
733 'user_id',
734 [
735 'user_email_token' => md5( $code ),
736 'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
737 ]
738 );
739
740 return $id ? self::newFromId( $id ) : null;
741 }
742
750 public static function newFromSession( WebRequest $request = null ) {
751 $user = new User;
752 $user->mFrom = 'session';
753 $user->mRequest = $request;
754 return $user;
755 }
756
772 public static function newFromRow( $row, $data = null ) {
773 $user = new User;
774 $user->loadFromRow( $row, $data );
775 return $user;
776 }
777
813 public static function newSystemUser( $name, $options = [] ) {
814 $options += [
815 'validate' => 'valid',
816 'create' => true,
817 'steal' => false,
818 ];
819
820 $name = self::getCanonicalName( $name, $options['validate'] );
821 if ( $name === false ) {
822 return null;
823 }
824
826 $userQuery = self::getQueryInfo();
827 $row = $dbr->selectRow(
828 $userQuery['tables'],
829 $userQuery['fields'],
830 [ 'user_name' => $name ],
831 __METHOD__,
832 [],
833 $userQuery['joins']
834 );
835 if ( !$row ) {
836 // Try the master database...
837 $dbw = wfGetDB( DB_MASTER );
838 $row = $dbw->selectRow(
839 $userQuery['tables'],
840 $userQuery['fields'],
841 [ 'user_name' => $name ],
842 __METHOD__,
843 [],
844 $userQuery['joins']
845 );
846 }
847
848 if ( !$row ) {
849 // No user. Create it?
850 return $options['create']
851 ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
852 : null;
853 }
854
855 $user = self::newFromRow( $row );
856
857 // A user is considered to exist as a non-system user if it can
858 // authenticate, or has an email set, or has a non-invalid token.
859 if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
860 AuthManager::singleton()->userCanAuthenticate( $name )
861 ) {
862 // User exists. Steal it?
863 if ( !$options['steal'] ) {
864 return null;
865 }
866
867 AuthManager::singleton()->revokeAccessForUser( $name );
868
869 $user->invalidateEmail();
870 $user->mToken = self::INVALID_TOKEN;
871 $user->saveSettings();
872 SessionManager::singleton()->preventSessionsForUser( $user->getName() );
873 }
874
875 return $user;
876 }
877
878 // @}
879
885 public static function whoIs( $id ) {
886 return UserCache::singleton()->getProp( $id, 'name' );
887 }
888
895 public static function whoIsReal( $id ) {
896 return UserCache::singleton()->getProp( $id, 'real_name' );
897 }
898
905 public static function idFromName( $name, $flags = self::READ_NORMAL ) {
906 // Don't explode on self::$idCacheByName[$name] if $name is not a string but e.g. a User object
907 $name = (string)$name;
908 $nt = Title::makeTitleSafe( NS_USER, $name );
909 if ( is_null( $nt ) ) {
910 // Illegal name
911 return null;
912 }
913
914 if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
915 return self::$idCacheByName[$name];
916 }
917
918 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
919 $db = wfGetDB( $index );
920
921 $s = $db->selectRow(
922 'user',
923 [ 'user_id' ],
924 [ 'user_name' => $nt->getText() ],
925 __METHOD__,
927 );
928
929 if ( $s === false ) {
930 $result = null;
931 } else {
932 $result = (int)$s->user_id;
933 }
934
935 self::$idCacheByName[$name] = $result;
936
937 if ( count( self::$idCacheByName ) > 1000 ) {
938 self::$idCacheByName = [];
939 }
940
941 return $result;
942 }
943
947 public static function resetIdByNameCache() {
948 self::$idCacheByName = [];
949 }
950
967 public static function isIP( $name ) {
968 return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
969 || IP::isIPv6( $name );
970 }
971
978 public function isIPRange() {
979 return IP::isValidRange( $this->mName );
980 }
981
993 public static function isValidUserName( $name ) {
994 global $wgMaxNameChars;
995
996 if ( $name == ''
997 || self::isIP( $name )
998 || strpos( $name, '/' ) !== false
999 || strlen( $name ) > $wgMaxNameChars
1000 || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
1001 ) {
1002 return false;
1003 }
1004
1005 // Ensure that the name can't be misresolved as a different title,
1006 // such as with extra namespace keys at the start.
1007 $parsed = Title::newFromText( $name );
1008 if ( is_null( $parsed )
1009 || $parsed->getNamespace()
1010 || strcmp( $name, $parsed->getPrefixedText() ) ) {
1011 return false;
1012 }
1013
1014 // Check an additional blacklist of troublemaker characters.
1015 // Should these be merged into the title char list?
1016 $unicodeBlacklist = '/[' .
1017 '\x{0080}-\x{009f}' . # iso-8859-1 control chars
1018 '\x{00a0}' . # non-breaking space
1019 '\x{2000}-\x{200f}' . # various whitespace
1020 '\x{2028}-\x{202f}' . # breaks and control chars
1021 '\x{3000}' . # ideographic space
1022 '\x{e000}-\x{f8ff}' . # private use
1023 ']/u';
1024 if ( preg_match( $unicodeBlacklist, $name ) ) {
1025 return false;
1026 }
1027
1028 return true;
1029 }
1030
1042 public static function isUsableName( $name ) {
1043 global $wgReservedUsernames;
1044 // Must be a valid username, obviously ;)
1045 if ( !self::isValidUserName( $name ) ) {
1046 return false;
1047 }
1048
1049 static $reservedUsernames = false;
1050 if ( !$reservedUsernames ) {
1051 $reservedUsernames = $wgReservedUsernames;
1052 Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
1053 }
1054
1055 // Certain names may be reserved for batch processes.
1056 foreach ( $reservedUsernames as $reserved ) {
1057 if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1058 $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1059 }
1060 if ( $reserved == $name ) {
1061 return false;
1062 }
1063 }
1064 return true;
1065 }
1066
1077 public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1078 if ( $groups === [] ) {
1080 }
1081
1082 $groups = array_unique( (array)$groups );
1083 $limit = min( 5000, $limit );
1084
1085 $conds = [ 'ug_group' => $groups ];
1086 if ( $after !== null ) {
1087 $conds[] = 'ug_user > ' . (int)$after;
1088 }
1089
1090 $dbr = wfGetDB( DB_REPLICA );
1091 $ids = $dbr->selectFieldValues(
1092 'user_groups',
1093 'ug_user',
1094 $conds,
1095 __METHOD__,
1096 [
1097 'DISTINCT' => true,
1098 'ORDER BY' => 'ug_user',
1099 'LIMIT' => $limit,
1100 ]
1101 ) ?: [];
1102 return UserArray::newFromIDs( $ids );
1103 }
1104
1117 public static function isCreatableName( $name ) {
1119
1120 // Ensure that the username isn't longer than 235 bytes, so that
1121 // (at least for the builtin skins) user javascript and css files
1122 // will work. (T25080)
1123 if ( strlen( $name ) > 235 ) {
1124 wfDebugLog( 'username', __METHOD__ .
1125 ": '$name' invalid due to length" );
1126 return false;
1127 }
1128
1129 // Preg yells if you try to give it an empty string
1130 if ( $wgInvalidUsernameCharacters !== '' &&
1131 preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name )
1132 ) {
1133 wfDebugLog( 'username', __METHOD__ .
1134 ": '$name' invalid due to wgInvalidUsernameCharacters" );
1135 return false;
1136 }
1137
1138 return self::isUsableName( $name );
1139 }
1140
1147 public function isValidPassword( $password ) {
1148 // simple boolean wrapper for checkPasswordValidity
1149 return $this->checkPasswordValidity( $password )->isGood();
1150 }
1151
1159 public function getPasswordValidity( $password ) {
1160 wfDeprecated( __METHOD__, '1.33' );
1161
1162 $result = $this->checkPasswordValidity( $password );
1163 if ( $result->isGood() ) {
1164 return true;
1165 }
1166
1167 $messages = [];
1168 foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1169 $messages[] = $error['message'];
1170 }
1171 foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1172 $messages[] = $warning['message'];
1173 }
1174 if ( count( $messages ) === 1 ) {
1175 return $messages[0];
1176 }
1177
1178 return $messages;
1179 }
1180
1202 public function checkPasswordValidity( $password ) {
1203 global $wgPasswordPolicy;
1204
1205 $upp = new UserPasswordPolicy(
1206 $wgPasswordPolicy['policies'],
1207 $wgPasswordPolicy['checks']
1208 );
1209
1210 $status = Status::newGood( [] );
1211 $result = false; // init $result to false for the internal checks
1212
1213 if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1214 $status->error( $result );
1215 return $status;
1216 }
1217
1218 if ( $result === false ) {
1219 $status->merge( $upp->checkUserPassword( $this, $password ), true );
1220 return $status;
1221 }
1222
1223 if ( $result === true ) {
1224 return $status;
1225 }
1226
1227 $status->error( $result );
1228 return $status; // the isValidPassword hook set a string $result and returned true
1229 }
1230
1244 public static function getCanonicalName( $name, $validate = 'valid' ) {
1245 // Force usernames to capital
1246 $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1247
1248 # Reject names containing '#'; these will be cleaned up
1249 # with title normalisation, but then it's too late to
1250 # check elsewhere
1251 if ( strpos( $name, '#' ) !== false ) {
1252 return false;
1253 }
1254
1255 // Clean up name according to title rules,
1256 // but only when validation is requested (T14654)
1257 $t = ( $validate !== false ) ?
1258 Title::newFromText( $name, NS_USER ) : Title::makeTitle( NS_USER, $name );
1259 // Check for invalid titles
1260 if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1261 return false;
1262 }
1263
1264 $name = $t->getText();
1265
1266 switch ( $validate ) {
1267 case false:
1268 break;
1269 case 'valid':
1270 if ( !self::isValidUserName( $name ) ) {
1271 $name = false;
1272 }
1273 break;
1274 case 'usable':
1275 if ( !self::isUsableName( $name ) ) {
1276 $name = false;
1277 }
1278 break;
1279 case 'creatable':
1280 if ( !self::isCreatableName( $name ) ) {
1281 $name = false;
1282 }
1283 break;
1284 default:
1285 throw new InvalidArgumentException(
1286 'Invalid parameter value for $validate in ' . __METHOD__ );
1287 }
1288 return $name;
1289 }
1290
1297 public static function randomPassword() {
1299 return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1300 }
1301
1310 public function loadDefaults( $name = false ) {
1311 $this->mId = 0;
1312 $this->mName = $name;
1313 $this->mActorId = null;
1314 $this->mRealName = '';
1315 $this->mEmail = '';
1316 $this->mOptionOverrides = null;
1317 $this->mOptionsLoaded = false;
1318
1319 $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1320 ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1321 if ( $loggedOut !== 0 ) {
1322 $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1323 } else {
1324 $this->mTouched = '1'; # Allow any pages to be cached
1325 }
1326
1327 $this->mToken = null; // Don't run cryptographic functions till we need a token
1328 $this->mEmailAuthenticated = null;
1329 $this->mEmailToken = '';
1330 $this->mEmailTokenExpires = null;
1331 $this->mRegistration = wfTimestamp( TS_MW );
1332 $this->mGroupMemberships = [];
1333
1334 Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1335 }
1336
1349 public function isItemLoaded( $item, $all = 'all' ) {
1350 return ( $this->mLoadedItems === true && $all === 'all' ) ||
1351 ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1352 }
1353
1359 protected function setItemLoaded( $item ) {
1360 if ( is_array( $this->mLoadedItems ) ) {
1361 $this->mLoadedItems[$item] = true;
1362 }
1363 }
1364
1370 private function loadFromSession() {
1371 // Deprecated hook
1372 $result = null;
1373 Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1374 if ( $result !== null ) {
1375 return $result;
1376 }
1377
1378 // MediaWiki\Session\Session already did the necessary authentication of the user
1379 // returned here, so just use it if applicable.
1380 $session = $this->getRequest()->getSession();
1381 $user = $session->getUser();
1382 if ( $user->isLoggedIn() ) {
1383 $this->loadFromUserObject( $user );
1384 if ( $user->isBlocked() ) {
1385 // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1386 // every session load, because an autoblocked editor might not edit again from the same
1387 // IP address after being blocked.
1388 $this->trackBlockWithCookie();
1389 }
1390
1391 // Other code expects these to be set in the session, so set them.
1392 $session->set( 'wsUserID', $this->getId() );
1393 $session->set( 'wsUserName', $this->getName() );
1394 $session->set( 'wsToken', $this->getToken() );
1395
1396 return true;
1397 }
1398
1399 return false;
1400 }
1401
1405 public function trackBlockWithCookie() {
1406 $block = $this->getBlock();
1407
1408 if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null
1409 && $block->shouldTrackWithCookie( $this->isAnon() )
1410 ) {
1411 $block->setCookie( $this->getRequest()->response() );
1412 }
1413 }
1414
1422 public function loadFromDatabase( $flags = self::READ_LATEST ) {
1423 // Paranoia
1424 $this->mId = intval( $this->mId );
1425
1426 if ( !$this->mId ) {
1427 // Anonymous users are not in the database
1428 $this->loadDefaults();
1429 return false;
1430 }
1431
1432 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1433 $db = wfGetDB( $index );
1434
1435 $userQuery = self::getQueryInfo();
1436 $s = $db->selectRow(
1437 $userQuery['tables'],
1438 $userQuery['fields'],
1439 [ 'user_id' => $this->mId ],
1440 __METHOD__,
1441 $options,
1442 $userQuery['joins']
1443 );
1444
1445 $this->queryFlagsUsed = $flags;
1446 Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1447
1448 if ( $s !== false ) {
1449 // Initialise user table data
1450 $this->loadFromRow( $s );
1451 $this->mGroupMemberships = null; // deferred
1452 $this->getEditCount(); // revalidation for nulls
1453 return true;
1454 }
1455
1456 // Invalid user_id
1457 $this->mId = 0;
1458 $this->loadDefaults();
1459
1460 return false;
1461 }
1462
1475 protected function loadFromRow( $row, $data = null ) {
1477
1478 if ( !is_object( $row ) ) {
1479 throw new InvalidArgumentException( '$row must be an object' );
1480 }
1481
1482 $all = true;
1483
1484 $this->mGroupMemberships = null; // deferred
1485
1486 // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
1487 // but it does little harm and might be needed for write callers loading a User.
1489 if ( isset( $row->actor_id ) ) {
1490 $this->mActorId = (int)$row->actor_id;
1491 if ( $this->mActorId !== 0 ) {
1492 $this->mFrom = 'actor';
1493 }
1494 $this->setItemLoaded( 'actor' );
1495 } else {
1496 $all = false;
1497 }
1498 }
1499
1500 if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1501 $this->mName = $row->user_name;
1502 $this->mFrom = 'name';
1503 $this->setItemLoaded( 'name' );
1504 } else {
1505 $all = false;
1506 }
1507
1508 if ( isset( $row->user_real_name ) ) {
1509 $this->mRealName = $row->user_real_name;
1510 $this->setItemLoaded( 'realname' );
1511 } else {
1512 $all = false;
1513 }
1514
1515 if ( isset( $row->user_id ) ) {
1516 $this->mId = intval( $row->user_id );
1517 if ( $this->mId !== 0 ) {
1518 $this->mFrom = 'id';
1519 }
1520 $this->setItemLoaded( 'id' );
1521 } else {
1522 $all = false;
1523 }
1524
1525 if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1526 self::$idCacheByName[$row->user_name] = $row->user_id;
1527 }
1528
1529 if ( isset( $row->user_editcount ) ) {
1530 $this->mEditCount = $row->user_editcount;
1531 } else {
1532 $all = false;
1533 }
1534
1535 if ( isset( $row->user_touched ) ) {
1536 $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1537 } else {
1538 $all = false;
1539 }
1540
1541 if ( isset( $row->user_token ) ) {
1542 // The definition for the column is binary(32), so trim the NULs
1543 // that appends. The previous definition was char(32), so trim
1544 // spaces too.
1545 $this->mToken = rtrim( $row->user_token, " \0" );
1546 if ( $this->mToken === '' ) {
1547 $this->mToken = null;
1548 }
1549 } else {
1550 $all = false;
1551 }
1552
1553 if ( isset( $row->user_email ) ) {
1554 $this->mEmail = $row->user_email;
1555 $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1556 $this->mEmailToken = $row->user_email_token;
1557 $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1558 $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1559 } else {
1560 $all = false;
1561 }
1562
1563 if ( $all ) {
1564 $this->mLoadedItems = true;
1565 }
1566
1567 if ( is_array( $data ) ) {
1568 if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1569 if ( $data['user_groups'] === [] ) {
1570 $this->mGroupMemberships = [];
1571 } else {
1572 $firstGroup = reset( $data['user_groups'] );
1573 if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1574 $this->mGroupMemberships = [];
1575 foreach ( $data['user_groups'] as $row ) {
1576 $ugm = UserGroupMembership::newFromRow( (object)$row );
1577 $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1578 }
1579 }
1580 }
1581 }
1582 if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1583 $this->loadOptions( $data['user_properties'] );
1584 }
1585 }
1586 }
1587
1593 protected function loadFromUserObject( $user ) {
1594 $user->load();
1595 foreach ( self::$mCacheVars as $var ) {
1596 $this->$var = $user->$var;
1597 }
1598 }
1599
1603 private function loadGroups() {
1604 if ( is_null( $this->mGroupMemberships ) ) {
1605 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1606 ? wfGetDB( DB_MASTER )
1607 : wfGetDB( DB_REPLICA );
1608 $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1609 $this->mId, $db );
1610 }
1611 }
1612
1627 public function addAutopromoteOnceGroups( $event ) {
1629
1630 if ( wfReadOnly() || !$this->getId() ) {
1631 return [];
1632 }
1633
1634 $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1635 if ( $toPromote === [] ) {
1636 return [];
1637 }
1638
1639 if ( !$this->checkAndSetTouched() ) {
1640 return []; // raced out (bug T48834)
1641 }
1642
1643 $oldGroups = $this->getGroups(); // previous groups
1644 $oldUGMs = $this->getGroupMemberships();
1645 foreach ( $toPromote as $group ) {
1646 $this->addGroup( $group );
1647 }
1648 $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1649 $newUGMs = $this->getGroupMemberships();
1650
1651 // update groups in external authentication database
1652 Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1653
1654 $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1655 $logEntry->setPerformer( $this );
1656 $logEntry->setTarget( $this->getUserPage() );
1657 $logEntry->setParameters( [
1658 '4::oldgroups' => $oldGroups,
1659 '5::newgroups' => $newGroups,
1660 ] );
1661 $logid = $logEntry->insert();
1663 $logEntry->publish( $logid );
1664 }
1665
1666 return $toPromote;
1667 }
1668
1678 protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
1679 if ( $this->mTouched ) {
1680 // CAS check: only update if the row wasn't changed sicne it was loaded.
1681 $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1682 }
1683
1684 return $conditions;
1685 }
1686
1696 protected function checkAndSetTouched() {
1697 $this->load();
1698
1699 if ( !$this->mId ) {
1700 return false; // anon
1701 }
1702
1703 // Get a new user_touched that is higher than the old one
1704 $newTouched = $this->newTouchedTimestamp();
1705
1706 $dbw = wfGetDB( DB_MASTER );
1707 $dbw->update( 'user',
1708 [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1709 $this->makeUpdateConditions( $dbw, [
1710 'user_id' => $this->mId,
1711 ] ),
1712 __METHOD__
1713 );
1714 $success = ( $dbw->affectedRows() > 0 );
1715
1716 if ( $success ) {
1717 $this->mTouched = $newTouched;
1718 $this->clearSharedCache( 'changed' );
1719 } else {
1720 // Clears on failure too since that is desired if the cache is stale
1721 $this->clearSharedCache( 'refresh' );
1722 }
1723
1724 return $success;
1725 }
1726
1734 public function clearInstanceCache( $reloadFrom = false ) {
1735 $this->mNewtalk = -1;
1736 $this->mDatePreference = null;
1737 $this->mBlockedby = -1; # Unset
1738 $this->mHash = false;
1739 $this->mRights = null;
1740 $this->mEffectiveGroups = null;
1741 $this->mImplicitGroups = null;
1742 $this->mGroupMemberships = null;
1743 $this->mOptions = null;
1744 $this->mOptionsLoaded = false;
1745 $this->mEditCount = null;
1746
1747 if ( $reloadFrom ) {
1748 $this->mLoadedItems = [];
1749 $this->mFrom = $reloadFrom;
1750 }
1751 }
1752
1754 private static $defOpt = null;
1756 private static $defOptLang = null;
1757
1764 public static function resetGetDefaultOptionsForTestsOnly() {
1765 Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
1766 self::$defOpt = null;
1767 self::$defOptLang = null;
1768 }
1769
1776 public static function getDefaultOptions() {
1778
1779 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1780 if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
1781 // The content language does not change (and should not change) mid-request, but the
1782 // unit tests change it anyway, and expect this method to return values relevant to the
1783 // current content language.
1784 return self::$defOpt;
1785 }
1786
1787 self::$defOpt = $wgDefaultUserOptions;
1788 // Default language setting
1789 self::$defOptLang = $contLang->getCode();
1790 self::$defOpt['language'] = self::$defOptLang;
1791 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1792 if ( $langCode === $contLang->getCode() ) {
1793 self::$defOpt['variant'] = $langCode;
1794 } else {
1795 self::$defOpt["variant-$langCode"] = $langCode;
1796 }
1797 }
1798
1799 // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1800 // since extensions may change the set of searchable namespaces depending
1801 // on user groups/permissions.
1802 foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1803 self::$defOpt['searchNs' . $nsnum] = (bool)$val;
1804 }
1805 self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1806
1807 Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
1808
1809 return self::$defOpt;
1810 }
1811
1818 public static function getDefaultOption( $opt ) {
1819 $defOpts = self::getDefaultOptions();
1820 return $defOpts[$opt] ?? null;
1821 }
1822
1829 private function getBlockedStatus( $fromReplica = true ) {
1831
1832 if ( $this->mBlockedby != -1 ) {
1833 return;
1834 }
1835
1836 wfDebug( __METHOD__ . ": checking...\n" );
1837
1838 // Initialize data...
1839 // Otherwise something ends up stomping on $this->mBlockedby when
1840 // things get lazy-loaded later, causing false positive block hits
1841 // due to -1 !== 0. Probably session-related... Nothing should be
1842 // overwriting mBlockedby, surely?
1843 $this->load();
1844
1845 # We only need to worry about passing the IP address to the Block generator if the
1846 # user is not immune to autoblocks/hardblocks, and they are the current user so we
1847 # know which IP address they're actually coming from
1848 $ip = null;
1849 $sessionUser = RequestContext::getMain()->getUser();
1850 // the session user is set up towards the end of Setup.php. Until then,
1851 // assume it's a logged-out user.
1852 $globalUserName = $sessionUser->isSafeToLoad()
1853 ? $sessionUser->getName()
1854 : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1855 if ( $this->getName() === $globalUserName && !$this->isAllowed( 'ipblock-exempt' ) ) {
1856 $ip = $this->getRequest()->getIP();
1857 }
1858
1859 // User/IP blocking
1860 $block = Block::newFromTarget( $this, $ip, !$fromReplica );
1861
1862 // Cookie blocking
1863 if ( !$block instanceof Block ) {
1864 $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
1865 }
1866
1867 // Proxy blocking
1868 if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1869 // Local list
1870 if ( self::isLocallyBlockedProxy( $ip ) ) {
1871 $block = new Block( [
1872 'byText' => wfMessage( 'proxyblocker' )->text(),
1873 'reason' => wfMessage( 'proxyblockreason' )->plain(),
1874 'address' => $ip,
1875 'systemBlock' => 'proxy',
1876 ] );
1877 } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1878 $block = new Block( [
1879 'byText' => wfMessage( 'sorbs' )->text(),
1880 'reason' => wfMessage( 'sorbsreason' )->plain(),
1881 'address' => $ip,
1882 'systemBlock' => 'dnsbl',
1883 ] );
1884 }
1885 }
1886
1887 // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
1888 if ( !$block instanceof Block
1890 && $ip !== null
1891 && !in_array( $ip, $wgProxyWhitelist )
1892 ) {
1893 $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1894 $xff = array_map( 'trim', explode( ',', $xff ) );
1895 $xff = array_diff( $xff, [ $ip ] );
1896 $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$fromReplica );
1897 $block = Block::chooseBlock( $xffblocks, $xff );
1898 if ( $block instanceof Block ) {
1899 # Mangle the reason to alert the user that the block
1900 # originated from matching the X-Forwarded-For header.
1901 $block->setReason( wfMessage( 'xffblockreason', $block->getReason() )->plain() );
1902 }
1903 }
1904
1905 if ( !$block instanceof Block
1906 && $ip !== null
1907 && $this->isAnon()
1908 && IP::isInRanges( $ip, $wgSoftBlockRanges )
1909 ) {
1910 $block = new Block( [
1911 'address' => $ip,
1912 'byText' => 'MediaWiki default',
1913 'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
1914 'anonOnly' => true,
1915 'systemBlock' => 'wgSoftBlockRanges',
1916 ] );
1917 }
1918
1919 if ( $block instanceof Block ) {
1920 wfDebug( __METHOD__ . ": Found block.\n" );
1921 $this->mBlock = $block;
1922 $this->mBlockedby = $block->getByName();
1923 $this->mBlockreason = $block->getReason();
1924 $this->mHideName = $block->getHideName();
1925 $this->mAllowUsertalk = $block->isUsertalkEditAllowed();
1926 } else {
1927 $this->mBlock = null;
1928 $this->mBlockedby = '';
1929 $this->mBlockreason = '';
1930 $this->mHideName = 0;
1931 $this->mAllowUsertalk = false;
1932 }
1933
1934 // Avoid PHP 7.1 warning of passing $this by reference
1935 $thisUser = $this;
1936 // Extensions
1937 Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
1938 }
1939
1945 protected function getBlockFromCookieValue( $blockCookieVal ) {
1946 // Make sure there's something to check. The cookie value must start with a number.
1947 if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
1948 return false;
1949 }
1950 // Load the Block from the ID in the cookie.
1951 $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
1952 if ( $blockCookieId !== null ) {
1953 // An ID was found in the cookie.
1954 $tmpBlock = Block::newFromID( $blockCookieId );
1955 if ( $tmpBlock instanceof Block ) {
1956 $config = RequestContext::getMain()->getConfig();
1957
1958 switch ( $tmpBlock->getType() ) {
1959 case Block::TYPE_USER:
1960 $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
1961 $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
1962 break;
1963 case Block::TYPE_IP:
1964 case Block::TYPE_RANGE:
1965 // If block is type IP or IP range, load only if user is not logged in (T152462)
1966 $blockIsValid = !$tmpBlock->isExpired() && !$this->isLoggedIn();
1967 $useBlockCookie = ( $config->get( 'CookieSetOnIpBlock' ) === true );
1968 break;
1969 default:
1970 $blockIsValid = false;
1971 $useBlockCookie = false;
1972 }
1973
1974 if ( $blockIsValid && $useBlockCookie ) {
1975 // Use the block.
1976 return $tmpBlock;
1977 }
1978
1979 // If the block is not valid, remove the cookie.
1980 Block::clearCookie( $this->getRequest()->response() );
1981 } else {
1982 // If the block doesn't exist, remove the cookie.
1983 Block::clearCookie( $this->getRequest()->response() );
1984 }
1985 }
1986 return false;
1987 }
1988
1996 public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1998
1999 if ( !$wgEnableDnsBlacklist ||
2000 ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) )
2001 ) {
2002 return false;
2003 }
2004
2005 return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
2006 }
2007
2015 public function inDnsBlacklist( $ip, $bases ) {
2016 $found = false;
2017 // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
2018 if ( IP::isIPv4( $ip ) ) {
2019 // Reverse IP, T23255
2020 $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
2021
2022 foreach ( (array)$bases as $base ) {
2023 // Make hostname
2024 // If we have an access key, use that too (ProjectHoneypot, etc.)
2025 $basename = $base;
2026 if ( is_array( $base ) ) {
2027 if ( count( $base ) >= 2 ) {
2028 // Access key is 1, base URL is 0
2029 $host = "{$base[1]}.$ipReversed.{$base[0]}";
2030 } else {
2031 $host = "$ipReversed.{$base[0]}";
2032 }
2033 $basename = $base[0];
2034 } else {
2035 $host = "$ipReversed.$base";
2036 }
2037
2038 // Send query
2039 $ipList = gethostbynamel( $host );
2040
2041 if ( $ipList ) {
2042 wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
2043 $found = true;
2044 break;
2045 }
2046
2047 wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
2048 }
2049 }
2050
2051 return $found;
2052 }
2053
2061 public static function isLocallyBlockedProxy( $ip ) {
2062 global $wgProxyList;
2063
2064 if ( !$wgProxyList ) {
2065 return false;
2066 }
2067
2068 if ( !is_array( $wgProxyList ) ) {
2069 // Load values from the specified file
2070 $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
2071 }
2072
2073 $resultProxyList = [];
2074 $deprecatedIPEntries = [];
2075
2076 // backward compatibility: move all ip addresses in keys to values
2077 foreach ( $wgProxyList as $key => $value ) {
2078 $keyIsIP = IP::isIPAddress( $key );
2079 $valueIsIP = IP::isIPAddress( $value );
2080 if ( $keyIsIP && !$valueIsIP ) {
2081 $deprecatedIPEntries[] = $key;
2082 $resultProxyList[] = $key;
2083 } elseif ( $keyIsIP && $valueIsIP ) {
2084 $deprecatedIPEntries[] = $key;
2085 $resultProxyList[] = $key;
2086 $resultProxyList[] = $value;
2087 } else {
2088 $resultProxyList[] = $value;
2089 }
2090 }
2091
2092 if ( $deprecatedIPEntries ) {
2094 'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
2095 implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
2096 }
2097
2098 $proxyListIPSet = new IPSet( $resultProxyList );
2099 return $proxyListIPSet->match( $ip );
2100 }
2101
2107 public function isPingLimitable() {
2109 if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
2110 // No other good way currently to disable rate limits
2111 // for specific IPs. :P
2112 // But this is a crappy hack and should die.
2113 return false;
2114 }
2115 return !$this->isAllowed( 'noratelimit' );
2116 }
2117
2132 public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
2133 // Avoid PHP 7.1 warning of passing $this by reference
2134 $user = $this;
2135 // Call the 'PingLimiter' hook
2136 $result = false;
2137 if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
2138 return $result;
2139 }
2140
2141 global $wgRateLimits;
2142 if ( !isset( $wgRateLimits[$action] ) ) {
2143 return false;
2144 }
2145
2146 $limits = array_merge(
2147 [ '&can-bypass' => true ],
2148 $wgRateLimits[$action]
2149 );
2150
2151 // Some groups shouldn't trigger the ping limiter, ever
2152 if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
2153 return false;
2154 }
2155
2156 $keys = [];
2157 $id = $this->getId();
2158 $userLimit = false;
2159 $isNewbie = $this->isNewbie();
2160 $cache = ObjectCache::getLocalClusterInstance();
2161
2162 if ( $id == 0 ) {
2163 // limits for anons
2164 if ( isset( $limits['anon'] ) ) {
2165 $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
2166 }
2167 } elseif ( isset( $limits['user'] ) ) {
2168 // limits for logged-in users
2169 $userLimit = $limits['user'];
2170 }
2171
2172 // limits for anons and for newbie logged-in users
2173 if ( $isNewbie ) {
2174 // ip-based limits
2175 if ( isset( $limits['ip'] ) ) {
2176 $ip = $this->getRequest()->getIP();
2177 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
2178 }
2179 // subnet-based limits
2180 if ( isset( $limits['subnet'] ) ) {
2181 $ip = $this->getRequest()->getIP();
2182 $subnet = IP::getSubnet( $ip );
2183 if ( $subnet !== false ) {
2184 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
2185 }
2186 }
2187 }
2188
2189 // Check for group-specific permissions
2190 // If more than one group applies, use the group with the highest limit ratio (max/period)
2191 foreach ( $this->getGroups() as $group ) {
2192 if ( isset( $limits[$group] ) ) {
2193 if ( $userLimit === false
2194 || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2195 ) {
2196 $userLimit = $limits[$group];
2197 }
2198 }
2199 }
2200
2201 // limits for newbie logged-in users (override all the normal user limits)
2202 if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2203 $userLimit = $limits['newbie'];
2204 }
2205
2206 // Set the user limit key
2207 if ( $userLimit !== false ) {
2208 // phan is confused because &can-bypass's value is a bool, so it assumes
2209 // that $userLimit is also a bool here.
2210 // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2211 list( $max, $period ) = $userLimit;
2212 wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2213 $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2214 }
2215
2216 // ip-based limits for all ping-limitable users
2217 if ( isset( $limits['ip-all'] ) ) {
2218 $ip = $this->getRequest()->getIP();
2219 // ignore if user limit is more permissive
2220 if ( $isNewbie || $userLimit === false
2221 || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2222 $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2223 }
2224 }
2225
2226 // subnet-based limits for all ping-limitable users
2227 if ( isset( $limits['subnet-all'] ) ) {
2228 $ip = $this->getRequest()->getIP();
2229 $subnet = IP::getSubnet( $ip );
2230 if ( $subnet !== false ) {
2231 // ignore if user limit is more permissive
2232 if ( $isNewbie || $userLimit === false
2233 || $limits['ip-all'][0] / $limits['ip-all'][1]
2234 > $userLimit[0] / $userLimit[1] ) {
2235 $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2236 }
2237 }
2238 }
2239
2240 $triggered = false;
2241 foreach ( $keys as $key => $limit ) {
2242 // phan is confused because &can-bypass's value is a bool, so it assumes
2243 // that $userLimit is also a bool here.
2244 // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
2245 list( $max, $period ) = $limit;
2246 $summary = "(limit $max in {$period}s)";
2247 $count = $cache->get( $key );
2248 // Already pinged?
2249 if ( $count ) {
2250 if ( $count >= $max ) {
2251 wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2252 "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2253 $triggered = true;
2254 } else {
2255 wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2256 }
2257 } else {
2258 wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2259 if ( $incrBy > 0 ) {
2260 $cache->add( $key, 0, intval( $period ) ); // first ping
2261 }
2262 }
2263 if ( $incrBy > 0 ) {
2264 $cache->incr( $key, $incrBy );
2265 }
2266 }
2267
2268 return $triggered;
2269 }
2270
2278 public function isBlocked( $fromReplica = true ) {
2279 return $this->getBlock( $fromReplica ) instanceof Block &&
2280 $this->getBlock()->appliesToRight( 'edit' );
2281 }
2282
2289 public function getBlock( $fromReplica = true ) {
2290 $this->getBlockedStatus( $fromReplica );
2291 return $this->mBlock instanceof Block ? $this->mBlock : null;
2292 }
2293
2306 public function isBlockedFrom( $title, $fromReplica = false ) {
2307 return MediaWikiServices::getInstance()->getPermissionManager()
2308 ->isBlockedFrom( $this, $title, $fromReplica );
2309 }
2310
2315 public function blockedBy() {
2316 $this->getBlockedStatus();
2317 return $this->mBlockedby;
2318 }
2319
2324 public function blockedFor() {
2325 $this->getBlockedStatus();
2326 return $this->mBlockreason;
2327 }
2328
2333 public function getBlockId() {
2334 $this->getBlockedStatus();
2335 return ( $this->mBlock ? $this->mBlock->getId() : false );
2336 }
2337
2346 public function isBlockedGlobally( $ip = '' ) {
2347 return $this->getGlobalBlock( $ip ) instanceof Block;
2348 }
2349
2360 public function getGlobalBlock( $ip = '' ) {
2361 if ( $this->mGlobalBlock !== null ) {
2362 return $this->mGlobalBlock ?: null;
2363 }
2364 // User is already an IP?
2365 if ( IP::isIPAddress( $this->getName() ) ) {
2366 $ip = $this->getName();
2367 } elseif ( !$ip ) {
2368 $ip = $this->getRequest()->getIP();
2369 }
2370 // Avoid PHP 7.1 warning of passing $this by reference
2371 $user = $this;
2372 $blocked = false;
2373 $block = null;
2374 Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2375
2376 if ( $blocked && $block === null ) {
2377 // back-compat: UserIsBlockedGlobally didn't have $block param first
2378 $block = new Block( [
2379 'address' => $ip,
2380 'systemBlock' => 'global-block'
2381 ] );
2382 }
2383
2384 $this->mGlobalBlock = $blocked ? $block : false;
2385 return $this->mGlobalBlock ?: null;
2386 }
2387
2393 public function isLocked() {
2394 if ( $this->mLocked !== null ) {
2395 return $this->mLocked;
2396 }
2397 // Reset for hook
2398 $this->mLocked = false;
2399 Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2400 return $this->mLocked;
2401 }
2402
2408 public function isHidden() {
2409 if ( $this->mHideName !== null ) {
2410 return (bool)$this->mHideName;
2411 }
2412 $this->getBlockedStatus();
2413 if ( !$this->mHideName ) {
2414 // Reset for hook
2415 $this->mHideName = false;
2416 Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2417 }
2418 return (bool)$this->mHideName;
2419 }
2420
2425 public function getId() {
2426 if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2427 // Special case, we know the user is anonymous
2428 return 0;
2429 }
2430
2431 if ( !$this->isItemLoaded( 'id' ) ) {
2432 // Don't load if this was initialized from an ID
2433 $this->load();
2434 }
2435
2436 return (int)$this->mId;
2437 }
2438
2443 public function setId( $v ) {
2444 $this->mId = $v;
2445 $this->clearInstanceCache( 'id' );
2446 }
2447
2452 public function getName() {
2453 if ( $this->isItemLoaded( 'name', 'only' ) ) {
2454 // Special case optimisation
2455 return $this->mName;
2456 }
2457
2458 $this->load();
2459 if ( $this->mName === false ) {
2460 // Clean up IPs
2461 $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2462 }
2463
2464 return $this->mName;
2465 }
2466
2480 public function setName( $str ) {
2481 $this->load();
2482 $this->mName = $str;
2483 }
2484
2491 public function getActorId( IDatabase $dbw = null ) {
2493
2494 // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
2495 // but it does little harm and might be needed for write callers loading a User.
2497 return 0;
2498 }
2499
2500 if ( !$this->isItemLoaded( 'actor' ) ) {
2501 $this->load();
2502 }
2503
2504 // Currently $this->mActorId might be null if $this was loaded from a
2505 // cache entry that was written when $wgActorTableSchemaMigrationStage
2506 // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
2507 // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
2508 // has been removed), that condition may be removed.
2509 if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2510 $q = [
2511 'actor_user' => $this->getId() ?: null,
2512 'actor_name' => (string)$this->getName(),
2513 ];
2514 if ( $dbw ) {
2515 if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2517 'Cannot create an actor for a usable name that is not an existing user'
2518 );
2519 }
2520 if ( $q['actor_name'] === '' ) {
2521 throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2522 }
2523 $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2524 if ( $dbw->affectedRows() ) {
2525 $this->mActorId = (int)$dbw->insertId();
2526 } else {
2527 // Outdated cache?
2528 // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2529 $this->mActorId = (int)$dbw->selectField(
2530 'actor',
2531 'actor_id',
2532 $q,
2533 __METHOD__,
2534 [ 'LOCK IN SHARE MODE' ]
2535 );
2536 if ( !$this->mActorId ) {
2538 "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2539 );
2540 }
2541 }
2542 $this->invalidateCache();
2543 } else {
2544 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2545 $db = wfGetDB( $index );
2546 $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2547 }
2548 $this->setItemLoaded( 'actor' );
2549 }
2550
2551 return (int)$this->mActorId;
2552 }
2553
2558 public function getTitleKey() {
2559 return str_replace( ' ', '_', $this->getName() );
2560 }
2561
2566 public function getNewtalk() {
2567 $this->load();
2568
2569 // Load the newtalk status if it is unloaded (mNewtalk=-1)
2570 if ( $this->mNewtalk === -1 ) {
2571 $this->mNewtalk = false; # reset talk page status
2572
2573 // Check memcached separately for anons, who have no
2574 // entire User object stored in there.
2575 if ( !$this->mId ) {
2576 global $wgDisableAnonTalk;
2577 if ( $wgDisableAnonTalk ) {
2578 // Anon newtalk disabled by configuration.
2579 $this->mNewtalk = false;
2580 } else {
2581 $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2582 }
2583 } else {
2584 $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2585 }
2586 }
2587
2588 return (bool)$this->mNewtalk;
2589 }
2590
2604 public function getNewMessageLinks() {
2605 // Avoid PHP 7.1 warning of passing $this by reference
2606 $user = $this;
2607 $talks = [];
2608 if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2609 return $talks;
2610 }
2611
2612 if ( !$this->getNewtalk() ) {
2613 return [];
2614 }
2615 $utp = $this->getTalkPage();
2616 $dbr = wfGetDB( DB_REPLICA );
2617 // Get the "last viewed rev" timestamp from the oldest message notification
2618 $timestamp = $dbr->selectField( 'user_newtalk',
2619 'MIN(user_last_timestamp)',
2620 $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2621 __METHOD__ );
2622 $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2623 return [
2624 [
2625 'wiki' => WikiMap::getWikiIdFromDbDomain( WikiMap::getCurrentWikiDbDomain() ),
2626 'link' => $utp->getLocalURL(),
2627 'rev' => $rev
2628 ]
2629 ];
2630 }
2631
2637 public function getNewMessageRevisionId() {
2638 $newMessageRevisionId = null;
2639 $newMessageLinks = $this->getNewMessageLinks();
2640
2641 // Note: getNewMessageLinks() never returns more than a single link
2642 // and it is always for the same wiki, but we double-check here in
2643 // case that changes some time in the future.
2644 if ( $newMessageLinks && count( $newMessageLinks ) === 1
2645 && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
2646 && $newMessageLinks[0]['rev']
2647 ) {
2649 $newMessageRevision = $newMessageLinks[0]['rev'];
2650 $newMessageRevisionId = $newMessageRevision->getId();
2651 }
2652
2653 return $newMessageRevisionId;
2654 }
2655
2664 protected function checkNewtalk( $field, $id ) {
2665 $dbr = wfGetDB( DB_REPLICA );
2666
2667 $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2668
2669 return $ok !== false;
2670 }
2671
2679 protected function updateNewtalk( $field, $id, $curRev = null ) {
2680 // Get timestamp of the talk page revision prior to the current one
2681 $prevRev = $curRev ? $curRev->getPrevious() : false;
2682 $ts = $prevRev ? $prevRev->getTimestamp() : null;
2683 // Mark the user as having new messages since this revision
2684 $dbw = wfGetDB( DB_MASTER );
2685 $dbw->insert( 'user_newtalk',
2686 [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2687 __METHOD__,
2688 'IGNORE' );
2689 if ( $dbw->affectedRows() ) {
2690 wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2691 return true;
2692 }
2693
2694 wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2695 return false;
2696 }
2697
2704 protected function deleteNewtalk( $field, $id ) {
2705 $dbw = wfGetDB( DB_MASTER );
2706 $dbw->delete( 'user_newtalk',
2707 [ $field => $id ],
2708 __METHOD__ );
2709 if ( $dbw->affectedRows() ) {
2710 wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2711 return true;
2712 }
2713
2714 wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2715 return false;
2716 }
2717
2724 public function setNewtalk( $val, $curRev = null ) {
2725 if ( wfReadOnly() ) {
2726 return;
2727 }
2728
2729 $this->load();
2730 $this->mNewtalk = $val;
2731
2732 if ( $this->isAnon() ) {
2733 $field = 'user_ip';
2734 $id = $this->getName();
2735 } else {
2736 $field = 'user_id';
2737 $id = $this->getId();
2738 }
2739
2740 if ( $val ) {
2741 $changed = $this->updateNewtalk( $field, $id, $curRev );
2742 } else {
2743 $changed = $this->deleteNewtalk( $field, $id );
2744 }
2745
2746 if ( $changed ) {
2747 $this->invalidateCache();
2748 }
2749 }
2750
2757 private function newTouchedTimestamp() {
2758 $time = time();
2759 if ( $this->mTouched ) {
2760 $time = max( $time, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2761 }
2762
2763 return wfTimestamp( TS_MW, $time );
2764 }
2765
2776 public function clearSharedCache( $mode = 'refresh' ) {
2777 if ( !$this->getId() ) {
2778 return;
2779 }
2780
2781 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2782 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2783 $key = $this->getCacheKey( $cache );
2784
2785 if ( $mode === 'refresh' ) {
2786 $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
2787 } else {
2788 $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
2789 function () use ( $cache, $key ) {
2790 $cache->delete( $key );
2791 },
2792 __METHOD__
2793 );
2794 }
2795 }
2796
2802 public function invalidateCache() {
2803 $this->touch();
2804 $this->clearSharedCache( 'changed' );
2805 }
2806
2819 public function touch() {
2820 $id = $this->getId();
2821 if ( $id ) {
2822 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2823 $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2824 $cache->touchCheckKey( $key );
2825 $this->mQuickTouched = null;
2826 }
2827 }
2828
2834 public function validateCache( $timestamp ) {
2835 return ( $timestamp >= $this->getTouched() );
2836 }
2837
2846 public function getTouched() {
2847 $this->load();
2848
2849 if ( $this->mId ) {
2850 if ( $this->mQuickTouched === null ) {
2851 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2852 $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2853
2854 $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2855 }
2856
2857 return max( $this->mTouched, $this->mQuickTouched );
2858 }
2859
2860 return $this->mTouched;
2861 }
2862
2868 public function getDBTouched() {
2869 $this->load();
2870
2871 return $this->mTouched;
2872 }
2873
2890 public function setPassword( $str ) {
2891 wfDeprecated( __METHOD__, '1.27' );
2892 return $this->setPasswordInternal( $str );
2893 }
2894
2903 public function setInternalPassword( $str ) {
2904 wfDeprecated( __METHOD__, '1.27' );
2905 $this->setPasswordInternal( $str );
2906 }
2907
2916 private function setPasswordInternal( $str ) {
2917 $manager = AuthManager::singleton();
2918
2919 // If the user doesn't exist yet, fail
2920 if ( !$manager->userExists( $this->getName() ) ) {
2921 throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2922 }
2923
2925 'username' => $this->getName(),
2926 'password' => $str,
2927 'retype' => $str,
2928 ] );
2929 if ( !$status->isGood() ) {
2930 \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
2931 ->info( __METHOD__ . ': Password change rejected: '
2932 . $status->getWikiText( null, null, 'en' ) );
2933 return false;
2934 }
2935
2936 $this->setOption( 'watchlisttoken', false );
2937 SessionManager::singleton()->invalidateSessionsForUser( $this );
2938
2939 return true;
2940 }
2941
2955 $manager = AuthManager::singleton();
2956 $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2957 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2958
2959 $status = Status::newGood( 'ignored' );
2960 foreach ( $reqs as $req ) {
2961 $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2962 }
2963 if ( $status->getValue() === 'ignored' ) {
2964 $status->warning( 'authenticationdatachange-ignored' );
2965 }
2966
2967 if ( $status->isGood() ) {
2968 foreach ( $reqs as $req ) {
2969 $manager->changeAuthenticationData( $req );
2970 }
2971 }
2972 return $status;
2973 }
2974
2981 public function getToken( $forceCreation = true ) {
2983
2984 $this->load();
2985 if ( !$this->mToken && $forceCreation ) {
2986 $this->setToken();
2987 }
2988
2989 if ( !$this->mToken ) {
2990 // The user doesn't have a token, return null to indicate that.
2991 return null;
2992 }
2993
2994 if ( $this->mToken === self::INVALID_TOKEN ) {
2995 // We return a random value here so existing token checks are very
2996 // likely to fail.
2997 return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2998 }
2999
3000 if ( $wgAuthenticationTokenVersion === null ) {
3001 // $wgAuthenticationTokenVersion not in use, so return the raw secret
3002 return $this->mToken;
3003 }
3004
3005 // $wgAuthenticationTokenVersion in use, so hmac it.
3006 $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
3007
3008 // The raw hash can be overly long. Shorten it up.
3009 $len = max( 32, self::TOKEN_LENGTH );
3010 if ( strlen( $ret ) < $len ) {
3011 // Should never happen, even md5 is 128 bits
3012 throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
3013 }
3014
3015 return substr( $ret, -$len );
3016 }
3017
3024 public function setToken( $token = false ) {
3025 $this->load();
3026 if ( $this->mToken === self::INVALID_TOKEN ) {
3027 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3028 ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
3029 } elseif ( !$token ) {
3030 $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
3031 } else {
3032 $this->mToken = $token;
3033 }
3034 }
3035
3044 public function setNewpassword( $str, $throttle = true ) {
3045 throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
3046 }
3047
3052 public function getEmail() {
3053 $this->load();
3054 Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
3055 return $this->mEmail;
3056 }
3057
3063 $this->load();
3064 Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
3066 }
3067
3072 public function setEmail( $str ) {
3073 $this->load();
3074 if ( $str == $this->mEmail ) {
3075 return;
3076 }
3077 $this->invalidateEmail();
3078 $this->mEmail = $str;
3079 Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
3080 }
3081
3089 public function setEmailWithConfirmation( $str ) {
3091
3092 if ( !$wgEnableEmail ) {
3093 return Status::newFatal( 'emaildisabled' );
3094 }
3095
3096 $oldaddr = $this->getEmail();
3097 if ( $str === $oldaddr ) {
3098 return Status::newGood( true );
3099 }
3100
3101 $type = $oldaddr != '' ? 'changed' : 'set';
3102 $notificationResult = null;
3103
3104 if ( $wgEmailAuthentication && $type === 'changed' ) {
3105 // Send the user an email notifying the user of the change in registered
3106 // email address on their previous email address
3107 $change = $str != '' ? 'changed' : 'removed';
3108 $notificationResult = $this->sendMail(
3109 wfMessage( 'notificationemail_subject_' . $change )->text(),
3110 wfMessage( 'notificationemail_body_' . $change,
3111 $this->getRequest()->getIP(),
3112 $this->getName(),
3113 $str )->text()
3114 );
3115 }
3116
3117 $this->setEmail( $str );
3118
3119 if ( $str !== '' && $wgEmailAuthentication ) {
3120 // Send a confirmation request to the new address if needed
3122
3123 if ( $notificationResult !== null ) {
3124 $result->merge( $notificationResult );
3125 }
3126
3127 if ( $result->isGood() ) {
3128 // Say to the caller that a confirmation and notification mail has been sent
3129 $result->value = 'eauth';
3130 }
3131 } else {
3132 $result = Status::newGood( true );
3133 }
3134
3135 return $result;
3136 }
3137
3142 public function getRealName() {
3143 if ( !$this->isItemLoaded( 'realname' ) ) {
3144 $this->load();
3145 }
3146
3147 return $this->mRealName;
3148 }
3149
3154 public function setRealName( $str ) {
3155 $this->load();
3156 $this->mRealName = $str;
3157 }
3158
3169 public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3170 global $wgHiddenPrefs;
3171 $this->loadOptions();
3172
3173 # We want 'disabled' preferences to always behave as the default value for
3174 # users, even if they have set the option explicitly in their settings (ie they
3175 # set it, and then it was disabled removing their ability to change it). But
3176 # we don't want to erase the preferences in the database in case the preference
3177 # is re-enabled again. So don't touch $mOptions, just override the returned value
3178 if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3179 return self::getDefaultOption( $oname );
3180 }
3181
3182 if ( array_key_exists( $oname, $this->mOptions ) ) {
3183 return $this->mOptions[$oname];
3184 }
3185
3186 return $defaultOverride;
3187 }
3188
3197 public function getOptions( $flags = 0 ) {
3198 global $wgHiddenPrefs;
3199 $this->loadOptions();
3201
3202 # We want 'disabled' preferences to always behave as the default value for
3203 # users, even if they have set the option explicitly in their settings (ie they
3204 # set it, and then it was disabled removing their ability to change it). But
3205 # we don't want to erase the preferences in the database in case the preference
3206 # is re-enabled again. So don't touch $mOptions, just override the returned value
3207 foreach ( $wgHiddenPrefs as $pref ) {
3208 $default = self::getDefaultOption( $pref );
3209 if ( $default !== null ) {
3210 $options[$pref] = $default;
3211 }
3212 }
3213
3214 if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3215 $options = array_diff_assoc( $options, self::getDefaultOptions() );
3216 }
3217
3218 return $options;
3219 }
3220
3228 public function getBoolOption( $oname ) {
3229 return (bool)$this->getOption( $oname );
3230 }
3231
3240 public function getIntOption( $oname, $defaultOverride = 0 ) {
3241 $val = $this->getOption( $oname );
3242 if ( $val == '' ) {
3243 $val = $defaultOverride;
3244 }
3245 return intval( $val );
3246 }
3247
3256 public function setOption( $oname, $val ) {
3257 $this->loadOptions();
3258
3259 // Explicitly NULL values should refer to defaults
3260 if ( is_null( $val ) ) {
3261 $val = self::getDefaultOption( $oname );
3262 }
3263
3264 $this->mOptions[$oname] = $val;
3265 }
3266
3277 public function getTokenFromOption( $oname ) {
3278 global $wgHiddenPrefs;
3279
3280 $id = $this->getId();
3281 if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3282 return false;
3283 }
3284
3285 $token = $this->getOption( $oname );
3286 if ( !$token ) {
3287 // Default to a value based on the user token to avoid space
3288 // wasted on storing tokens for all users. When this option
3289 // is set manually by the user, only then is it stored.
3290 $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3291 }
3292
3293 return $token;
3294 }
3295
3305 public function resetTokenFromOption( $oname ) {
3306 global $wgHiddenPrefs;
3307 if ( in_array( $oname, $wgHiddenPrefs ) ) {
3308 return false;
3309 }
3310
3311 $token = MWCryptRand::generateHex( 40 );
3312 $this->setOption( $oname, $token );
3313 return $token;
3314 }
3315
3339 public static function listOptionKinds() {
3340 return [
3341 'registered',
3342 'registered-multiselect',
3343 'registered-checkmatrix',
3344 'userjs',
3345 'special',
3346 'unused'
3347 ];
3348 }
3349
3362 public function getOptionKinds( IContextSource $context, $options = null ) {
3363 $this->loadOptions();
3364 if ( $options === null ) {
3366 }
3367
3368 $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3369 $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3370 $mapping = [];
3371
3372 // Pull out the "special" options, so they don't get converted as
3373 // multiselect or checkmatrix.
3374 $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3375 foreach ( $specialOptions as $name => $value ) {
3376 unset( $prefs[$name] );
3377 }
3378
3379 // Multiselect and checkmatrix options are stored in the database with
3380 // one key per option, each having a boolean value. Extract those keys.
3381 $multiselectOptions = [];
3382 foreach ( $prefs as $name => $info ) {
3383 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3384 ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3385 $opts = HTMLFormField::flattenOptions( $info['options'] );
3386 $prefix = $info['prefix'] ?? $name;
3387
3388 foreach ( $opts as $value ) {
3389 $multiselectOptions["$prefix$value"] = true;
3390 }
3391
3392 unset( $prefs[$name] );
3393 }
3394 }
3395 $checkmatrixOptions = [];
3396 foreach ( $prefs as $name => $info ) {
3397 if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3398 ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3399 $columns = HTMLFormField::flattenOptions( $info['columns'] );
3400 $rows = HTMLFormField::flattenOptions( $info['rows'] );
3401 $prefix = $info['prefix'] ?? $name;
3402
3403 foreach ( $columns as $column ) {
3404 foreach ( $rows as $row ) {
3405 $checkmatrixOptions["$prefix$column-$row"] = true;
3406 }
3407 }
3408
3409 unset( $prefs[$name] );
3410 }
3411 }
3412
3413 // $value is ignored
3414 foreach ( $options as $key => $value ) {
3415 if ( isset( $prefs[$key] ) ) {
3416 $mapping[$key] = 'registered';
3417 } elseif ( isset( $multiselectOptions[$key] ) ) {
3418 $mapping[$key] = 'registered-multiselect';
3419 } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3420 $mapping[$key] = 'registered-checkmatrix';
3421 } elseif ( isset( $specialOptions[$key] ) ) {
3422 $mapping[$key] = 'special';
3423 } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3424 $mapping[$key] = 'userjs';
3425 } else {
3426 $mapping[$key] = 'unused';
3427 }
3428 }
3429
3430 return $mapping;
3431 }
3432
3447 public function resetOptions(
3448 $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3450 ) {
3451 $this->load();
3452 $defaultOptions = self::getDefaultOptions();
3453
3454 if ( !is_array( $resetKinds ) ) {
3455 $resetKinds = [ $resetKinds ];
3456 }
3457
3458 if ( in_array( 'all', $resetKinds ) ) {
3459 $newOptions = $defaultOptions;
3460 } else {
3461 if ( $context === null ) {
3462 $context = RequestContext::getMain();
3463 }
3464
3465 $optionKinds = $this->getOptionKinds( $context );
3466 $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3467 $newOptions = [];
3468
3469 // Use default values for the options that should be deleted, and
3470 // copy old values for the ones that shouldn't.
3471 foreach ( $this->mOptions as $key => $value ) {
3472 if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3473 if ( array_key_exists( $key, $defaultOptions ) ) {
3474 $newOptions[$key] = $defaultOptions[$key];
3475 }
3476 } else {
3477 $newOptions[$key] = $value;
3478 }
3479 }
3480 }
3481
3482 Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3483
3484 $this->mOptions = $newOptions;
3485 $this->mOptionsLoaded = true;
3486 }
3487
3492 public function getDatePreference() {
3493 // Important migration for old data rows
3494 if ( is_null( $this->mDatePreference ) ) {
3495 global $wgLang;
3496 $value = $this->getOption( 'date' );
3497 $map = $wgLang->getDatePreferenceMigrationMap();
3498 if ( isset( $map[$value] ) ) {
3499 $value = $map[$value];
3500 }
3501 $this->mDatePreference = $value;
3502 }
3504 }
3505
3512 public function requiresHTTPS() {
3513 global $wgSecureLogin;
3514 if ( !$wgSecureLogin ) {
3515 return false;
3516 }
3517
3518 $https = $this->getBoolOption( 'prefershttps' );
3519 Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3520 if ( $https ) {
3521 $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3522 }
3523
3524 return $https;
3525 }
3526
3532 public function getStubThreshold() {
3533 global $wgMaxArticleSize; # Maximum article size, in Kb
3534 $threshold = $this->getIntOption( 'stubthreshold' );
3535 if ( $threshold > $wgMaxArticleSize * 1024 ) {
3536 // If they have set an impossible value, disable the preference
3537 // so we can use the parser cache again.
3538 $threshold = 0;
3539 }
3540 return $threshold;
3541 }
3542
3547 public function getRights() {
3548 if ( is_null( $this->mRights ) ) {
3549 $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3550 Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3551
3552 // Deny any rights denied by the user's session, unless this
3553 // endpoint has no sessions.
3554 if ( !defined( 'MW_NO_SESSION' ) ) {
3555 $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3556 if ( $allowedRights !== null ) {
3557 $this->mRights = array_intersect( $this->mRights, $allowedRights );
3558 }
3559 }
3560
3561 Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
3562 // Force reindexation of rights when a hook has unset one of them
3563 $this->mRights = array_values( array_unique( $this->mRights ) );
3564
3565 // If block disables login, we should also remove any
3566 // extra rights blocked users might have, in case the
3567 // blocked user has a pre-existing session (T129738).
3568 // This is checked here for cases where people only call
3569 // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3570 // to give a better error message in the common case.
3571 $config = RequestContext::getMain()->getConfig();
3572 if (
3573 $this->isLoggedIn() &&
3574 $config->get( 'BlockDisablesLogin' ) &&
3575 $this->isBlocked()
3576 ) {
3577 $anon = new User;
3578 $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3579 }
3580 }
3581 return $this->mRights;
3582 }
3583
3590 public function getGroups() {
3591 $this->load();
3592 $this->loadGroups();
3593 return array_keys( $this->mGroupMemberships );
3594 }
3595
3603 public function getGroupMemberships() {
3604 $this->load();
3605 $this->loadGroups();
3607 }
3608
3616 public function getEffectiveGroups( $recache = false ) {
3617 if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3618 $this->mEffectiveGroups = array_unique( array_merge(
3619 $this->getGroups(), // explicit groups
3620 $this->getAutomaticGroups( $recache ) // implicit groups
3621 ) );
3622 // Avoid PHP 7.1 warning of passing $this by reference
3623 $user = $this;
3624 // Hook for additional groups
3625 Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3626 // Force reindexation of groups when a hook has unset one of them
3627 $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3628 }
3630 }
3631
3639 public function getAutomaticGroups( $recache = false ) {
3640 if ( $recache || is_null( $this->mImplicitGroups ) ) {
3641 $this->mImplicitGroups = [ '*' ];
3642 if ( $this->getId() ) {
3643 $this->mImplicitGroups[] = 'user';
3644
3645 $this->mImplicitGroups = array_unique( array_merge(
3646 $this->mImplicitGroups,
3648 ) );
3649 }
3650 if ( $recache ) {
3651 // Assure data consistency with rights/groups,
3652 // as getEffectiveGroups() depends on this function
3653 $this->mEffectiveGroups = null;
3654 }
3655 }
3657 }
3658
3668 public function getFormerGroups() {
3669 $this->load();
3670
3671 if ( is_null( $this->mFormerGroups ) ) {
3672 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3673 ? wfGetDB( DB_MASTER )
3674 : wfGetDB( DB_REPLICA );
3675 $res = $db->select( 'user_former_groups',
3676 [ 'ufg_group' ],
3677 [ 'ufg_user' => $this->mId ],
3678 __METHOD__ );
3679 $this->mFormerGroups = [];
3680 foreach ( $res as $row ) {
3681 $this->mFormerGroups[] = $row->ufg_group;
3682 }
3683 }
3684
3685 return $this->mFormerGroups;
3686 }
3687
3692 public function getEditCount() {
3693 if ( !$this->getId() ) {
3694 return null;
3695 }
3696
3697 if ( $this->mEditCount === null ) {
3698 /* Populate the count, if it has not been populated yet */
3699 $dbr = wfGetDB( DB_REPLICA );
3700 // check if the user_editcount field has been initialized
3701 $count = $dbr->selectField(
3702 'user', 'user_editcount',
3703 [ 'user_id' => $this->mId ],
3704 __METHOD__
3705 );
3706
3707 if ( $count === null ) {
3708 // it has not been initialized. do so.
3709 $count = $this->initEditCountInternal();
3710 }
3711 $this->mEditCount = $count;
3712 }
3713 return (int)$this->mEditCount;
3714 }
3715
3727 public function addGroup( $group, $expiry = null ) {
3728 $this->load();
3729 $this->loadGroups();
3730
3731 if ( $expiry ) {
3732 $expiry = wfTimestamp( TS_MW, $expiry );
3733 }
3734
3735 if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3736 return false;
3737 }
3738
3739 // create the new UserGroupMembership and put it in the DB
3740 $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3741 if ( !$ugm->insert( true ) ) {
3742 return false;
3743 }
3744
3745 $this->mGroupMemberships[$group] = $ugm;
3746
3747 // Refresh the groups caches, and clear the rights cache so it will be
3748 // refreshed on the next call to $this->getRights().
3749 $this->getEffectiveGroups( true );
3750 $this->mRights = null;
3751
3752 $this->invalidateCache();
3753
3754 return true;
3755 }
3756
3763 public function removeGroup( $group ) {
3764 $this->load();
3765
3766 if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3767 return false;
3768 }
3769
3770 $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3771 // delete the membership entry
3772 if ( !$ugm || !$ugm->delete() ) {
3773 return false;
3774 }
3775
3776 $this->loadGroups();
3777 unset( $this->mGroupMemberships[$group] );
3778
3779 // Refresh the groups caches, and clear the rights cache so it will be
3780 // refreshed on the next call to $this->getRights().
3781 $this->getEffectiveGroups( true );
3782 $this->mRights = null;
3783
3784 $this->invalidateCache();
3785
3786 return true;
3787 }
3788
3793 public function isLoggedIn() {
3794 return $this->getId() != 0;
3795 }
3796
3801 public function isAnon() {
3802 return !$this->isLoggedIn();
3803 }
3804
3809 public function isBot() {
3810 if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3811 return true;
3812 }
3813
3814 $isBot = false;
3815 Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3816
3817 return $isBot;
3818 }
3819
3826 public function isAllowedAny() {
3827 $permissions = func_get_args();
3828 foreach ( $permissions as $permission ) {
3829 if ( $this->isAllowed( $permission ) ) {
3830 return true;
3831 }
3832 }
3833 return false;
3834 }
3835
3841 public function isAllowedAll() {
3842 $permissions = func_get_args();
3843 foreach ( $permissions as $permission ) {
3844 if ( !$this->isAllowed( $permission ) ) {
3845 return false;
3846 }
3847 }
3848 return true;
3849 }
3850
3856 public function isAllowed( $action = '' ) {
3857 if ( $action === '' ) {
3858 return true; // In the spirit of DWIM
3859 }
3860 // Use strict parameter to avoid matching numeric 0 accidentally inserted
3861 // by misconfiguration: 0 == 'foo'
3862 return in_array( $action, $this->getRights(), true );
3863 }
3864
3869 public function useRCPatrol() {
3870 global $wgUseRCPatrol;
3871 return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3872 }
3873
3878 public function useNPPatrol() {
3880 return (
3882 && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3883 );
3884 }
3885
3890 public function useFilePatrol() {
3892 return (
3894 && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3895 );
3896 }
3897
3903 public function getRequest() {
3904 if ( $this->mRequest ) {
3905 return $this->mRequest;
3906 }
3907
3908 global $wgRequest;
3909 return $wgRequest;
3910 }
3911
3920 public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3921 if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3922 return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3923 }
3924 return false;
3925 }
3926
3934 public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3935 if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3936 MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3937 $this,
3938 [ $title->getSubjectPage(), $title->getTalkPage() ]
3939 );
3940 }
3941 $this->invalidateCache();
3942 }
3943
3951 public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3952 if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3953 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3954 $store->removeWatch( $this, $title->getSubjectPage() );
3955 $store->removeWatch( $this, $title->getTalkPage() );
3956 }
3957 $this->invalidateCache();
3958 }
3959
3968 public function clearNotification( &$title, $oldid = 0 ) {
3970
3971 // Do nothing if the database is locked to writes
3972 if ( wfReadOnly() ) {
3973 return;
3974 }
3975
3976 // Do nothing if not allowed to edit the watchlist
3977 if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3978 return;
3979 }
3980
3981 // If we're working on user's talk page, we should update the talk page message indicator
3982 if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3983 // Avoid PHP 7.1 warning of passing $this by reference
3984 $user = $this;
3985 if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3986 return;
3987 }
3988
3989 // Try to update the DB post-send and only if needed...
3990 DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3991 if ( !$this->getNewtalk() ) {
3992 return; // no notifications to clear
3993 }
3994
3995 // Delete the last notifications (they stack up)
3996 $this->setNewtalk( false );
3997
3998 // If there is a new, unseen, revision, use its timestamp
3999 $nextid = $oldid
4000 ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
4001 : null;
4002 if ( $nextid ) {
4003 $this->setNewtalk( true, Revision::newFromId( $nextid ) );
4004 }
4005 } );
4006 }
4007
4009 return;
4010 }
4011
4012 if ( $this->isAnon() ) {
4013 // Nothing else to do...
4014 return;
4015 }
4016
4017 // Only update the timestamp if the page is being watched.
4018 // The query to find out if it is watched is cached both in memcached and per-invocation,
4019 // and when it does have to be executed, it can be on a replica DB
4020 // If this is the user's newtalk page, we always update the timestamp
4021 $force = '';
4022 if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
4023 $force = 'force';
4024 }
4025
4026 MediaWikiServices::getInstance()->getWatchedItemStore()
4027 ->resetNotificationTimestamp( $this, $title, $force, $oldid );
4028 }
4029
4036 public function clearAllNotifications() {
4038 // Do nothing if not allowed to edit the watchlist
4039 if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
4040 return;
4041 }
4042
4043 if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
4044 $this->setNewtalk( false );
4045 return;
4046 }
4047
4048 $id = $this->getId();
4049 if ( !$id ) {
4050 return;
4051 }
4052
4053 $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
4054 $watchedItemStore->resetAllNotificationTimestampsForUser( $this );
4055
4056 // We also need to clear here the "you have new message" notification for the own
4057 // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
4058 }
4059
4065 public function getExperienceLevel() {
4066 global $wgLearnerEdits,
4070
4071 if ( $this->isAnon() ) {
4072 return false;
4073 }
4074
4075 $editCount = $this->getEditCount();
4076 $registration = $this->getRegistration();
4077 $now = time();
4078 $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
4079 $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
4080
4081 if ( $editCount < $wgLearnerEdits ||
4082 $registration > $learnerRegistration ) {
4083 return 'newcomer';
4084 }
4085
4086 if ( $editCount > $wgExperiencedUserEdits &&
4087 $registration <= $experiencedRegistration
4088 ) {
4089 return 'experienced';
4090 }
4091
4092 return 'learner';
4093 }
4094
4103 public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
4104 $this->load();
4105 if ( $this->mId == 0 ) {
4106 return;
4107 }
4108
4109 $session = $this->getRequest()->getSession();
4110 if ( $request && $session->getRequest() !== $request ) {
4111 $session = $session->sessionWithRequest( $request );
4112 }
4113 $delay = $session->delaySave();
4114
4115 if ( !$session->getUser()->equals( $this ) ) {
4116 if ( !$session->canSetUser() ) {
4117 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
4118 ->warning( __METHOD__ .
4119 ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
4120 );
4121 return;
4122 }
4123 $session->setUser( $this );
4124 }
4125
4126 $session->setRememberUser( $rememberMe );
4127 if ( $secure !== null ) {
4128 $session->setForceHTTPS( $secure );
4129 }
4130
4131 $session->persist();
4132
4133 ScopedCallback::consume( $delay );
4134 }
4135
4139 public function logout() {
4140 // Avoid PHP 7.1 warning of passing $this by reference
4141 $user = $this;
4142 if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
4143 $this->doLogout();
4144 }
4145 }
4146
4151 public function doLogout() {
4152 $session = $this->getRequest()->getSession();
4153 if ( !$session->canSetUser() ) {
4154 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
4155 ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
4156 $error = 'immutable';
4157 } elseif ( !$session->getUser()->equals( $this ) ) {
4158 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
4159 ->warning( __METHOD__ .
4160 ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4161 );
4162 // But we still may as well make this user object anon
4163 $this->clearInstanceCache( 'defaults' );
4164 $error = 'wronguser';
4165 } else {
4166 $this->clearInstanceCache( 'defaults' );
4167 $delay = $session->delaySave();
4168 $session->unpersist(); // Clear cookies (T127436)
4169 $session->setLoggedOutTimestamp( time() );
4170 $session->setUser( new User );
4171 $session->set( 'wsUserID', 0 ); // Other code expects this
4172 $session->resetAllTokens();
4173 ScopedCallback::consume( $delay );
4174 $error = false;
4175 }
4176 \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4177 'event' => 'logout',
4178 'successful' => $error === false,
4179 'status' => $error ?: 'success',
4180 ] );
4181 }
4182
4187 public function saveSettings() {
4188 if ( wfReadOnly() ) {
4189 // @TODO: caller should deal with this instead!
4190 // This should really just be an exception.
4191 MWExceptionHandler::logException( new DBExpectedError(
4192 null,
4193 "Could not update user with ID '{$this->mId}'; DB is read-only."
4194 ) );
4195 return;
4196 }
4197
4198 $this->load();
4199 if ( $this->mId == 0 ) {
4200 return; // anon
4201 }
4202
4203 // Get a new user_touched that is higher than the old one.
4204 // This will be used for a CAS check as a last-resort safety
4205 // check against race conditions and replica DB lag.
4206 $newTouched = $this->newTouchedTimestamp();
4207
4208 $dbw = wfGetDB( DB_MASTER );
4209 $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
4211
4212 $dbw->update( 'user',
4213 [ /* SET */
4214 'user_name' => $this->mName,
4215 'user_real_name' => $this->mRealName,
4216 'user_email' => $this->mEmail,
4217 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4218 'user_touched' => $dbw->timestamp( $newTouched ),
4219 'user_token' => strval( $this->mToken ),
4220 'user_email_token' => $this->mEmailToken,
4221 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4222 ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4223 'user_id' => $this->mId,
4224 ] ), $fname
4225 );
4226
4227 if ( !$dbw->affectedRows() ) {
4228 // Maybe the problem was a missed cache update; clear it to be safe
4229 $this->clearSharedCache( 'refresh' );
4230 // User was changed in the meantime or loaded with stale data
4231 $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4232 LoggerFactory::getInstance( 'preferences' )->warning(
4233 "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4234 [ 'user_id' => $this->mId, 'db_flag' => $from ]
4235 );
4236 throw new MWException( "CAS update failed on user_touched. " .
4237 "The version of the user to be saved is older than the current version."
4238 );
4239 }
4240
4242 $dbw->update(
4243 'actor',
4244 [ 'actor_name' => $this->mName ],
4245 [ 'actor_user' => $this->mId ],
4246 $fname
4247 );
4248 }
4249 } );
4250
4251 $this->mTouched = $newTouched;
4252 $this->saveOptions();
4253
4254 Hooks::run( 'UserSaveSettings', [ $this ] );
4255 $this->clearSharedCache( 'changed' );
4256 $this->getUserPage()->purgeSquid();
4257 }
4258
4265 public function idForName( $flags = 0 ) {
4266 $s = trim( $this->getName() );
4267 if ( $s === '' ) {
4268 return 0;
4269 }
4270
4271 $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4272 ? wfGetDB( DB_MASTER )
4273 : wfGetDB( DB_REPLICA );
4274
4275 $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4276 ? [ 'LOCK IN SHARE MODE' ]
4277 : [];
4278
4279 $id = $db->selectField( 'user',
4280 'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4281
4282 return (int)$id;
4283 }
4284
4300 public static function createNew( $name, $params = [] ) {
4301 foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4302 if ( isset( $params[$field] ) ) {
4303 wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4304 unset( $params[$field] );
4305 }
4306 }
4307
4308 $user = new User;
4309 $user->load();
4310 $user->setToken(); // init token
4311 if ( isset( $params['options'] ) ) {
4312 $user->mOptions = $params['options'] + (array)$user->mOptions;
4313 unset( $params['options'] );
4314 }
4315 $dbw = wfGetDB( DB_MASTER );
4316
4317 $noPass = PasswordFactory::newInvalidPassword()->toString();
4318
4319 $fields = [
4320 'user_name' => $name,
4321 'user_password' => $noPass,
4322 'user_newpassword' => $noPass,
4323 'user_email' => $user->mEmail,
4324 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4325 'user_real_name' => $user->mRealName,
4326 'user_token' => strval( $user->mToken ),
4327 'user_registration' => $dbw->timestamp( $user->mRegistration ),
4328 'user_editcount' => 0,
4329 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4330 ];
4331 foreach ( $params as $name => $value ) {
4332 $fields["user_$name"] = $value;
4333 }
4334
4335 return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
4336 $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4337 if ( $dbw->affectedRows() ) {
4338 $newUser = self::newFromId( $dbw->insertId() );
4339 $newUser->mName = $fields['user_name'];
4340 $newUser->updateActorId( $dbw );
4341 // Load the user from master to avoid replica lag
4342 $newUser->load( self::READ_LATEST );
4343 } else {
4344 $newUser = null;
4345 }
4346 return $newUser;
4347 } );
4348 }
4349
4376 public function addToDatabase() {
4377 $this->load();
4378 if ( !$this->mToken ) {
4379 $this->setToken(); // init token
4380 }
4381
4382 if ( !is_string( $this->mName ) ) {
4383 throw new RuntimeException( "User name field is not set." );
4384 }
4385
4386 $this->mTouched = $this->newTouchedTimestamp();
4387
4388 $dbw = wfGetDB( DB_MASTER );
4389 $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
4390 $noPass = PasswordFactory::newInvalidPassword()->toString();
4391 $dbw->insert( 'user',
4392 [
4393 'user_name' => $this->mName,
4394 'user_password' => $noPass,
4395 'user_newpassword' => $noPass,
4396 'user_email' => $this->mEmail,
4397 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4398 'user_real_name' => $this->mRealName,
4399 'user_token' => strval( $this->mToken ),
4400 'user_registration' => $dbw->timestamp( $this->mRegistration ),
4401 'user_editcount' => 0,
4402 'user_touched' => $dbw->timestamp( $this->mTouched ),
4403 ], $fname,
4404 [ 'IGNORE' ]
4405 );
4406 if ( !$dbw->affectedRows() ) {
4407 // Use locking reads to bypass any REPEATABLE-READ snapshot.
4408 $this->mId = $dbw->selectField(
4409 'user',
4410 'user_id',
4411 [ 'user_name' => $this->mName ],
4412 $fname,
4413 [ 'LOCK IN SHARE MODE' ]
4414 );
4415 $loaded = false;
4416 if ( $this->mId && $this->loadFromDatabase( self::READ_LOCKING ) ) {
4417 $loaded = true;
4418 }
4419 if ( !$loaded ) {
4420 throw new MWException( $fname . ": hit a key conflict attempting " .
4421 "to insert user '{$this->mName}' row, but it was not present in select!" );
4422 }
4423 return Status::newFatal( 'userexists' );
4424 }
4425 $this->mId = $dbw->insertId();
4426 self::$idCacheByName[$this->mName] = $this->mId;
4427 $this->updateActorId( $dbw );
4428
4429 return Status::newGood();
4430 } );
4431 if ( !$status->isGood() ) {
4432 return $status;
4433 }
4434
4435 // Clear instance cache other than user table data and actor, which is already accurate
4436 $this->clearInstanceCache();
4437
4438 $this->saveOptions();
4439 return Status::newGood();
4440 }
4441
4446 private function updateActorId( IDatabase $dbw ) {
4448
4450 $dbw->insert(
4451 'actor',
4452 [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4453 __METHOD__
4454 );
4455 $this->mActorId = (int)$dbw->insertId();
4456 }
4457 }
4458
4464 public function spreadAnyEditBlock() {
4465 if ( $this->isLoggedIn() && $this->isBlocked() ) {
4466 return $this->spreadBlock();
4467 }
4468
4469 return false;
4470 }
4471
4477 protected function spreadBlock() {
4478 wfDebug( __METHOD__ . "()\n" );
4479 $this->load();
4480 if ( $this->mId == 0 ) {
4481 return false;
4482 }
4483
4484 $userblock = Block::newFromTarget( $this->getName() );
4485 if ( !$userblock ) {
4486 return false;
4487 }
4488
4489 return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4490 }
4491
4496 public function isBlockedFromCreateAccount() {
4497 $this->getBlockedStatus();
4498 if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
4499 return $this->mBlock;
4500 }
4501
4502 # T15611: if the IP address the user is trying to create an account from is
4503 # blocked with createaccount disabled, prevent new account creation there even
4504 # when the user is logged in
4505 if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4506 $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4507 }
4508 return $this->mBlockedFromCreateAccount instanceof Block
4509 && $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
4510 ? $this->mBlockedFromCreateAccount
4511 : false;
4512 }
4513
4518 public function isBlockedFromEmailuser() {
4519 $this->getBlockedStatus();
4520 return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
4521 }
4522
4529 public function isBlockedFromUpload() {
4530 $this->getBlockedStatus();
4531 return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
4532 }
4533
4538 public function isAllowedToCreateAccount() {
4539 return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4540 }
4541
4547 public function getUserPage() {
4548 return Title::makeTitle( NS_USER, $this->getName() );
4549 }
4550
4556 public function getTalkPage() {
4557 $title = $this->getUserPage();
4558 return $title->getTalkPage();
4559 }
4560
4566 public function isNewbie() {
4567 return !$this->isAllowed( 'autoconfirmed' );
4568 }
4569
4576 public function checkPassword( $password ) {
4577 wfDeprecated( __METHOD__, '1.27' );
4578
4579 $manager = AuthManager::singleton();
4580 $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4581 $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4582 [
4583 'username' => $this->getName(),
4584 'password' => $password,
4585 ]
4586 );
4587 $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4588 switch ( $res->status ) {
4589 case AuthenticationResponse::PASS:
4590 return true;
4591 case AuthenticationResponse::FAIL:
4592 // Hope it's not a PreAuthenticationProvider that failed...
4593 \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
4594 ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4595 return false;
4596 default:
4597 throw new BadMethodCallException(
4598 'AuthManager returned a response unsupported by ' . __METHOD__
4599 );
4600 }
4601 }
4602
4611 public function checkTemporaryPassword( $plaintext ) {
4612 wfDeprecated( __METHOD__, '1.27' );
4613 // Can't check the temporary password individually.
4614 return $this->checkPassword( $plaintext );
4615 }
4616
4628 public function getEditTokenObject( $salt = '', $request = null ) {
4629 if ( $this->isAnon() ) {
4630 return new LoggedOutEditToken();
4631 }
4632
4633 if ( !$request ) {
4634 $request = $this->getRequest();
4635 }
4636 return $request->getSession()->getToken( $salt );
4637 }
4638
4652 public function getEditToken( $salt = '', $request = null ) {
4653 return $this->getEditTokenObject( $salt, $request )->toString();
4654 }
4655
4668 public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4669 return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4670 }
4671
4682 public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4683 $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4684 return $this->matchEditToken( $val, $salt, $request, $maxage );
4685 }
4686
4694 public function sendConfirmationMail( $type = 'created' ) {
4695 global $wgLang;
4696 $expiration = null; // gets passed-by-ref and defined in next line.
4697 $token = $this->confirmationToken( $expiration );
4698 $url = $this->confirmationTokenUrl( $token );
4699 $invalidateURL = $this->invalidationTokenUrl( $token );
4700 $this->saveSettings();
4701
4702 if ( $type == 'created' || $type === false ) {
4703 $message = 'confirmemail_body';
4704 $type = 'created';
4705 } elseif ( $type === true ) {
4706 $message = 'confirmemail_body_changed';
4707 $type = 'changed';
4708 } else {
4709 // Messages: confirmemail_body_changed, confirmemail_body_set
4710 $message = 'confirmemail_body_' . $type;
4711 }
4712
4713 $mail = [
4714 'subject' => wfMessage( 'confirmemail_subject' )->text(),
4715 'body' => wfMessage( $message,
4716 $this->getRequest()->getIP(),
4717 $this->getName(),
4718 $url,
4719 $wgLang->userTimeAndDate( $expiration, $this ),
4720 $invalidateURL,
4721 $wgLang->userDate( $expiration, $this ),
4722 $wgLang->userTime( $expiration, $this ) )->text(),
4723 'from' => null,
4724 'replyTo' => null,
4725 ];
4726 $info = [
4727 'type' => $type,
4728 'ip' => $this->getRequest()->getIP(),
4729 'confirmURL' => $url,
4730 'invalidateURL' => $invalidateURL,
4731 'expiration' => $expiration
4732 ];
4733
4734 Hooks::run( 'UserSendConfirmationMail', [ $this, &$mail, $info ] );
4735 return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
4736 }
4737
4749 public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4750 global $wgPasswordSender;
4751
4752 if ( $from instanceof User ) {
4753 $sender = MailAddress::newFromUser( $from );
4754 } else {
4755 $sender = new MailAddress( $wgPasswordSender,
4756 wfMessage( 'emailsender' )->inContentLanguage()->text() );
4757 }
4758 $to = MailAddress::newFromUser( $this );
4759
4760 return UserMailer::send( $to, $sender, $subject, $body, [
4761 'replyTo' => $replyto,
4762 ] );
4763 }
4764
4775 protected function confirmationToken( &$expiration ) {
4777 $now = time();
4778 $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4779 $expiration = wfTimestamp( TS_MW, $expires );
4780 $this->load();
4781 $token = MWCryptRand::generateHex( 32 );
4782 $hash = md5( $token );
4783 $this->mEmailToken = $hash;
4784 $this->mEmailTokenExpires = $expiration;
4785 return $token;
4786 }
4787
4793 protected function confirmationTokenUrl( $token ) {
4794 return $this->getTokenUrl( 'ConfirmEmail', $token );
4795 }
4796
4802 protected function invalidationTokenUrl( $token ) {
4803 return $this->getTokenUrl( 'InvalidateEmail', $token );
4804 }
4805
4820 protected function getTokenUrl( $page, $token ) {
4821 // Hack to bypass localization of 'Special:'
4822 $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4823 return $title->getCanonicalURL();
4824 }
4825
4833 public function confirmEmail() {
4834 // Check if it's already confirmed, so we don't touch the database
4835 // and fire the ConfirmEmailComplete hook on redundant confirmations.
4836 if ( !$this->isEmailConfirmed() ) {
4838 Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4839 }
4840 return true;
4841 }
4842
4850 public function invalidateEmail() {
4851 $this->load();
4852 $this->mEmailToken = null;
4853 $this->mEmailTokenExpires = null;
4854 $this->setEmailAuthenticationTimestamp( null );
4855 $this->mEmail = '';
4856 Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4857 return true;
4858 }
4859
4864 public function setEmailAuthenticationTimestamp( $timestamp ) {
4865 $this->load();
4866 $this->mEmailAuthenticated = $timestamp;
4867 Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4868 }
4869
4875 public function canSendEmail() {
4877 if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4878 return false;
4879 }
4880 $canSend = $this->isEmailConfirmed();
4881 // Avoid PHP 7.1 warning of passing $this by reference
4882 $user = $this;
4883 Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4884 return $canSend;
4885 }
4886
4892 public function canReceiveEmail() {
4893 return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4894 }
4895
4906 public function isEmailConfirmed() {
4908 $this->load();
4909 // Avoid PHP 7.1 warning of passing $this by reference
4910 $user = $this;
4911 $confirmed = true;
4912 if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4913 if ( $this->isAnon() ) {
4914 return false;
4915 }
4916 if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4917 return false;
4918 }
4920 return false;
4921 }
4922 return true;
4923 }
4924
4925 return $confirmed;
4926 }
4927
4932 public function isEmailConfirmationPending() {
4934 return $wgEmailAuthentication &&
4935 !$this->isEmailConfirmed() &&
4936 $this->mEmailToken &&
4937 $this->mEmailTokenExpires > wfTimestamp();
4938 }
4939
4947 public function getRegistration() {
4948 if ( $this->isAnon() ) {
4949 return false;
4950 }
4951 $this->load();
4952 return $this->mRegistration;
4953 }
4954
4961 public function getFirstEditTimestamp() {
4962 return $this->getEditTimestamp( true );
4963 }
4964
4972 public function getLatestEditTimestamp() {
4973 return $this->getEditTimestamp( false );
4974 }
4975
4983 private function getEditTimestamp( $first ) {
4984 if ( $this->getId() == 0 ) {
4985 return false; // anons
4986 }
4987 $dbr = wfGetDB( DB_REPLICA );
4988 $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4989 $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
4990 ? 'revactor_timestamp' : 'rev_timestamp';
4991 $sortOrder = $first ? 'ASC' : 'DESC';
4992 $time = $dbr->selectField(
4993 [ 'revision' ] + $actorWhere['tables'],
4994 $tsField,
4995 [ $actorWhere['conds'] ],
4996 __METHOD__,
4997 [ 'ORDER BY' => "$tsField $sortOrder" ],
4998 $actorWhere['joins']
4999 );
5000 if ( !$time ) {
5001 return false; // no edits
5002 }
5003 return wfTimestamp( TS_MW, $time );
5004 }
5005
5012 public static function getGroupPermissions( $groups ) {
5014 $rights = [];
5015 // grant every granted permission first
5016 foreach ( $groups as $group ) {
5017 if ( isset( $wgGroupPermissions[$group] ) ) {
5018 $rights = array_merge( $rights,
5019 // array_filter removes empty items
5020 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
5021 }
5022 }
5023 // now revoke the revoked permissions
5024 foreach ( $groups as $group ) {
5025 if ( isset( $wgRevokePermissions[$group] ) ) {
5026 $rights = array_diff( $rights,
5027 array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
5028 }
5029 }
5030 return array_unique( $rights );
5031 }
5032
5039 public static function getGroupsWithPermission( $role ) {
5040 global $wgGroupPermissions;
5041 $allowedGroups = [];
5042 foreach ( array_keys( $wgGroupPermissions ) as $group ) {
5043 if ( self::groupHasPermission( $group, $role ) ) {
5044 $allowedGroups[] = $group;
5045 }
5046 }
5047 return $allowedGroups;
5048 }
5049
5062 public static function groupHasPermission( $group, $role ) {
5064 return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
5065 && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
5066 }
5067
5082 public static function isEveryoneAllowed( $right ) {
5084 static $cache = [];
5085
5086 // Use the cached results, except in unit tests which rely on
5087 // being able change the permission mid-request
5088 if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
5089 return $cache[$right];
5090 }
5091
5092 if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
5093 $cache[$right] = false;
5094 return false;
5095 }
5096
5097 // If it's revoked anywhere, then everyone doesn't have it
5098 foreach ( $wgRevokePermissions as $rights ) {
5099 if ( isset( $rights[$right] ) && $rights[$right] ) {
5100 $cache[$right] = false;
5101 return false;
5102 }
5103 }
5104
5105 // Remove any rights that aren't allowed to the global-session user,
5106 // unless there are no sessions for this endpoint.
5107 if ( !defined( 'MW_NO_SESSION' ) ) {
5108 $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
5109 if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
5110 $cache[$right] = false;
5111 return false;
5112 }
5113 }
5114
5115 // Allow extensions to say false
5116 if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
5117 $cache[$right] = false;
5118 return false;
5119 }
5120
5121 $cache[$right] = true;
5122 return true;
5123 }
5124
5131 public static function getAllGroups() {
5133 return array_values( array_diff(
5134 array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
5135 self::getImplicitGroups()
5136 ) );
5137 }
5138
5143 public static function getAllRights() {
5144 if ( self::$mAllRights === false ) {
5145 global $wgAvailableRights;
5146 if ( count( $wgAvailableRights ) ) {
5147 self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
5148 } else {
5149 self::$mAllRights = self::$mCoreRights;
5150 }
5151 Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
5152 }
5153 return self::$mAllRights;
5154 }
5155
5162 public static function getImplicitGroups() {
5163 global $wgImplicitGroups;
5164 return $wgImplicitGroups;
5165 }
5166
5174 public static function getGroupPage( $group ) {
5175 wfDeprecated( __METHOD__, '1.29' );
5176 return UserGroupMembership::getGroupPage( $group );
5177 }
5178
5189 public static function makeGroupLinkHTML( $group, $text = '' ) {
5190 wfDeprecated( __METHOD__, '1.29' );
5191
5192 if ( $text == '' ) {
5193 $text = UserGroupMembership::getGroupName( $group );
5194 }
5195 $title = UserGroupMembership::getGroupPage( $group );
5196 if ( $title ) {
5197 return MediaWikiServices::getInstance()
5198 ->getLinkRenderer()->makeLink( $title, $text );
5199 }
5200
5201 return htmlspecialchars( $text );
5202 }
5203
5214 public static function makeGroupLinkWiki( $group, $text = '' ) {
5215 wfDeprecated( __METHOD__, '1.29' );
5216
5217 if ( $text == '' ) {
5218 $text = UserGroupMembership::getGroupName( $group );
5219 }
5220 $title = UserGroupMembership::getGroupPage( $group );
5221 if ( $title ) {
5222 $page = $title->getFullText();
5223 return "[[$page|$text]]";
5224 }
5225
5226 return $text;
5227 }
5228
5238 public static function changeableByGroup( $group ) {
5240
5241 $groups = [
5242 'add' => [],
5243 'remove' => [],
5244 'add-self' => [],
5245 'remove-self' => []
5246 ];
5247
5248 if ( empty( $wgAddGroups[$group] ) ) {
5249 // Don't add anything to $groups
5250 } elseif ( $wgAddGroups[$group] === true ) {
5251 // You get everything
5252 $groups['add'] = self::getAllGroups();
5253 } elseif ( is_array( $wgAddGroups[$group] ) ) {
5254 $groups['add'] = $wgAddGroups[$group];
5255 }
5256
5257 // Same thing for remove
5258 if ( empty( $wgRemoveGroups[$group] ) ) {
5259 // Do nothing
5260 } elseif ( $wgRemoveGroups[$group] === true ) {
5261 $groups['remove'] = self::getAllGroups();
5262 } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5263 $groups['remove'] = $wgRemoveGroups[$group];
5264 }
5265
5266 // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5267 if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5268 foreach ( $wgGroupsAddToSelf as $key => $value ) {
5269 if ( is_int( $key ) ) {
5270 $wgGroupsAddToSelf['user'][] = $value;
5271 }
5272 }
5273 }
5274
5275 if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5276 foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5277 if ( is_int( $key ) ) {
5278 $wgGroupsRemoveFromSelf['user'][] = $value;
5279 }
5280 }
5281 }
5282
5283 // Now figure out what groups the user can add to him/herself
5284 if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5285 // Do nothing
5286 } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5287 // No idea WHY this would be used, but it's there
5288 $groups['add-self'] = self::getAllGroups();
5289 } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5290 $groups['add-self'] = $wgGroupsAddToSelf[$group];
5291 }
5292
5293 if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5294 // Do nothing
5295 } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5296 $groups['remove-self'] = self::getAllGroups();
5297 } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5298 $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5299 }
5300
5301 return $groups;
5302 }
5303
5311 public function changeableGroups() {
5312 if ( $this->isAllowed( 'userrights' ) ) {
5313 // This group gives the right to modify everything (reverse-
5314 // compatibility with old "userrights lets you change
5315 // everything")
5316 // Using array_merge to make the groups reindexed
5317 $all = array_merge( self::getAllGroups() );
5318 return [
5319 'add' => $all,
5320 'remove' => $all,
5321 'add-self' => [],
5322 'remove-self' => []
5323 ];
5324 }
5325
5326 // Okay, it's not so simple, we will have to go through the arrays
5327 $groups = [
5328 'add' => [],
5329 'remove' => [],
5330 'add-self' => [],
5331 'remove-self' => []
5332 ];
5333 $addergroups = $this->getEffectiveGroups();
5334
5335 foreach ( $addergroups as $addergroup ) {
5336 $groups = array_merge_recursive(
5337 $groups, $this->changeableByGroup( $addergroup )
5338 );
5339 $groups['add'] = array_unique( $groups['add'] );
5340 $groups['remove'] = array_unique( $groups['remove'] );
5341 $groups['add-self'] = array_unique( $groups['add-self'] );
5342 $groups['remove-self'] = array_unique( $groups['remove-self'] );
5343 }
5344 return $groups;
5345 }
5346
5350 public function incEditCount() {
5351 if ( $this->isAnon() ) {
5352 return; // sanity
5353 }
5354
5355 DeferredUpdates::addUpdate(
5356 new UserEditCountUpdate( $this, 1 ),
5357 DeferredUpdates::POSTSEND
5358 );
5359 }
5360
5366 public function setEditCountInternal( $count ) {
5367 $this->mEditCount = $count;
5368 }
5369
5377 public function initEditCountInternal() {
5378 // Pull from a replica DB to be less cruel to servers
5379 // Accuracy isn't the point anyway here
5380 $dbr = wfGetDB( DB_REPLICA );
5381 $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5382 $count = (int)$dbr->selectField(
5383 [ 'revision' ] + $actorWhere['tables'],
5384 'COUNT(*)',
5385 [ $actorWhere['conds'] ],
5386 __METHOD__,
5387 [],
5388 $actorWhere['joins']
5389 );
5390
5391 $dbw = wfGetDB( DB_MASTER );
5392 $dbw->update(
5393 'user',
5394 [ 'user_editcount' => $count ],
5395 [
5396 'user_id' => $this->getId(),
5397 'user_editcount IS NULL OR user_editcount < ' . (int)$count
5398 ],
5399 __METHOD__
5400 );
5401
5402 return $count;
5403 }
5404
5412 public static function getRightDescription( $right ) {
5413 $key = "right-$right";
5414 $msg = wfMessage( $key );
5415 return $msg->isDisabled() ? $right : $msg->text();
5416 }
5417
5425 public static function getGrantName( $grant ) {
5426 $key = "grant-$grant";
5427 $msg = wfMessage( $key );
5428 return $msg->isDisabled() ? $grant : $msg->text();
5429 }
5430
5451 public function addNewUserLogEntry( $action = false, $reason = '' ) {
5452 return true; // disabled
5453 }
5454
5464 $this->addNewUserLogEntry( 'autocreate' );
5465
5466 return true;
5467 }
5468
5474 protected function loadOptions( $data = null ) {
5475 $this->load();
5476
5477 if ( $this->mOptionsLoaded ) {
5478 return;
5479 }
5480
5481 $this->mOptions = self::getDefaultOptions();
5482
5483 if ( !$this->getId() ) {
5484 // For unlogged-in users, load language/variant options from request.
5485 // There's no need to do it for logged-in users: they can set preferences,
5486 // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5487 // so don't override user's choice (especially when the user chooses site default).
5488 $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5489 $this->mOptions['variant'] = $variant;
5490 $this->mOptions['language'] = $variant;
5491 $this->mOptionsLoaded = true;
5492 return;
5493 }
5494
5495 // Maybe load from the object
5496 if ( !is_null( $this->mOptionOverrides ) ) {
5497 wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5498 foreach ( $this->mOptionOverrides as $key => $value ) {
5499 $this->mOptions[$key] = $value;
5500 }
5501 } else {
5502 if ( !is_array( $data ) ) {
5503 wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5504 // Load from database
5505 $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5506 ? wfGetDB( DB_MASTER )
5507 : wfGetDB( DB_REPLICA );
5508
5509 $res = $dbr->select(
5510 'user_properties',
5511 [ 'up_property', 'up_value' ],
5512 [ 'up_user' => $this->getId() ],
5513 __METHOD__
5514 );
5515
5516 $this->mOptionOverrides = [];
5517 $data = [];
5518 foreach ( $res as $row ) {
5519 // Convert '0' to 0. PHP's boolean conversion considers them both
5520 // false, but e.g. JavaScript considers the former as true.
5521 // @todo: T54542 Somehow determine the desired type (string/int/bool)
5522 // and convert all values here.
5523 if ( $row->up_value === '0' ) {
5524 $row->up_value = 0;
5525 }
5526 $data[$row->up_property] = $row->up_value;
5527 }
5528 }
5529
5530 foreach ( $data as $property => $value ) {
5531 $this->mOptionOverrides[$property] = $value;
5532 $this->mOptions[$property] = $value;
5533 }
5534 }
5535
5536 // Replace deprecated language codes
5537 $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5538 $this->mOptions['language']
5539 );
5540
5541 $this->mOptionsLoaded = true;
5542
5543 Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5544 }
5545
5551 protected function saveOptions() {
5552 $this->loadOptions();
5553
5554 // Not using getOptions(), to keep hidden preferences in database
5555 $saveOptions = $this->mOptions;
5556
5557 // Allow hooks to abort, for instance to save to a global profile.
5558 // Reset options to default state before saving.
5559 if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5560 return;
5561 }
5562
5563 $userId = $this->getId();
5564
5565 $insert_rows = []; // all the new preference rows
5566 foreach ( $saveOptions as $key => $value ) {
5567 // Don't bother storing default values
5568 $defaultOption = self::getDefaultOption( $key );
5569 if ( ( $defaultOption === null && $value !== false && $value !== null )
5570 || $value != $defaultOption
5571 ) {
5572 $insert_rows[] = [
5573 'up_user' => $userId,
5574 'up_property' => $key,
5575 'up_value' => $value,
5576 ];
5577 }
5578 }
5579
5580 $dbw = wfGetDB( DB_MASTER );
5581
5582 $res = $dbw->select( 'user_properties',
5583 [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5584
5585 // Find prior rows that need to be removed or updated. These rows will
5586 // all be deleted (the latter so that INSERT IGNORE applies the new values).
5587 $keysDelete = [];
5588 foreach ( $res as $row ) {
5589 if ( !isset( $saveOptions[$row->up_property] )
5590 || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5591 ) {
5592 $keysDelete[] = $row->up_property;
5593 }
5594 }
5595
5596 if ( count( $keysDelete ) ) {
5597 // Do the DELETE by PRIMARY KEY for prior rows.
5598 // In the past a very large portion of calls to this function are for setting
5599 // 'rememberpassword' for new accounts (a preference that has since been removed).
5600 // Doing a blanket per-user DELETE for new accounts with no rows in the table
5601 // caused gap locks on [max user ID,+infinity) which caused high contention since
5602 // updates would pile up on each other as they are for higher (newer) user IDs.
5603 // It might not be necessary these days, but it shouldn't hurt either.
5604 $dbw->delete( 'user_properties',
5605 [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5606 }
5607 // Insert the new preference rows
5608 $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5609 }
5610
5617 public static function selectFields() {
5618 wfDeprecated( __METHOD__, '1.31' );
5619 return [
5620 'user_id',
5621 'user_name',
5622 'user_real_name',
5623 'user_email',
5624 'user_touched',
5625 'user_token',
5626 'user_email_authenticated',
5627 'user_email_token',
5628 'user_email_token_expires',
5629 'user_registration',
5630 'user_editcount',
5631 ];
5632 }
5633
5643 public static function getQueryInfo() {
5645
5646 $ret = [
5647 'tables' => [ 'user' ],
5648 'fields' => [
5649 'user_id',
5650 'user_name',
5651 'user_real_name',
5652 'user_email',
5653 'user_touched',
5654 'user_token',
5655 'user_email_authenticated',
5656 'user_email_token',
5657 'user_email_token_expires',
5658 'user_registration',
5659 'user_editcount',
5660 ],
5661 'joins' => [],
5662 ];
5663
5664 // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
5665 // but it does little harm and might be needed for write callers loading a User.
5667 $ret['tables']['user_actor'] = 'actor';
5668 $ret['fields'][] = 'user_actor.actor_id';
5669 $ret['joins']['user_actor'] = [
5670 ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
5671 [ 'user_actor.actor_user = user_id' ]
5672 ];
5673 }
5674
5675 return $ret;
5676 }
5677
5685 static function newFatalPermissionDeniedStatus( $permission ) {
5686 global $wgLang;
5687
5688 $groups = [];
5689 foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5690 $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5691 }
5692
5693 if ( $groups ) {
5694 return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5695 }
5696
5697 return Status::newFatal( 'badaccess-group0' );
5698 }
5699
5709 public function getInstanceForUpdate() {
5710 if ( !$this->getId() ) {
5711 return null; // anon
5712 }
5713
5714 $user = self::newFromId( $this->getId() );
5715 if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5716 return null;
5717 }
5718
5719 return $user;
5720 }
5721
5729 public function equals( UserIdentity $user ) {
5730 // XXX it's not clear whether central ID providers are supposed to obey this
5731 return $this->getName() === $user->getName();
5732 }
5733
5739 public function isAllowUsertalk() {
5740 return $this->mAllowUsertalk;
5741 }
5742
5743}
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
target page
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
$wgRateLimitsExcludedIPs
Array of IPs / CIDR ranges which should be excluded from rate limits.
$wgGroupsRemoveFromSelf
$wgApplyIpBlocksToXff
Whether to look at the X-Forwarded-For header's list of (potentially spoofed) IPs and apply IP blocks...
$wgUserEmailConfirmationTokenExpiry
The time, in seconds, when an email confirmation email expires.
$wgRemoveGroups
$wgMaxArticleSize
Maximum article size in kilobytes.
$wgLearnerMemberSince
Name of the external diff engine to use.
$wgProxyList
Big list of banned IP addresses.
$wgHiddenPrefs
An array of preferences to not show for the user.
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
$wgAutopromoteOnceLogInRC
Put user rights log entries for autopromotion in recent changes?
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
$wgPasswordPolicy
Password policy for the wiki.
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
string[] $wgSoftBlockRanges
IP ranges that should be considered soft-blocked (anon-only, account creation allowed).
$wgProxyWhitelist
Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other methods mi...
$wgGroupsAddToSelf
A map of group names that the user is in, to group names that those users are allowed to add or revok...
$wgAvailableRights
A list of available rights, in addition to the ones defined by the core.
$wgShowUpdatedMarker
Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages ...
$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.
$wgInvalidUsernameCharacters
Characters to prevent during new account creations.
$wgEnableEmail
Set to true to enable the e-mail basic features: Password reminders, etc.
$wgMaxNameChars
Maximum number of bytes in username.
$wgSecureLogin
This is to let user authenticate using https when they come from http.
$wgImplicitGroups
Implicit groups, aren't shown on Special:Listusers or somewhere else.
$wgAddGroups
$wgAddGroups and $wgRemoveGroups can be used to give finer control over who can assign which groups a...
$wgGroupPermissions
Permission keys given to users in each group.
$wgExperiencedUserEdits
Name of the external diff engine to use.
$wgReservedUsernames
Array of usernames which may not be registered or logged in from Maintenance scripts can still use th...
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
$wgRevokePermissions
Permission keys revoked from users in each group.
$wgEnableDnsBlacklist
Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies.
$wgDnsBlacklistUrls
List of DNS blacklists to use, if $wgEnableDnsBlacklist is true.
$wgMinimalPasswordLength
Specifies the minimal length of a user password.
$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.
$wgNamespacesToBeSearchedDefault
List of namespaces which are searched by default.
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
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.
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.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$messages
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition Setup.php:123
foreach( $wgExtensionFunctions as $func) if(!defined('MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition Setup.php:929
$wgUseEnotif
Definition Setup.php:437
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:728
$wgLang
Definition Setup.php:875
static getAutopromoteOnceGroups(User $user, $event)
Get the groups for the given user based on the given criteria.
static getAutopromoteGroups(User $user)
Get the groups for the given user based on $wgAutopromote.
static clearCookie(WebResponse $response)
Unset the 'BlockID' cookie.
Definition Block.php:1815
static newFromID( $id)
Load a block from the block id.
Definition Block.php:192
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition Block.php:1442
appliesToRight( $right)
Determine whether the Block prevents a given right.
Definition Block.php:1231
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition Block.php:1523
const TYPE_RANGE
Definition Block.php:98
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the 'BlockID' cookie.
Definition Block.php:1851
const TYPE_USER
Definition Block.php:96
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing Block object if possible.
Definition Block.php:1403
const TYPE_IP
Definition Block.php:97
Exception thrown when an actor can't be created.
Value object representing a logged-out user's edit token.
static hmac( $data, $key, $raw=true)
Generate an acceptably unstable one-way-hmac of some text making use of the best hash algorithm that ...
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.
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Class for creating new log entries and inserting them into the database.
Definition LogEntry.php:441
This serves as the entry point to the authentication system.
This is a value object for authentication requests.
This is a value object to hold authentication response data.
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
This serves as the entry point to the MediaWiki session handling system.
Value object representing a CSRF token.
Definition Token.php:32
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition Revision.php:295
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition Revision.php:118
static newFromIDs( $ids)
Definition UserArray.php:45
static singleton()
Definition UserCache.php:34
Handles increment the edit count for a given set of users.
Represents a "user group membership" – a specific instance of a user belonging to a group.
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
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:48
getEditTimestamp( $first)
Get the timestamp of the first or latest edit.
Definition User.php:4983
loadFromSession()
Load user data from the session.
Definition User.php:1370
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition User.php:3934
setEditCountInternal( $count)
This method should not be called outside User/UserEditCountUpdate.
Definition User.php:5366
string $mEmailToken
Definition User.php:222
string $mToken
Definition User.php:218
string $mTouched
TS_MW timestamp from the DB.
Definition User.php:214
logout()
Log this user out.
Definition User.php:4139
getOptions( $flags=0)
Get all user's options.
Definition User.php:3197
getRequest()
Get the WebRequest object to use with this object.
Definition User.php:3903
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition User.php:1159
Block $mBlockedFromCreateAccount
Definition User.php:298
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2452
addToDatabase()
Add this existing user object to the database.
Definition User.php:4376
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition User.php:3512
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition User.php:4446
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition User.php:4065
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition User.php:5082
static string null $defOptLang
Is the user an IP range?
Definition User.php:1756
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:585
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition User.php:3447
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition User.php:895
invalidateCache()
Immediately touch the user data cache for this account.
Definition User.php:2802
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition User.php:86
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition User.php:3062
array $mOptionOverrides
Definition User.php:232
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition User.php:4518
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition User.php:5643
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition User.php:239
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition User.php:1117
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition User.php:4961
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:3362
int null $mActorId
Definition User.php:207
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition User.php:5238
const VERSION
@const int Serialized record version.
Definition User.php:62
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:4628
static getAllGroups()
Return the set of defined explicit groups.
Definition User.php:5131
bool $mAllowUsertalk
Definition User.php:295
string $mEmailTokenExpires
Definition User.php:224
isAllowUsertalk()
Checks if usertalk is allowed.
Definition User.php:5739
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition User.php:4103
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:4652
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition User.php:1996
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition User.php:301
static $mAllRights
String Cached results of getAllRights()
Definition User.php:198
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition User.php:5729
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition User.php:4820
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition User.php:3727
array $mEffectiveGroups
Definition User.php:274
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2346
string $mQuickTouched
TS_MW timestamp from cache.
Definition User.php:216
const INVALID_TOKEN
@const string An invalid value for user_token
Definition User.php:57
isSafeToLoad()
Test if it's safe to load this User object.
Definition User.php:341
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition User.php:5062
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition User.php:4906
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition User.php:4464
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition User.php:3890
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition User.php:458
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition User.php:3856
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition User.php:2868
setName( $str)
Set the user name.
Definition User.php:2480
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition User.php:2954
Block $mGlobalBlock
Definition User.php:280
static $mCoreRights
Array of Strings Core rights.
Definition User.php:113
getId()
Get the user's ID.
Definition User.php:2425
getRealName()
Get the user's real name.
Definition User.php:3142
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition User.php:5474
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition User.php:3228
getBlockedStatus( $fromReplica=true)
Get blocking information.
Definition User.php:1829
getRegistration()
Get the timestamp of account creation.
Definition User.php:4947
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition User.php:2061
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2360
string $mRealName
Definition User.php:209
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition User.php:676
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition User.php:1349
getMutableCacheKeys(WANObjectCache $cache)
Definition User.php:510
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition User.php:3277
isNewbie()
Determine whether the user is a newbie.
Definition User.php:4566
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition User.php:3968
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition User.php:947
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition User.php:2704
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition User.php:1422
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition User.php:4802
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition User.php:3878
static randomPassword()
Return a random password.
Definition User.php:1297
static purge( $wikiId, $userId)
Definition User.php:488
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition User.php:3240
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition User.php:1734
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition User.php:4892
string $mEmail
Definition User.php:212
loadDefaults( $name=false)
Set cached properties to default.
Definition User.php:1310
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition User.php:1405
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition User.php:3169
touch()
Update the "touched" timestamp for the user.
Definition User.php:2819
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition User.php:4611
isPingLimitable()
Is this user subject to rate limiting?
Definition User.php:2107
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition User.php:1244
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition User.php:772
getToken( $forceCreation=true)
Get the user's current token.
Definition User.php:2981
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:609
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition User.php:2903
confirmEmail()
Mark the e-mail address confirmed.
Definition User.php:4833
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition User.php:5039
setId( $v)
Set the user and reload all fields according to a given ID.
Definition User.php:2443
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition User.php:5012
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition User.php:262
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition User.php:1776
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition User.php:1077
bool $mLocked
Definition User.php:282
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition User.php:3616
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition User.php:5617
isHidden()
Check if user account is hidden.
Definition User.php:2408
static array null $defOpt
Is the user an IP range?
Definition User.php:1754
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition User.php:726
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition User.php:3089
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition User.php:2757
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition User.php:2015
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition User.php:1593
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition User.php:1042
static getDefaultOption( $opt)
Get a given default option value.
Definition User.php:1818
const IGNORE_USER_RIGHTS
Definition User.php:78
getDatePreference()
Get the user's preferred date format.
Definition User.php:3492
string $mHash
Definition User.php:268
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition User.php:1202
static resetGetDefaultOptionsForTestsOnly()
Reset the process cache of default user options.
Definition User.php:1764
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition User.php:5174
array $mRights
Definition User.php:270
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition User.php:4265
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition User.php:3603
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition User.php:4682
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition User.php:3920
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition User.php:1475
setPassword( $str)
Set the password and reset the random token.
Definition User.php:2890
string $mBlockedby
Definition User.php:266
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition User.php:4793
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition User.php:4538
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition User.php:750
static getAllRights()
Get a list of all available permissions.
Definition User.php:5143
getNewtalk()
Check if the user has new messages.
Definition User.php:2566
getGroups()
Get the list of explicit group memberships this user has.
Definition User.php:3590
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition User.php:5451
validateCache( $timestamp)
Validate the cache for this account.
Definition User.php:2834
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition User.php:3869
makeUpdateConditions(IDatabase $db, array $conditions)
Builds update conditions.
Definition User.php:1678
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition User.php:1603
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition User.php:885
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition User.php:4850
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition User.php:4864
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:813
setOption( $oname, $val)
Set the given option for a user.
Definition User.php:3256
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition User.php:5551
static getRightDescription( $right)
Get the description of a given right.
Definition User.php:5412
getEditCount()
Get the user's edit count.
Definition User.php:3692
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition User.php:2491
const CHECK_USER_RIGHTS
Definition User.php:73
UserGroupMembership[] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition User.php:230
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition User.php:1627
clearSharedCache( $mode='refresh')
Clear user data from memcached.
Definition User.php:2776
isValidPassword( $password)
Is the input a valid password for this user?
Definition User.php:1147
array $mFormerGroups
Definition User.php:278
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition User.php:4496
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition User.php:4477
getFormerGroups()
Returns the groups the user has belonged to.
Definition User.php:3668
setRealName( $str)
Set the user's real name.
Definition User.php:3154
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition User.php:4972
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition User.php:652
int $mId
Cache variables.
Definition User.php:203
getTitleKey()
Get the user's name escaped by underscores.
Definition User.php:2558
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:967
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name.
Definition User.php:5214
getTouched()
Get the user touched timestamp.
Definition User.php:2846
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition User.php:4749
Block $mBlock
Definition User.php:292
isLocked()
Check if user account is locked.
Definition User.php:2393
array $mOptions
Definition User.php:286
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition User.php:1696
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition User.php:3951
setPasswordInternal( $str)
Actually set the password and such.
Definition User.php:2916
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition User.php:4932
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition User.php:2664
__toString()
Definition User.php:323
getUserPage()
Get this user's personal page title.
Definition User.php:4547
isIPRange()
Is the user an IP range?
Definition User.php:978
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition User.php:257
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition User.php:4775
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition User.php:4875
getBlockId()
If user is blocked, return the ID for the block.
Definition User.php:2333
isBot()
Definition User.php:3809
string $mRegistration
Definition User.php:226
__construct()
Lightweight constructor for an anonymous user.
Definition User.php:316
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition User.php:2724
getStubThreshold()
Get the user preferred stub threshold.
Definition User.php:3532
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition User.php:2132
string $mDatePreference
Definition User.php:264
static isValidUserName( $name)
Is the input a valid username?
Definition User.php:993
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition User.php:4694
getTalkPage()
Get this user's talk page title.
Definition User.php:4556
isLoggedIn()
Get whether the user is logged in.
Definition User.php:3793
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition User.php:905
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition User.php:522
getCacheKey(WANObjectCache $cache)
Definition User.php:499
saveSettings()
Save this user's settings into the database.
Definition User.php:4187
static $idCacheByName
Definition User.php:303
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition User.php:2637
getBlockFromCookieValue( $blockCookieVal)
Try to load a Block from an ID given in a cookie value.
Definition User.php:1945
static getGrantName( $grant)
Get the name of a given grant.
Definition User.php:5425
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition User.php:3826
getEmail()
Get the user's e-mail address.
Definition User.php:3052
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition User.php:5685
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition User.php:3044
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition User.php:3639
isBlockedFromUpload()
Get whether the user is blocked from using Special:Upload.
Definition User.php:4529
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition User.php:358
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition User.php:2315
getRights()
Get the permissions this user has.
Definition User.php:3547
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition User.php:4668
string $mEmailAuthenticated
Definition User.php:220
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition User.php:68
const TOKEN_LENGTH
@const int Number of characters in user_token field.
Definition User.php:52
isAllowedAll()
Definition User.php:3841
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition User.php:3024
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition User.php:2679
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition User.php:4300
initEditCountInternal()
Initialize user_editcount from data out of the revision table.
Definition User.php:5377
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition User.php:5463
doLogout()
Clear the user's session, and reset the instance cache.
Definition User.php:4151
setItemLoaded( $item)
Set that an item has been loaded.
Definition User.php:1359
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition User.php:5350
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition User.php:244
blockedFor()
If user is blocked, return the specified reason for the block.
Definition User.php:2324
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition User.php:3339
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition User.php:2289
int $mEditCount
Definition User.php:228
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition User.php:3305
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition User.php:5189
bool $mHideName
Definition User.php:284
static getImplicitGroups()
Get a list of implicit groups TODO: Should we deprecate this? It's trivial, but we don't want to enco...
Definition User.php:5162
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition User.php:5311
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition User.php:2278
string $mName
Definition User.php:205
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition User.php:2604
isAnon()
Get whether the user is anonymous.
Definition User.php:3801
setEmail( $str)
Set the user's e-mail address.
Definition User.php:3072
string $mBlockreason
Definition User.php:272
WebRequest $mRequest
Definition User.php:289
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition User.php:4576
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition User.php:5709
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition User.php:2306
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition User.php:624
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition User.php:4036
array $mImplicitGroups
Definition User.php:276
removeGroup( $group)
Remove the user from the given group.
Definition User.php:3763
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:49
The ContentHandler facility adds support for arbitrary content types on wiki pages
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
const SCHEMA_COMPAT_READ_NEW
Definition Defines.php:296
const NS_USER
Definition Defines.php:75
const NS_MAIN
Definition Defines.php:73
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:295
const SCHEMA_COMPAT_NEW
Definition Defines.php:300
const NS_USER_TALK
Definition Defines.php:76
this hook is for auditing only $req
Definition hooks.txt:979
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1802
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition hooks.txt:2818
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2843
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition hooks.txt:1991
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:181
either a plain
Definition hooks.txt:2054
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition hooks.txt:1266
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:1999
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition hooks.txt:2848
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:955
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition hooks.txt:783
null for the local wiki Added in
Definition hooks.txt:1588
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition hooks.txt:856
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:2004
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition hooks.txt:2003
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:271
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing we can concentrate it all in an extension file
Definition hooks.txt:106
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of or an object and a method hook function The function part of a third party developers and local administrators to define code that will be run at certain points in the mainline and to modify the data run by that mainline code Hooks can keep mainline code and make it easier to write extensions Hooks are a principled alternative to local patches for two options in MediaWiki One reverses the order of a title before displaying the article
Definition hooks.txt:33
return true to allow those checks to and false if checking is done & $user
Definition hooks.txt:1510
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1779
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for objects which can provide a MediaWiki context on request.
Interface for database access objects.
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Interface for objects representing user identity.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
insertId()
Get the inserted value of an auto-increment row.
$cache
Definition mcc.php:33
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
MediaWiki has optional support for a high distributed memory object caching system For general information on but for a larger site with heavy load
Definition memcached.txt:6
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26
$property
$params