MediaWiki REL1_32
User.php
Go to the documentation of this file.
1<?php
31use Wikimedia\IPSet;
32use Wikimedia\ScopedCallback;
36
51 const TOKEN_LENGTH = 32;
52
56 const INVALID_TOKEN = '*** INVALID ***';
57
63 const EDIT_TOKEN_SUFFIX = Token::SUFFIX;
64
68 const VERSION = 12;
69
75
79 const CHECK_USER_RIGHTS = true;
80
84 const IGNORE_USER_RIGHTS = false;
85
92 protected static $mCacheVars = [
93 // user table
94 'mId',
95 'mName',
96 'mRealName',
97 'mEmail',
98 'mTouched',
99 'mToken',
100 'mEmailAuthenticated',
101 'mEmailToken',
102 'mEmailTokenExpires',
103 'mRegistration',
104 'mEditCount',
105 // user_groups table
106 'mGroupMemberships',
107 // user_properties table
108 'mOptionOverrides',
109 // actor table
110 'mActorId',
111 ];
112
119 protected static $mCoreRights = [
120 'apihighlimits',
121 'applychangetags',
122 'autoconfirmed',
123 'autocreateaccount',
124 'autopatrol',
125 'bigdelete',
126 'block',
127 'blockemail',
128 'bot',
129 'browsearchive',
130 'changetags',
131 'createaccount',
132 'createpage',
133 'createtalk',
134 'delete',
135 'deletechangetags',
136 'deletedhistory',
137 'deletedtext',
138 'deletelogentry',
139 'deleterevision',
140 'edit',
141 'editcontentmodel',
142 'editinterface',
143 'editprotected',
144 'editmyoptions',
145 'editmyprivateinfo',
146 'editmyusercss',
147 'editmyuserjson',
148 'editmyuserjs',
149 'editmywatchlist',
150 'editsemiprotected',
151 'editsitecss',
152 'editsitejson',
153 'editsitejs',
154 'editusercss',
155 'edituserjson',
156 'edituserjs',
157 'hideuser',
158 'import',
159 'importupload',
160 'ipblock-exempt',
161 'managechangetags',
162 'markbotedits',
163 'mergehistory',
164 'minoredit',
165 'move',
166 'movefile',
167 'move-categorypages',
168 'move-rootuserpages',
169 'move-subpages',
170 'nominornewtalk',
171 'noratelimit',
172 'override-export-depth',
173 'pagelang',
174 'patrol',
175 'patrolmarks',
176 'protect',
177 'purge',
178 'read',
179 'reupload',
180 'reupload-own',
181 'reupload-shared',
182 'rollback',
183 'sendemail',
184 'siteadmin',
185 'suppressionlog',
186 'suppressredirect',
187 'suppressrevision',
188 'unblockself',
189 'undelete',
190 'unwatchedpages',
191 'upload',
192 'upload_by_url',
193 'userrights',
194 'userrights-interwiki',
195 'viewmyprivateinfo',
196 'viewmywatchlist',
197 'viewsuppressed',
198 'writeapi',
199 ];
200
204 protected static $mAllRights = false;
205
207 // @{
209 public $mId;
211 public $mName;
213 protected $mActorId;
216
218 public $mEmail;
220 public $mTouched;
222 protected $mQuickTouched;
224 protected $mToken;
228 protected $mEmailToken;
232 protected $mRegistration;
234 protected $mEditCount;
239 // @}
240
244 // @{
246
250 protected $mLoadedItems = [];
251 // @}
252
263 public $mFrom;
264
268 protected $mNewtalk;
274 protected $mHash;
276 public $mRights;
278 protected $mBlockreason;
284 protected $mFormerGroups;
286 protected $mGlobalBlock;
288 protected $mLocked;
292 public $mOptions;
293
295 private $mRequest;
296
298 public $mBlock;
299
302
305
307 protected $queryFlagsUsed = self::READ_NORMAL;
308
309 public static $idCacheByName = [];
310
322 public function __construct() {
323 $this->clearInstanceCache( 'defaults' );
324 }
325
329 public function __toString() {
330 return (string)$this->getName();
331 }
332
347 public function isSafeToLoad() {
348 global $wgFullyInitialised;
349
350 // The user is safe to load if:
351 // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
352 // * mLoadedItems === true (already loaded)
353 // * mFrom !== 'session' (sessions not involved at all)
354
355 return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
356 $this->mLoadedItems === true || $this->mFrom !== 'session';
357 }
358
364 public function load( $flags = self::READ_NORMAL ) {
365 global $wgFullyInitialised;
366
367 if ( $this->mLoadedItems === true ) {
368 return;
369 }
370
371 // Set it now to avoid infinite recursion in accessors
372 $oldLoadedItems = $this->mLoadedItems;
373 $this->mLoadedItems = true;
374 $this->queryFlagsUsed = $flags;
375
376 // If this is called too early, things are likely to break.
377 if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
378 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
379 ->warning( 'User::loadFromSession called before the end of Setup.php', [
380 'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
381 ] );
382 $this->loadDefaults();
383 $this->mLoadedItems = $oldLoadedItems;
384 return;
385 }
386
387 switch ( $this->mFrom ) {
388 case 'defaults':
389 $this->loadDefaults();
390 break;
391 case 'name':
392 // Make sure this thread sees its own changes
393 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
394 if ( $lb->hasOrMadeRecentMasterChanges() ) {
395 $flags |= self::READ_LATEST;
396 $this->queryFlagsUsed = $flags;
397 }
398
399 $this->mId = self::idFromName( $this->mName, $flags );
400 if ( !$this->mId ) {
401 // Nonexistent user placeholder object
402 $this->loadDefaults( $this->mName );
403 } else {
404 $this->loadFromId( $flags );
405 }
406 break;
407 case 'id':
408 // Make sure this thread sees its own changes, if the ID isn't 0
409 if ( $this->mId != 0 ) {
410 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
411 if ( $lb->hasOrMadeRecentMasterChanges() ) {
412 $flags |= self::READ_LATEST;
413 $this->queryFlagsUsed = $flags;
414 }
415 }
416
417 $this->loadFromId( $flags );
418 break;
419 case 'actor':
420 // Make sure this thread sees its own changes
421 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
422 if ( $lb->hasOrMadeRecentMasterChanges() ) {
423 $flags |= self::READ_LATEST;
424 $this->queryFlagsUsed = $flags;
425 }
426
427 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
428 $row = wfGetDB( $index )->selectRow(
429 'actor',
430 [ 'actor_user', 'actor_name' ],
431 [ 'actor_id' => $this->mActorId ],
432 __METHOD__,
434 );
435
436 if ( !$row ) {
437 // Ugh.
438 $this->loadDefaults();
439 } elseif ( $row->actor_user ) {
440 $this->mId = $row->actor_user;
441 $this->loadFromId( $flags );
442 } else {
443 $this->loadDefaults( $row->actor_name );
444 }
445 break;
446 case 'session':
447 if ( !$this->loadFromSession() ) {
448 // Loading from session failed. Load defaults.
449 $this->loadDefaults();
450 }
451 Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
452 break;
453 default:
454 throw new UnexpectedValueException(
455 "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
456 }
457 }
458
464 public function loadFromId( $flags = self::READ_NORMAL ) {
465 if ( $this->mId == 0 ) {
466 // Anonymous users are not in the database (don't need cache)
467 $this->loadDefaults();
468 return false;
469 }
470
471 // Try cache (unless this needs data from the master DB).
472 // NOTE: if this thread called saveSettings(), the cache was cleared.
473 $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
474 if ( $latest ) {
475 if ( !$this->loadFromDatabase( $flags ) ) {
476 // Can't load from ID
477 return false;
478 }
479 } else {
480 $this->loadFromCache();
481 }
482
483 $this->mLoadedItems = true;
484 $this->queryFlagsUsed = $flags;
485
486 return true;
487 }
488
494 public static function purge( $wikiId, $userId ) {
495 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
496 $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
497 $cache->delete( $key );
498 }
499
505 protected function getCacheKey( WANObjectCache $cache ) {
506 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
507 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
508
509 return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
510 }
511
518 $id = $this->getId();
519
520 return $id ? [ $this->getCacheKey( $cache ) ] : [];
521 }
522
529 protected function loadFromCache() {
530 $cache = ObjectCache::getMainWANInstance();
531 $data = $cache->getWithSetCallback(
532 $this->getCacheKey( $cache ),
533 $cache::TTL_HOUR,
534 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
535 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
536 wfDebug( "User: cache miss for user {$this->mId}\n" );
537
538 $this->loadFromDatabase( self::READ_NORMAL );
539 $this->loadGroups();
540 $this->loadOptions();
541
542 $data = [];
543 foreach ( self::$mCacheVars as $name ) {
544 $data[$name] = $this->$name;
545 }
546
547 $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
548
549 // if a user group membership is about to expire, the cache needs to
550 // expire at that time (T163691)
551 foreach ( $this->mGroupMemberships as $ugm ) {
552 if ( $ugm->getExpiry() ) {
553 $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
554 if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
555 $ttl = $secondsUntilExpiry;
556 }
557 }
558 }
559
560 return $data;
561 },
562 [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
563 );
564
565 // Restore from cache
566 foreach ( self::$mCacheVars as $name ) {
567 $this->$name = $data[$name];
568 }
569
570 return true;
571 }
572
574 // @{
575
592 public static function newFromName( $name, $validate = 'valid' ) {
593 if ( $validate === true ) {
594 $validate = 'valid';
595 }
596 $name = self::getCanonicalName( $name, $validate );
597 if ( $name === false ) {
598 return false;
599 } else {
600 // Create unloaded user object
601 $u = new User;
602 $u->mName = $name;
603 $u->mFrom = 'name';
604 $u->setItemLoaded( 'name' );
605 return $u;
606 }
607 }
608
615 public static function newFromId( $id ) {
616 $u = new User;
617 $u->mId = $id;
618 $u->mFrom = 'id';
619 $u->setItemLoaded( 'id' );
620 return $u;
621 }
622
630 public static function newFromActorId( $id ) {
632
633 // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
634 // but it does little harm and might be needed for write callers loading a User.
636 throw new BadMethodCallException(
637 'Cannot use ' . __METHOD__
638 . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
639 );
640 }
641
642 $u = new User;
643 $u->mActorId = $id;
644 $u->mFrom = 'actor';
645 $u->setItemLoaded( 'actor' );
646 return $u;
647 }
648
658 public static function newFromIdentity( UserIdentity $identity ) {
659 if ( $identity instanceof User ) {
660 return $identity;
661 }
662
663 return self::newFromAnyId(
664 $identity->getId() === 0 ? null : $identity->getId(),
665 $identity->getName() === '' ? null : $identity->getName(),
666 $identity->getActorId() === 0 ? null : $identity->getActorId()
667 );
668 }
669
682 public static function newFromAnyId( $userId, $userName, $actorId ) {
684
685 $user = new User;
686 $user->mFrom = 'defaults';
687
688 // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
689 // but it does little harm and might be needed for write callers loading a User.
690 if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
691 $user->mActorId = (int)$actorId;
692 if ( $user->mActorId !== 0 ) {
693 $user->mFrom = 'actor';
694 }
695 $user->setItemLoaded( 'actor' );
696 }
697
698 if ( $userName !== null && $userName !== '' ) {
699 $user->mName = $userName;
700 $user->mFrom = 'name';
701 $user->setItemLoaded( 'name' );
702 }
703
704 if ( $userId !== null ) {
705 $user->mId = (int)$userId;
706 if ( $user->mId !== 0 ) {
707 $user->mFrom = 'id';
708 }
709 $user->setItemLoaded( 'id' );
710 }
711
712 if ( $user->mFrom === 'defaults' ) {
713 throw new InvalidArgumentException(
714 'Cannot create a user with no name, no ID, and no actor ID'
715 );
716 }
717
718 return $user;
719 }
720
732 public static function newFromConfirmationCode( $code, $flags = 0 ) {
733 $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
734 ? wfGetDB( DB_MASTER )
735 : wfGetDB( DB_REPLICA );
736
737 $id = $db->selectField(
738 'user',
739 'user_id',
740 [
741 'user_email_token' => md5( $code ),
742 'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
743 ]
744 );
745
746 return $id ? self::newFromId( $id ) : null;
747 }
748
756 public static function newFromSession( WebRequest $request = null ) {
757 $user = new User;
758 $user->mFrom = 'session';
759 $user->mRequest = $request;
760 return $user;
761 }
762
778 public static function newFromRow( $row, $data = null ) {
779 $user = new User;
780 $user->loadFromRow( $row, $data );
781 return $user;
782 }
783
819 public static function newSystemUser( $name, $options = [] ) {
820 $options += [
821 'validate' => 'valid',
822 'create' => true,
823 'steal' => false,
824 ];
825
826 $name = self::getCanonicalName( $name, $options['validate'] );
827 if ( $name === false ) {
828 return null;
829 }
830
832 $userQuery = self::getQueryInfo();
833 $row = $dbr->selectRow(
834 $userQuery['tables'],
835 $userQuery['fields'],
836 [ 'user_name' => $name ],
837 __METHOD__,
838 [],
839 $userQuery['joins']
840 );
841 if ( !$row ) {
842 // Try the master database...
843 $dbw = wfGetDB( DB_MASTER );
844 $row = $dbw->selectRow(
845 $userQuery['tables'],
846 $userQuery['fields'],
847 [ 'user_name' => $name ],
848 __METHOD__,
849 [],
850 $userQuery['joins']
851 );
852 }
853
854 if ( !$row ) {
855 // No user. Create it?
856 return $options['create']
857 ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
858 : null;
859 }
860
861 $user = self::newFromRow( $row );
862
863 // A user is considered to exist as a non-system user if it can
864 // authenticate, or has an email set, or has a non-invalid token.
865 if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
866 AuthManager::singleton()->userCanAuthenticate( $name )
867 ) {
868 // User exists. Steal it?
869 if ( !$options['steal'] ) {
870 return null;
871 }
872
873 AuthManager::singleton()->revokeAccessForUser( $name );
874
875 $user->invalidateEmail();
876 $user->mToken = self::INVALID_TOKEN;
877 $user->saveSettings();
878 SessionManager::singleton()->preventSessionsForUser( $user->getName() );
879 }
880
881 return $user;
882 }
883
884 // @}
885
891 public static function whoIs( $id ) {
892 return UserCache::singleton()->getProp( $id, 'name' );
893 }
894
901 public static function whoIsReal( $id ) {
902 return UserCache::singleton()->getProp( $id, 'real_name' );
903 }
904
911 public static function idFromName( $name, $flags = self::READ_NORMAL ) {
912 $nt = Title::makeTitleSafe( NS_USER, $name );
913 if ( is_null( $nt ) ) {
914 // Illegal name
915 return null;
916 }
917
918 if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) {
919 return self::$idCacheByName[$name];
920 }
921
922 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
923 $db = wfGetDB( $index );
924
925 $s = $db->selectRow(
926 'user',
927 [ 'user_id' ],
928 [ 'user_name' => $nt->getText() ],
929 __METHOD__,
931 );
932
933 if ( $s === false ) {
934 $result = null;
935 } else {
936 $result = $s->user_id;
937 }
938
939 self::$idCacheByName[$name] = $result;
940
941 if ( count( self::$idCacheByName ) > 1000 ) {
942 self::$idCacheByName = [];
943 }
944
945 return $result;
946 }
947
951 public static function resetIdByNameCache() {
952 self::$idCacheByName = [];
953 }
954
971 public static function isIP( $name ) {
972 return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
973 || IP::isIPv6( $name );
974 }
975
982 public function isIPRange() {
983 return IP::isValidRange( $this->mName );
984 }
985
997 public static function isValidUserName( $name ) {
998 global $wgMaxNameChars;
999
1000 if ( $name == ''
1001 || self::isIP( $name )
1002 || strpos( $name, '/' ) !== false
1003 || strlen( $name ) > $wgMaxNameChars
1004 || $name != MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name )
1005 ) {
1006 return false;
1007 }
1008
1009 // Ensure that the name can't be misresolved as a different title,
1010 // such as with extra namespace keys at the start.
1011 $parsed = Title::newFromText( $name );
1012 if ( is_null( $parsed )
1013 || $parsed->getNamespace()
1014 || strcmp( $name, $parsed->getPrefixedText() ) ) {
1015 return false;
1016 }
1017
1018 // Check an additional blacklist of troublemaker characters.
1019 // Should these be merged into the title char list?
1020 $unicodeBlacklist = '/[' .
1021 '\x{0080}-\x{009f}' . # iso-8859-1 control chars
1022 '\x{00a0}' . # non-breaking space
1023 '\x{2000}-\x{200f}' . # various whitespace
1024 '\x{2028}-\x{202f}' . # breaks and control chars
1025 '\x{3000}' . # ideographic space
1026 '\x{e000}-\x{f8ff}' . # private use
1027 ']/u';
1028 if ( preg_match( $unicodeBlacklist, $name ) ) {
1029 return false;
1030 }
1031
1032 return true;
1033 }
1034
1046 public static function isUsableName( $name ) {
1047 global $wgReservedUsernames;
1048 // Must be a valid username, obviously ;)
1049 if ( !self::isValidUserName( $name ) ) {
1050 return false;
1051 }
1052
1053 static $reservedUsernames = false;
1054 if ( !$reservedUsernames ) {
1055 $reservedUsernames = $wgReservedUsernames;
1056 Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
1057 }
1058
1059 // Certain names may be reserved for batch processes.
1060 foreach ( $reservedUsernames as $reserved ) {
1061 if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
1062 $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->plain();
1063 }
1064 if ( $reserved == $name ) {
1065 return false;
1066 }
1067 }
1068 return true;
1069 }
1070
1081 public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
1082 if ( $groups === [] ) {
1084 }
1085
1086 $groups = array_unique( (array)$groups );
1087 $limit = min( 5000, $limit );
1088
1089 $conds = [ 'ug_group' => $groups ];
1090 if ( $after !== null ) {
1091 $conds[] = 'ug_user > ' . (int)$after;
1092 }
1093
1094 $dbr = wfGetDB( DB_REPLICA );
1095 $ids = $dbr->selectFieldValues(
1096 'user_groups',
1097 'ug_user',
1098 $conds,
1099 __METHOD__,
1100 [
1101 'DISTINCT' => true,
1102 'ORDER BY' => 'ug_user',
1103 'LIMIT' => $limit,
1104 ]
1105 ) ?: [];
1106 return UserArray::newFromIDs( $ids );
1107 }
1108
1121 public static function isCreatableName( $name ) {
1123
1124 // Ensure that the username isn't longer than 235 bytes, so that
1125 // (at least for the builtin skins) user javascript and css files
1126 // will work. (T25080)
1127 if ( strlen( $name ) > 235 ) {
1128 wfDebugLog( 'username', __METHOD__ .
1129 ": '$name' invalid due to length" );
1130 return false;
1131 }
1132
1133 // Preg yells if you try to give it an empty string
1134 if ( $wgInvalidUsernameCharacters !== '' ) {
1135 if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
1136 wfDebugLog( 'username', __METHOD__ .
1137 ": '$name' invalid due to wgInvalidUsernameCharacters" );
1138 return false;
1139 }
1140 }
1141
1142 return self::isUsableName( $name );
1143 }
1144
1151 public function isValidPassword( $password ) {
1152 // simple boolean wrapper for getPasswordValidity
1153 return $this->getPasswordValidity( $password ) === true;
1154 }
1155
1162 public function getPasswordValidity( $password ) {
1163 $result = $this->checkPasswordValidity( $password );
1164 if ( $result->isGood() ) {
1165 return true;
1166 } else {
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 return $messages;
1178 }
1179 }
1180
1198 public function checkPasswordValidity( $password ) {
1199 global $wgPasswordPolicy;
1200
1201 $upp = new UserPasswordPolicy(
1202 $wgPasswordPolicy['policies'],
1203 $wgPasswordPolicy['checks']
1204 );
1205
1206 $status = Status::newGood();
1207 $result = false; // init $result to false for the internal checks
1208
1209 if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1210 $status->error( $result );
1211 return $status;
1212 }
1213
1214 if ( $result === false ) {
1215 $status->merge( $upp->checkUserPassword( $this, $password ) );
1216 return $status;
1217 } elseif ( $result === true ) {
1218 return $status;
1219 } else {
1220 $status->error( $result );
1221 return $status; // the isValidPassword hook set a string $result and returned true
1222 }
1223 }
1224
1238 public static function getCanonicalName( $name, $validate = 'valid' ) {
1239 // Force usernames to capital
1240 $name = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $name );
1241
1242 # Reject names containing '#'; these will be cleaned up
1243 # with title normalisation, but then it's too late to
1244 # check elsewhere
1245 if ( strpos( $name, '#' ) !== false ) {
1246 return false;
1247 }
1248
1249 // Clean up name according to title rules,
1250 // but only when validation is requested (T14654)
1251 $t = ( $validate !== false ) ?
1252 Title::newFromText( $name, NS_USER ) : Title::makeTitle( NS_USER, $name );
1253 // Check for invalid titles
1254 if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1255 return false;
1256 }
1257
1258 // Reject various classes of invalid names
1259 $name = AuthManager::callLegacyAuthPlugin(
1260 'getCanonicalName', [ $t->getText() ], $t->getText()
1261 );
1262
1263 switch ( $validate ) {
1264 case false:
1265 break;
1266 case 'valid':
1267 if ( !self::isValidUserName( $name ) ) {
1268 $name = false;
1269 }
1270 break;
1271 case 'usable':
1272 if ( !self::isUsableName( $name ) ) {
1273 $name = false;
1274 }
1275 break;
1276 case 'creatable':
1277 if ( !self::isCreatableName( $name ) ) {
1278 $name = false;
1279 }
1280 break;
1281 default:
1282 throw new InvalidArgumentException(
1283 'Invalid parameter value for $validate in ' . __METHOD__ );
1284 }
1285 return $name;
1286 }
1287
1294 public static function randomPassword() {
1296 return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1297 }
1298
1307 public function loadDefaults( $name = false ) {
1308 $this->mId = 0;
1309 $this->mName = $name;
1310 $this->mActorId = null;
1311 $this->mRealName = '';
1312 $this->mEmail = '';
1313 $this->mOptionOverrides = null;
1314 $this->mOptionsLoaded = false;
1315
1316 $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1317 ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1318 if ( $loggedOut !== 0 ) {
1319 $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1320 } else {
1321 $this->mTouched = '1'; # Allow any pages to be cached
1322 }
1323
1324 $this->mToken = null; // Don't run cryptographic functions till we need a token
1325 $this->mEmailAuthenticated = null;
1326 $this->mEmailToken = '';
1327 $this->mEmailTokenExpires = null;
1328 $this->mRegistration = wfTimestamp( TS_MW );
1329 $this->mGroupMemberships = [];
1330
1331 Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1332 }
1333
1346 public function isItemLoaded( $item, $all = 'all' ) {
1347 return ( $this->mLoadedItems === true && $all === 'all' ) ||
1348 ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1349 }
1350
1356 protected function setItemLoaded( $item ) {
1357 if ( is_array( $this->mLoadedItems ) ) {
1358 $this->mLoadedItems[$item] = true;
1359 }
1360 }
1361
1367 private function loadFromSession() {
1368 // Deprecated hook
1369 $result = null;
1370 Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1371 if ( $result !== null ) {
1372 return $result;
1373 }
1374
1375 // MediaWiki\Session\Session already did the necessary authentication of the user
1376 // returned here, so just use it if applicable.
1377 $session = $this->getRequest()->getSession();
1378 $user = $session->getUser();
1379 if ( $user->isLoggedIn() ) {
1380 $this->loadFromUserObject( $user );
1381 if ( $user->isBlocked() ) {
1382 // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1383 // every session load, because an autoblocked editor might not edit again from the same
1384 // IP address after being blocked.
1385 $this->trackBlockWithCookie();
1386 }
1387
1388 // Other code expects these to be set in the session, so set them.
1389 $session->set( 'wsUserID', $this->getId() );
1390 $session->set( 'wsUserName', $this->getName() );
1391 $session->set( 'wsToken', $this->getToken() );
1392
1393 return true;
1394 }
1395
1396 return false;
1397 }
1398
1402 public function trackBlockWithCookie() {
1403 $block = $this->getBlock();
1404 if ( $block && $this->getRequest()->getCookie( 'BlockID' ) === null ) {
1405 $config = RequestContext::getMain()->getConfig();
1406 $shouldSetCookie = false;
1407
1408 if ( $this->isAnon() && $config->get( 'CookieSetOnIpBlock' ) ) {
1409 // If user is logged-out, set a cookie to track the Block
1410 $shouldSetCookie = in_array( $block->getType(), [
1412 ] );
1413 if ( $shouldSetCookie ) {
1414 $block->setCookie( $this->getRequest()->response() );
1415
1416 // temporary measure the use of cookies on ip blocks
1417 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1418 $stats->increment( 'block.ipblock.setCookie.success' );
1419 }
1420 } elseif ( $this->isLoggedIn() && $config->get( 'CookieSetOnAutoblock' ) ) {
1421 $shouldSetCookie = $block->getType() === Block::TYPE_USER && $block->isAutoblocking();
1422 if ( $shouldSetCookie ) {
1423 $block->setCookie( $this->getRequest()->response() );
1424 }
1425 }
1426 }
1427 }
1428
1436 public function loadFromDatabase( $flags = self::READ_LATEST ) {
1437 // Paranoia
1438 $this->mId = intval( $this->mId );
1439
1440 if ( !$this->mId ) {
1441 // Anonymous users are not in the database
1442 $this->loadDefaults();
1443 return false;
1444 }
1445
1446 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
1447 $db = wfGetDB( $index );
1448
1449 $userQuery = self::getQueryInfo();
1450 $s = $db->selectRow(
1451 $userQuery['tables'],
1452 $userQuery['fields'],
1453 [ 'user_id' => $this->mId ],
1454 __METHOD__,
1455 $options,
1456 $userQuery['joins']
1457 );
1458
1459 $this->queryFlagsUsed = $flags;
1460 Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1461
1462 if ( $s !== false ) {
1463 // Initialise user table data
1464 $this->loadFromRow( $s );
1465 $this->mGroupMemberships = null; // deferred
1466 $this->getEditCount(); // revalidation for nulls
1467 return true;
1468 } else {
1469 // Invalid user_id
1470 $this->mId = 0;
1471 $this->loadDefaults();
1472 return false;
1473 }
1474 }
1475
1488 protected function loadFromRow( $row, $data = null ) {
1490
1491 if ( !is_object( $row ) ) {
1492 throw new InvalidArgumentException( '$row must be an object' );
1493 }
1494
1495 $all = true;
1496
1497 $this->mGroupMemberships = null; // deferred
1498
1499 // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
1500 // but it does little harm and might be needed for write callers loading a User.
1502 if ( isset( $row->actor_id ) ) {
1503 $this->mActorId = (int)$row->actor_id;
1504 if ( $this->mActorId !== 0 ) {
1505 $this->mFrom = 'actor';
1506 }
1507 $this->setItemLoaded( 'actor' );
1508 } else {
1509 $all = false;
1510 }
1511 }
1512
1513 if ( isset( $row->user_name ) && $row->user_name !== '' ) {
1514 $this->mName = $row->user_name;
1515 $this->mFrom = 'name';
1516 $this->setItemLoaded( 'name' );
1517 } else {
1518 $all = false;
1519 }
1520
1521 if ( isset( $row->user_real_name ) ) {
1522 $this->mRealName = $row->user_real_name;
1523 $this->setItemLoaded( 'realname' );
1524 } else {
1525 $all = false;
1526 }
1527
1528 if ( isset( $row->user_id ) ) {
1529 $this->mId = intval( $row->user_id );
1530 if ( $this->mId !== 0 ) {
1531 $this->mFrom = 'id';
1532 }
1533 $this->setItemLoaded( 'id' );
1534 } else {
1535 $all = false;
1536 }
1537
1538 if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
1539 self::$idCacheByName[$row->user_name] = $row->user_id;
1540 }
1541
1542 if ( isset( $row->user_editcount ) ) {
1543 $this->mEditCount = $row->user_editcount;
1544 } else {
1545 $all = false;
1546 }
1547
1548 if ( isset( $row->user_touched ) ) {
1549 $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1550 } else {
1551 $all = false;
1552 }
1553
1554 if ( isset( $row->user_token ) ) {
1555 // The definition for the column is binary(32), so trim the NULs
1556 // that appends. The previous definition was char(32), so trim
1557 // spaces too.
1558 $this->mToken = rtrim( $row->user_token, " \0" );
1559 if ( $this->mToken === '' ) {
1560 $this->mToken = null;
1561 }
1562 } else {
1563 $all = false;
1564 }
1565
1566 if ( isset( $row->user_email ) ) {
1567 $this->mEmail = $row->user_email;
1568 $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1569 $this->mEmailToken = $row->user_email_token;
1570 $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1571 $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1572 } else {
1573 $all = false;
1574 }
1575
1576 if ( $all ) {
1577 $this->mLoadedItems = true;
1578 }
1579
1580 if ( is_array( $data ) ) {
1581 if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1582 if ( !count( $data['user_groups'] ) ) {
1583 $this->mGroupMemberships = [];
1584 } else {
1585 $firstGroup = reset( $data['user_groups'] );
1586 if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1587 $this->mGroupMemberships = [];
1588 foreach ( $data['user_groups'] as $row ) {
1589 $ugm = UserGroupMembership::newFromRow( (object)$row );
1590 $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1591 }
1592 }
1593 }
1594 }
1595 if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1596 $this->loadOptions( $data['user_properties'] );
1597 }
1598 }
1599 }
1600
1606 protected function loadFromUserObject( $user ) {
1607 $user->load();
1608 foreach ( self::$mCacheVars as $var ) {
1609 $this->$var = $user->$var;
1610 }
1611 }
1612
1616 private function loadGroups() {
1617 if ( is_null( $this->mGroupMemberships ) ) {
1618 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1619 ? wfGetDB( DB_MASTER )
1620 : wfGetDB( DB_REPLICA );
1621 $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1622 $this->mId, $db );
1623 }
1624 }
1625
1640 public function addAutopromoteOnceGroups( $event ) {
1642
1643 if ( wfReadOnly() || !$this->getId() ) {
1644 return [];
1645 }
1646
1647 $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1648 if ( !count( $toPromote ) ) {
1649 return [];
1650 }
1651
1652 if ( !$this->checkAndSetTouched() ) {
1653 return []; // raced out (bug T48834)
1654 }
1655
1656 $oldGroups = $this->getGroups(); // previous groups
1657 $oldUGMs = $this->getGroupMemberships();
1658 foreach ( $toPromote as $group ) {
1659 $this->addGroup( $group );
1660 }
1661 $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1662 $newUGMs = $this->getGroupMemberships();
1663
1664 // update groups in external authentication database
1665 Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
1666 AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1667
1668 $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1669 $logEntry->setPerformer( $this );
1670 $logEntry->setTarget( $this->getUserPage() );
1671 $logEntry->setParameters( [
1672 '4::oldgroups' => $oldGroups,
1673 '5::newgroups' => $newGroups,
1674 ] );
1675 $logid = $logEntry->insert();
1677 $logEntry->publish( $logid );
1678 }
1679
1680 return $toPromote;
1681 }
1682
1692 protected function makeUpdateConditions( Database $db, array $conditions ) {
1693 if ( $this->mTouched ) {
1694 // CAS check: only update if the row wasn't changed sicne it was loaded.
1695 $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1696 }
1697
1698 return $conditions;
1699 }
1700
1710 protected function checkAndSetTouched() {
1711 $this->load();
1712
1713 if ( !$this->mId ) {
1714 return false; // anon
1715 }
1716
1717 // Get a new user_touched that is higher than the old one
1718 $newTouched = $this->newTouchedTimestamp();
1719
1720 $dbw = wfGetDB( DB_MASTER );
1721 $dbw->update( 'user',
1722 [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1723 $this->makeUpdateConditions( $dbw, [
1724 'user_id' => $this->mId,
1725 ] ),
1726 __METHOD__
1727 );
1728 $success = ( $dbw->affectedRows() > 0 );
1729
1730 if ( $success ) {
1731 $this->mTouched = $newTouched;
1732 $this->clearSharedCache();
1733 } else {
1734 // Clears on failure too since that is desired if the cache is stale
1735 $this->clearSharedCache( 'refresh' );
1736 }
1737
1738 return $success;
1739 }
1740
1748 public function clearInstanceCache( $reloadFrom = false ) {
1749 $this->mNewtalk = -1;
1750 $this->mDatePreference = null;
1751 $this->mBlockedby = -1; # Unset
1752 $this->mHash = false;
1753 $this->mRights = null;
1754 $this->mEffectiveGroups = null;
1755 $this->mImplicitGroups = null;
1756 $this->mGroupMemberships = null;
1757 $this->mOptions = null;
1758 $this->mOptionsLoaded = false;
1759 $this->mEditCount = null;
1760
1761 if ( $reloadFrom ) {
1762 $this->mLoadedItems = [];
1763 $this->mFrom = $reloadFrom;
1764 }
1765 }
1766
1773 public static function getDefaultOptions() {
1775
1776 static $defOpt = null;
1777 static $defOptLang = null;
1778
1779 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1780 if ( $defOpt !== null && $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 $defOpt;
1785 }
1786
1787 $defOpt = $wgDefaultUserOptions;
1788 // Default language setting
1789 $defOptLang = $contLang->getCode();
1790 $defOpt['language'] = $defOptLang;
1791 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1792 if ( $langCode === $contLang->getCode() ) {
1793 $defOpt['variant'] = $langCode;
1794 } else {
1795 $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 $defOpt['searchNs' . $nsnum] = (bool)$val;
1804 }
1805 $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1806
1807 Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1808
1809 return $defOpt;
1810 }
1811
1818 public static function getDefaultOption( $opt ) {
1819 $defOpts = self::getDefaultOptions();
1820 if ( isset( $defOpts[$opt] ) ) {
1821 return $defOpts[$opt];
1822 } else {
1823 return null;
1824 }
1825 }
1826
1833 private function getBlockedStatus( $bFromSlave = true ) {
1835
1836 if ( -1 != $this->mBlockedby ) {
1837 return;
1838 }
1839
1840 wfDebug( __METHOD__ . ": checking...\n" );
1841
1842 // Initialize data...
1843 // Otherwise something ends up stomping on $this->mBlockedby when
1844 // things get lazy-loaded later, causing false positive block hits
1845 // due to -1 !== 0. Probably session-related... Nothing should be
1846 // overwriting mBlockedby, surely?
1847 $this->load();
1848
1849 # We only need to worry about passing the IP address to the Block generator if the
1850 # user is not immune to autoblocks/hardblocks, and they are the current user so we
1851 # know which IP address they're actually coming from
1852 $ip = null;
1853 $sessionUser = RequestContext::getMain()->getUser();
1854 // the session user is set up towards the end of Setup.php. Until then,
1855 // assume it's a logged-out user.
1856 $globalUserName = $sessionUser->isSafeToLoad()
1857 ? $sessionUser->getName()
1858 : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1859 if ( $this->getName() === $globalUserName && !$this->isAllowed( 'ipblock-exempt' ) ) {
1860 $ip = $this->getRequest()->getIP();
1861 }
1862
1863 // User/IP blocking
1864 $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1865
1866 // Cookie blocking
1867 if ( !$block instanceof Block ) {
1868 $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
1869 }
1870
1871 // Proxy blocking
1872 if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1873 // Local list
1874 if ( self::isLocallyBlockedProxy( $ip ) ) {
1875 $block = new Block( [
1876 'byText' => wfMessage( 'proxyblocker' )->text(),
1877 'reason' => wfMessage( 'proxyblockreason' )->plain(),
1878 'address' => $ip,
1879 'systemBlock' => 'proxy',
1880 ] );
1881 } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1882 $block = new Block( [
1883 'byText' => wfMessage( 'sorbs' )->text(),
1884 'reason' => wfMessage( 'sorbsreason' )->plain(),
1885 'address' => $ip,
1886 'systemBlock' => 'dnsbl',
1887 ] );
1888 }
1889 }
1890
1891 // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
1892 if ( !$block instanceof Block
1894 && $ip !== null
1895 && !in_array( $ip, $wgProxyWhitelist )
1896 ) {
1897 $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1898 $xff = array_map( 'trim', explode( ',', $xff ) );
1899 $xff = array_diff( $xff, [ $ip ] );
1900 $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1901 $block = Block::chooseBlock( $xffblocks, $xff );
1902 if ( $block instanceof Block ) {
1903 # Mangle the reason to alert the user that the block
1904 # originated from matching the X-Forwarded-For header.
1905 $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->plain();
1906 }
1907 }
1908
1909 if ( !$block instanceof Block
1910 && $ip !== null
1911 && $this->isAnon()
1912 && IP::isInRanges( $ip, $wgSoftBlockRanges )
1913 ) {
1914 $block = new Block( [
1915 'address' => $ip,
1916 'byText' => 'MediaWiki default',
1917 'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
1918 'anonOnly' => true,
1919 'systemBlock' => 'wgSoftBlockRanges',
1920 ] );
1921 }
1922
1923 if ( $block instanceof Block ) {
1924 wfDebug( __METHOD__ . ": Found block.\n" );
1925 $this->mBlock = $block;
1926 $this->mBlockedby = $block->getByName();
1927 $this->mBlockreason = $block->mReason;
1928 $this->mHideName = $block->mHideName;
1929 $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1930 } else {
1931 $this->mBlock = null;
1932 $this->mBlockedby = '';
1933 $this->mBlockreason = '';
1934 $this->mHideName = 0;
1935 $this->mAllowUsertalk = false;
1936 }
1937
1938 // Avoid PHP 7.1 warning of passing $this by reference
1939 $thisUser = $this;
1940 // Extensions
1941 Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
1942 }
1943
1949 protected function getBlockFromCookieValue( $blockCookieVal ) {
1950 // Make sure there's something to check. The cookie value must start with a number.
1951 if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
1952 return false;
1953 }
1954 // Load the Block from the ID in the cookie.
1955 $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
1956 if ( $blockCookieId !== null ) {
1957 // An ID was found in the cookie.
1958 $tmpBlock = Block::newFromID( $blockCookieId );
1959 if ( $tmpBlock instanceof Block ) {
1960 $config = RequestContext::getMain()->getConfig();
1961
1962 switch ( $tmpBlock->getType() ) {
1963 case Block::TYPE_USER:
1964 $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
1965 $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
1966 break;
1967 case Block::TYPE_IP:
1968 case Block::TYPE_RANGE:
1969 // If block is type IP or IP range, load only if user is not logged in (T152462)
1970 $blockIsValid = !$tmpBlock->isExpired() && !$this->isLoggedIn();
1971 $useBlockCookie = ( $config->get( 'CookieSetOnIpBlock' ) === true );
1972 break;
1973 default:
1974 $blockIsValid = false;
1975 $useBlockCookie = false;
1976 }
1977
1978 if ( $blockIsValid && $useBlockCookie ) {
1979 // Use the block.
1980 return $tmpBlock;
1981 } else {
1982 // If the block is not valid, remove the cookie.
1983 Block::clearCookie( $this->getRequest()->response() );
1984 }
1985 } else {
1986 // If the block doesn't exist, remove the cookie.
1987 Block::clearCookie( $this->getRequest()->response() );
1988 }
1989 }
1990 return false;
1991 }
1992
2000 public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
2002
2003 if ( !$wgEnableDnsBlacklist ) {
2004 return false;
2005 }
2006
2007 if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
2008 return false;
2009 }
2010
2011 return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
2012 }
2013
2021 public function inDnsBlacklist( $ip, $bases ) {
2022 $found = false;
2023 // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
2024 if ( IP::isIPv4( $ip ) ) {
2025 // Reverse IP, T23255
2026 $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
2027
2028 foreach ( (array)$bases as $base ) {
2029 // Make hostname
2030 // If we have an access key, use that too (ProjectHoneypot, etc.)
2031 $basename = $base;
2032 if ( is_array( $base ) ) {
2033 if ( count( $base ) >= 2 ) {
2034 // Access key is 1, base URL is 0
2035 $host = "{$base[1]}.$ipReversed.{$base[0]}";
2036 } else {
2037 $host = "$ipReversed.{$base[0]}";
2038 }
2039 $basename = $base[0];
2040 } else {
2041 $host = "$ipReversed.$base";
2042 }
2043
2044 // Send query
2045 $ipList = gethostbynamel( $host );
2046
2047 if ( $ipList ) {
2048 wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
2049 $found = true;
2050 break;
2051 } else {
2052 wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
2053 }
2054 }
2055 }
2056
2057 return $found;
2058 }
2059
2067 public static function isLocallyBlockedProxy( $ip ) {
2068 global $wgProxyList;
2069
2070 if ( !$wgProxyList ) {
2071 return false;
2072 }
2073
2074 if ( !is_array( $wgProxyList ) ) {
2075 // Load values from the specified file
2076 $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
2077 }
2078
2079 $resultProxyList = [];
2080 $deprecatedIPEntries = [];
2081
2082 // backward compatibility: move all ip addresses in keys to values
2083 foreach ( $wgProxyList as $key => $value ) {
2084 $keyIsIP = IP::isIPAddress( $key );
2085 $valueIsIP = IP::isIPAddress( $value );
2086 if ( $keyIsIP && !$valueIsIP ) {
2087 $deprecatedIPEntries[] = $key;
2088 $resultProxyList[] = $key;
2089 } elseif ( $keyIsIP && $valueIsIP ) {
2090 $deprecatedIPEntries[] = $key;
2091 $resultProxyList[] = $key;
2092 $resultProxyList[] = $value;
2093 } else {
2094 $resultProxyList[] = $value;
2095 }
2096 }
2097
2098 if ( $deprecatedIPEntries ) {
2100 'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
2101 implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
2102 }
2103
2104 $proxyListIPSet = new IPSet( $resultProxyList );
2105 return $proxyListIPSet->match( $ip );
2106 }
2107
2113 public function isPingLimitable() {
2115 if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
2116 // No other good way currently to disable rate limits
2117 // for specific IPs. :P
2118 // But this is a crappy hack and should die.
2119 return false;
2120 }
2121 return !$this->isAllowed( 'noratelimit' );
2122 }
2123
2138 public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
2139 // Avoid PHP 7.1 warning of passing $this by reference
2140 $user = $this;
2141 // Call the 'PingLimiter' hook
2142 $result = false;
2143 if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
2144 return $result;
2145 }
2146
2147 global $wgRateLimits;
2148 if ( !isset( $wgRateLimits[$action] ) ) {
2149 return false;
2150 }
2151
2152 $limits = array_merge(
2153 [ '&can-bypass' => true ],
2154 $wgRateLimits[$action]
2155 );
2156
2157 // Some groups shouldn't trigger the ping limiter, ever
2158 if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
2159 return false;
2160 }
2161
2162 $keys = [];
2163 $id = $this->getId();
2164 $userLimit = false;
2165 $isNewbie = $this->isNewbie();
2166 $cache = ObjectCache::getLocalClusterInstance();
2167
2168 if ( $id == 0 ) {
2169 // limits for anons
2170 if ( isset( $limits['anon'] ) ) {
2171 $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
2172 }
2173 } else {
2174 // limits for logged-in users
2175 if ( isset( $limits['user'] ) ) {
2176 $userLimit = $limits['user'];
2177 }
2178 }
2179
2180 // limits for anons and for newbie logged-in users
2181 if ( $isNewbie ) {
2182 // ip-based limits
2183 if ( isset( $limits['ip'] ) ) {
2184 $ip = $this->getRequest()->getIP();
2185 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
2186 }
2187 // subnet-based limits
2188 if ( isset( $limits['subnet'] ) ) {
2189 $ip = $this->getRequest()->getIP();
2190 $subnet = IP::getSubnet( $ip );
2191 if ( $subnet !== false ) {
2192 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
2193 }
2194 }
2195 }
2196
2197 // Check for group-specific permissions
2198 // If more than one group applies, use the group with the highest limit ratio (max/period)
2199 foreach ( $this->getGroups() as $group ) {
2200 if ( isset( $limits[$group] ) ) {
2201 if ( $userLimit === false
2202 || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
2203 ) {
2204 $userLimit = $limits[$group];
2205 }
2206 }
2207 }
2208
2209 // limits for newbie logged-in users (override all the normal user limits)
2210 if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
2211 $userLimit = $limits['newbie'];
2212 }
2213
2214 // Set the user limit key
2215 if ( $userLimit !== false ) {
2216 list( $max, $period ) = $userLimit;
2217 wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2218 $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2219 }
2220
2221 // ip-based limits for all ping-limitable users
2222 if ( isset( $limits['ip-all'] ) ) {
2223 $ip = $this->getRequest()->getIP();
2224 // ignore if user limit is more permissive
2225 if ( $isNewbie || $userLimit === false
2226 || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2227 $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2228 }
2229 }
2230
2231 // subnet-based limits for all ping-limitable users
2232 if ( isset( $limits['subnet-all'] ) ) {
2233 $ip = $this->getRequest()->getIP();
2234 $subnet = IP::getSubnet( $ip );
2235 if ( $subnet !== false ) {
2236 // ignore if user limit is more permissive
2237 if ( $isNewbie || $userLimit === false
2238 || $limits['ip-all'][0] / $limits['ip-all'][1]
2239 > $userLimit[0] / $userLimit[1] ) {
2240 $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2241 }
2242 }
2243 }
2244
2245 $triggered = false;
2246 foreach ( $keys as $key => $limit ) {
2247 list( $max, $period ) = $limit;
2248 $summary = "(limit $max in {$period}s)";
2249 $count = $cache->get( $key );
2250 // Already pinged?
2251 if ( $count ) {
2252 if ( $count >= $max ) {
2253 wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2254 "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2255 $triggered = true;
2256 } else {
2257 wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2258 }
2259 } else {
2260 wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2261 if ( $incrBy > 0 ) {
2262 $cache->add( $key, 0, intval( $period ) ); // first ping
2263 }
2264 }
2265 if ( $incrBy > 0 ) {
2266 $cache->incr( $key, $incrBy );
2267 }
2268 }
2269
2270 return $triggered;
2271 }
2272
2280 public function isBlocked( $bFromSlave = true ) {
2281 return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
2282 }
2283
2290 public function getBlock( $bFromSlave = true ) {
2291 $this->getBlockedStatus( $bFromSlave );
2292 return $this->mBlock instanceof Block ? $this->mBlock : null;
2293 }
2294
2302 public function isBlockedFrom( $title, $bFromSlave = false ) {
2303 global $wgBlockAllowsUTEdit;
2304
2305 $blocked = $this->isBlocked( $bFromSlave );
2306 $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
2307 // If a user's name is suppressed, they cannot make edits anywhere
2308 if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
2309 && $title->getNamespace() == NS_USER_TALK ) {
2310 $blocked = false;
2311 wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
2312 }
2313
2314 Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
2315
2316 return $blocked;
2317 }
2318
2323 public function blockedBy() {
2324 $this->getBlockedStatus();
2325 return $this->mBlockedby;
2326 }
2327
2332 public function blockedFor() {
2333 $this->getBlockedStatus();
2334 return $this->mBlockreason;
2335 }
2336
2341 public function getBlockId() {
2342 $this->getBlockedStatus();
2343 return ( $this->mBlock ? $this->mBlock->getId() : false );
2344 }
2345
2354 public function isBlockedGlobally( $ip = '' ) {
2355 return $this->getGlobalBlock( $ip ) instanceof Block;
2356 }
2357
2368 public function getGlobalBlock( $ip = '' ) {
2369 if ( $this->mGlobalBlock !== null ) {
2370 return $this->mGlobalBlock ?: null;
2371 }
2372 // User is already an IP?
2373 if ( IP::isIPAddress( $this->getName() ) ) {
2374 $ip = $this->getName();
2375 } elseif ( !$ip ) {
2376 $ip = $this->getRequest()->getIP();
2377 }
2378 // Avoid PHP 7.1 warning of passing $this by reference
2379 $user = $this;
2380 $blocked = false;
2381 $block = null;
2382 Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2383
2384 if ( $blocked && $block === null ) {
2385 // back-compat: UserIsBlockedGlobally didn't have $block param first
2386 $block = new Block( [
2387 'address' => $ip,
2388 'systemBlock' => 'global-block'
2389 ] );
2390 }
2391
2392 $this->mGlobalBlock = $blocked ? $block : false;
2393 return $this->mGlobalBlock ?: null;
2394 }
2395
2401 public function isLocked() {
2402 if ( $this->mLocked !== null ) {
2403 return $this->mLocked;
2404 }
2405 // Avoid PHP 7.1 warning of passing $this by reference
2406 $user = $this;
2407 $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2408 $this->mLocked = $authUser && $authUser->isLocked();
2409 Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2410 return $this->mLocked;
2411 }
2412
2418 public function isHidden() {
2419 if ( $this->mHideName !== null ) {
2420 return $this->mHideName;
2421 }
2422 $this->getBlockedStatus();
2423 if ( !$this->mHideName ) {
2424 // Avoid PHP 7.1 warning of passing $this by reference
2425 $user = $this;
2426 $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2427 $this->mHideName = $authUser && $authUser->isHidden();
2428 Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2429 }
2430 return $this->mHideName;
2431 }
2432
2437 public function getId() {
2438 if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2439 // Special case, we know the user is anonymous
2440 return 0;
2441 } elseif ( !$this->isItemLoaded( 'id' ) ) {
2442 // Don't load if this was initialized from an ID
2443 $this->load();
2444 }
2445
2446 return (int)$this->mId;
2447 }
2448
2453 public function setId( $v ) {
2454 $this->mId = $v;
2455 $this->clearInstanceCache( 'id' );
2456 }
2457
2462 public function getName() {
2463 if ( $this->isItemLoaded( 'name', 'only' ) ) {
2464 // Special case optimisation
2465 return $this->mName;
2466 } else {
2467 $this->load();
2468 if ( $this->mName === false ) {
2469 // Clean up IPs
2470 $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2471 }
2472 return $this->mName;
2473 }
2474 }
2475
2489 public function setName( $str ) {
2490 $this->load();
2491 $this->mName = $str;
2492 }
2493
2500 public function getActorId( IDatabase $dbw = null ) {
2502
2503 // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
2504 // but it does little harm and might be needed for write callers loading a User.
2506 return 0;
2507 }
2508
2509 if ( !$this->isItemLoaded( 'actor' ) ) {
2510 $this->load();
2511 }
2512
2513 // Currently $this->mActorId might be null if $this was loaded from a
2514 // cache entry that was written when $wgActorTableSchemaMigrationStage
2515 // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
2516 // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
2517 // has been removed), that condition may be removed.
2518 if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
2519 $q = [
2520 'actor_user' => $this->getId() ?: null,
2521 'actor_name' => (string)$this->getName(),
2522 ];
2523 if ( $dbw ) {
2524 if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
2526 'Cannot create an actor for a usable name that is not an existing user'
2527 );
2528 }
2529 if ( $q['actor_name'] === '' ) {
2530 throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
2531 }
2532 $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
2533 if ( $dbw->affectedRows() ) {
2534 $this->mActorId = (int)$dbw->insertId();
2535 } else {
2536 // Outdated cache?
2537 // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
2538 $this->mActorId = (int)$dbw->selectField(
2539 'actor',
2540 'actor_id',
2541 $q,
2542 __METHOD__,
2543 [ 'LOCK IN SHARE MODE' ]
2544 );
2545 if ( !$this->mActorId ) {
2547 "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
2548 );
2549 }
2550 }
2551 $this->invalidateCache();
2552 } else {
2553 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
2554 $db = wfGetDB( $index );
2555 $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
2556 }
2557 $this->setItemLoaded( 'actor' );
2558 }
2559
2560 return (int)$this->mActorId;
2561 }
2562
2567 public function getTitleKey() {
2568 return str_replace( ' ', '_', $this->getName() );
2569 }
2570
2575 public function getNewtalk() {
2576 $this->load();
2577
2578 // Load the newtalk status if it is unloaded (mNewtalk=-1)
2579 if ( $this->mNewtalk === -1 ) {
2580 $this->mNewtalk = false; # reset talk page status
2581
2582 // Check memcached separately for anons, who have no
2583 // entire User object stored in there.
2584 if ( !$this->mId ) {
2585 global $wgDisableAnonTalk;
2586 if ( $wgDisableAnonTalk ) {
2587 // Anon newtalk disabled by configuration.
2588 $this->mNewtalk = false;
2589 } else {
2590 $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2591 }
2592 } else {
2593 $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2594 }
2595 }
2596
2597 return (bool)$this->mNewtalk;
2598 }
2599
2613 public function getNewMessageLinks() {
2614 // Avoid PHP 7.1 warning of passing $this by reference
2615 $user = $this;
2616 $talks = [];
2617 if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2618 return $talks;
2619 } elseif ( !$this->getNewtalk() ) {
2620 return [];
2621 }
2622 $utp = $this->getTalkPage();
2623 $dbr = wfGetDB( DB_REPLICA );
2624 // Get the "last viewed rev" timestamp from the oldest message notification
2625 $timestamp = $dbr->selectField( 'user_newtalk',
2626 'MIN(user_last_timestamp)',
2627 $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2628 __METHOD__ );
2629 $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2630 return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2631 }
2632
2638 public function getNewMessageRevisionId() {
2639 $newMessageRevisionId = null;
2640 $newMessageLinks = $this->getNewMessageLinks();
2641 if ( $newMessageLinks ) {
2642 // Note: getNewMessageLinks() never returns more than a single link
2643 // and it is always for the same wiki, but we double-check here in
2644 // case that changes some time in the future.
2645 if ( count( $newMessageLinks ) === 1
2646 && $newMessageLinks[0]['wiki'] === wfWikiID()
2647 && $newMessageLinks[0]['rev']
2648 ) {
2650 $newMessageRevision = $newMessageLinks[0]['rev'];
2651 $newMessageRevisionId = $newMessageRevision->getId();
2652 }
2653 }
2654 return $newMessageRevisionId;
2655 }
2656
2665 protected function checkNewtalk( $field, $id ) {
2666 $dbr = wfGetDB( DB_REPLICA );
2667
2668 $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2669
2670 return $ok !== false;
2671 }
2672
2680 protected function updateNewtalk( $field, $id, $curRev = null ) {
2681 // Get timestamp of the talk page revision prior to the current one
2682 $prevRev = $curRev ? $curRev->getPrevious() : false;
2683 $ts = $prevRev ? $prevRev->getTimestamp() : null;
2684 // Mark the user as having new messages since this revision
2685 $dbw = wfGetDB( DB_MASTER );
2686 $dbw->insert( 'user_newtalk',
2687 [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2688 __METHOD__,
2689 'IGNORE' );
2690 if ( $dbw->affectedRows() ) {
2691 wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2692 return true;
2693 } else {
2694 wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2695 return false;
2696 }
2697 }
2698
2705 protected function deleteNewtalk( $field, $id ) {
2706 $dbw = wfGetDB( DB_MASTER );
2707 $dbw->delete( 'user_newtalk',
2708 [ $field => $id ],
2709 __METHOD__ );
2710 if ( $dbw->affectedRows() ) {
2711 wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2712 return true;
2713 } else {
2714 wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2715 return false;
2716 }
2717 }
2718
2725 public function setNewtalk( $val, $curRev = null ) {
2726 if ( wfReadOnly() ) {
2727 return;
2728 }
2729
2730 $this->load();
2731 $this->mNewtalk = $val;
2732
2733 if ( $this->isAnon() ) {
2734 $field = 'user_ip';
2735 $id = $this->getName();
2736 } else {
2737 $field = 'user_id';
2738 $id = $this->getId();
2739 }
2740
2741 if ( $val ) {
2742 $changed = $this->updateNewtalk( $field, $id, $curRev );
2743 } else {
2744 $changed = $this->deleteNewtalk( $field, $id );
2745 }
2746
2747 if ( $changed ) {
2748 $this->invalidateCache();
2749 }
2750 }
2751
2757 private function newTouchedTimestamp() {
2758 global $wgClockSkewFudge;
2759
2760 $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2761 if ( $this->mTouched && $time <= $this->mTouched ) {
2762 $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2763 }
2764
2765 return $time;
2766 }
2767
2778 public function clearSharedCache( $mode = 'changed' ) {
2779 if ( !$this->getId() ) {
2780 return;
2781 }
2782
2783 $cache = ObjectCache::getMainWANInstance();
2784 $key = $this->getCacheKey( $cache );
2785 if ( $mode === 'refresh' ) {
2786 $cache->delete( $key, 1 );
2787 } else {
2788 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2789 if ( $lb->hasOrMadeRecentMasterChanges() ) {
2790 $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
2791 function () use ( $cache, $key ) {
2792 $cache->delete( $key );
2793 },
2794 __METHOD__
2795 );
2796 } else {
2797 $cache->delete( $key );
2798 }
2799 }
2800 }
2801
2807 public function invalidateCache() {
2808 $this->touch();
2809 $this->clearSharedCache();
2810 }
2811
2824 public function touch() {
2825 $id = $this->getId();
2826 if ( $id ) {
2827 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2828 $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2829 $cache->touchCheckKey( $key );
2830 $this->mQuickTouched = null;
2831 }
2832 }
2833
2839 public function validateCache( $timestamp ) {
2840 return ( $timestamp >= $this->getTouched() );
2841 }
2842
2851 public function getTouched() {
2852 $this->load();
2853
2854 if ( $this->mId ) {
2855 if ( $this->mQuickTouched === null ) {
2856 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2857 $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2858
2859 $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2860 }
2861
2862 return max( $this->mTouched, $this->mQuickTouched );
2863 }
2864
2865 return $this->mTouched;
2866 }
2867
2873 public function getDBTouched() {
2874 $this->load();
2875
2876 return $this->mTouched;
2877 }
2878
2895 public function setPassword( $str ) {
2896 wfDeprecated( __METHOD__, '1.27' );
2897 return $this->setPasswordInternal( $str );
2898 }
2899
2908 public function setInternalPassword( $str ) {
2909 wfDeprecated( __METHOD__, '1.27' );
2910 $this->setPasswordInternal( $str );
2911 }
2912
2921 private function setPasswordInternal( $str ) {
2922 $manager = AuthManager::singleton();
2923
2924 // If the user doesn't exist yet, fail
2925 if ( !$manager->userExists( $this->getName() ) ) {
2926 throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2927 }
2928
2930 'username' => $this->getName(),
2931 'password' => $str,
2932 'retype' => $str,
2933 ] );
2934 if ( !$status->isGood() ) {
2935 \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
2936 ->info( __METHOD__ . ': Password change rejected: '
2937 . $status->getWikiText( null, null, 'en' ) );
2938 return false;
2939 }
2940
2941 $this->setOption( 'watchlisttoken', false );
2942 SessionManager::singleton()->invalidateSessionsForUser( $this );
2943
2944 return true;
2945 }
2946
2959 public function changeAuthenticationData( array $data ) {
2960 $manager = AuthManager::singleton();
2961 $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2962 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2963
2964 $status = Status::newGood( 'ignored' );
2965 foreach ( $reqs as $req ) {
2966 $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2967 }
2968 if ( $status->getValue() === 'ignored' ) {
2969 $status->warning( 'authenticationdatachange-ignored' );
2970 }
2971
2972 if ( $status->isGood() ) {
2973 foreach ( $reqs as $req ) {
2974 $manager->changeAuthenticationData( $req );
2975 }
2976 }
2977 return $status;
2978 }
2979
2986 public function getToken( $forceCreation = true ) {
2988
2989 $this->load();
2990 if ( !$this->mToken && $forceCreation ) {
2991 $this->setToken();
2992 }
2993
2994 if ( !$this->mToken ) {
2995 // The user doesn't have a token, return null to indicate that.
2996 return null;
2997 } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2998 // We return a random value here so existing token checks are very
2999 // likely to fail.
3000 return MWCryptRand::generateHex( self::TOKEN_LENGTH );
3001 } elseif ( $wgAuthenticationTokenVersion === null ) {
3002 // $wgAuthenticationTokenVersion not in use, so return the raw secret
3003 return $this->mToken;
3004 } else {
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 return substr( $ret, -$len );
3015 }
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 ) {
3105 // Send the user an email notifying the user of the change in registered
3106 // email address on their previous email address
3107 if ( $type == 'changed' ) {
3108 $change = $str != '' ? 'changed' : 'removed';
3109 $notificationResult = $this->sendMail(
3110 wfMessage( 'notificationemail_subject_' . $change )->text(),
3111 wfMessage( 'notificationemail_body_' . $change,
3112 $this->getRequest()->getIP(),
3113 $this->getName(),
3114 $str )->text()
3115 );
3116 }
3117 }
3118
3119 $this->setEmail( $str );
3120
3121 if ( $str !== '' && $wgEmailAuthentication ) {
3122 // Send a confirmation request to the new address if needed
3124
3125 if ( $notificationResult !== null ) {
3126 $result->merge( $notificationResult );
3127 }
3128
3129 if ( $result->isGood() ) {
3130 // Say to the caller that a confirmation and notification mail has been sent
3131 $result->value = 'eauth';
3132 }
3133 } else {
3134 $result = Status::newGood( true );
3135 }
3136
3137 return $result;
3138 }
3139
3144 public function getRealName() {
3145 if ( !$this->isItemLoaded( 'realname' ) ) {
3146 $this->load();
3147 }
3148
3149 return $this->mRealName;
3150 }
3151
3156 public function setRealName( $str ) {
3157 $this->load();
3158 $this->mRealName = $str;
3159 }
3160
3171 public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
3172 global $wgHiddenPrefs;
3173 $this->loadOptions();
3174
3175 # We want 'disabled' preferences to always behave as the default value for
3176 # users, even if they have set the option explicitly in their settings (ie they
3177 # set it, and then it was disabled removing their ability to change it). But
3178 # we don't want to erase the preferences in the database in case the preference
3179 # is re-enabled again. So don't touch $mOptions, just override the returned value
3180 if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
3181 return self::getDefaultOption( $oname );
3182 }
3183
3184 if ( array_key_exists( $oname, $this->mOptions ) ) {
3185 return $this->mOptions[$oname];
3186 } else {
3187 return $defaultOverride;
3188 }
3189 }
3190
3199 public function getOptions( $flags = 0 ) {
3200 global $wgHiddenPrefs;
3201 $this->loadOptions();
3203
3204 # We want 'disabled' preferences to always behave as the default value for
3205 # users, even if they have set the option explicitly in their settings (ie they
3206 # set it, and then it was disabled removing their ability to change it). But
3207 # we don't want to erase the preferences in the database in case the preference
3208 # is re-enabled again. So don't touch $mOptions, just override the returned value
3209 foreach ( $wgHiddenPrefs as $pref ) {
3210 $default = self::getDefaultOption( $pref );
3211 if ( $default !== null ) {
3212 $options[$pref] = $default;
3213 }
3214 }
3215
3216 if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
3217 $options = array_diff_assoc( $options, self::getDefaultOptions() );
3218 }
3219
3220 return $options;
3221 }
3222
3230 public function getBoolOption( $oname ) {
3231 return (bool)$this->getOption( $oname );
3232 }
3233
3242 public function getIntOption( $oname, $defaultOverride = 0 ) {
3243 $val = $this->getOption( $oname );
3244 if ( $val == '' ) {
3245 $val = $defaultOverride;
3246 }
3247 return intval( $val );
3248 }
3249
3258 public function setOption( $oname, $val ) {
3259 $this->loadOptions();
3260
3261 // Explicitly NULL values should refer to defaults
3262 if ( is_null( $val ) ) {
3263 $val = self::getDefaultOption( $oname );
3264 }
3265
3266 $this->mOptions[$oname] = $val;
3267 }
3268
3279 public function getTokenFromOption( $oname ) {
3280 global $wgHiddenPrefs;
3281
3282 $id = $this->getId();
3283 if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
3284 return false;
3285 }
3286
3287 $token = $this->getOption( $oname );
3288 if ( !$token ) {
3289 // Default to a value based on the user token to avoid space
3290 // wasted on storing tokens for all users. When this option
3291 // is set manually by the user, only then is it stored.
3292 $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3293 }
3294
3295 return $token;
3296 }
3297
3307 public function resetTokenFromOption( $oname ) {
3308 global $wgHiddenPrefs;
3309 if ( in_array( $oname, $wgHiddenPrefs ) ) {
3310 return false;
3311 }
3312
3313 $token = MWCryptRand::generateHex( 40 );
3314 $this->setOption( $oname, $token );
3315 return $token;
3316 }
3317
3341 public static function listOptionKinds() {
3342 return [
3343 'registered',
3344 'registered-multiselect',
3345 'registered-checkmatrix',
3346 'userjs',
3347 'special',
3348 'unused'
3349 ];
3350 }
3351
3364 public function getOptionKinds( IContextSource $context, $options = null ) {
3365 $this->loadOptions();
3366 if ( $options === null ) {
3368 }
3369
3370 $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
3371 $prefs = $preferencesFactory->getFormDescriptor( $this, $context );
3372 $mapping = [];
3373
3374 // Pull out the "special" options, so they don't get converted as
3375 // multiselect or checkmatrix.
3376 $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
3377 foreach ( $specialOptions as $name => $value ) {
3378 unset( $prefs[$name] );
3379 }
3380
3381 // Multiselect and checkmatrix options are stored in the database with
3382 // one key per option, each having a boolean value. Extract those keys.
3383 $multiselectOptions = [];
3384 foreach ( $prefs as $name => $info ) {
3385 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3386 ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
3387 $opts = HTMLFormField::flattenOptions( $info['options'] );
3388 $prefix = $info['prefix'] ?? $name;
3389
3390 foreach ( $opts as $value ) {
3391 $multiselectOptions["$prefix$value"] = true;
3392 }
3393
3394 unset( $prefs[$name] );
3395 }
3396 }
3397 $checkmatrixOptions = [];
3398 foreach ( $prefs as $name => $info ) {
3399 if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3400 ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
3401 $columns = HTMLFormField::flattenOptions( $info['columns'] );
3402 $rows = HTMLFormField::flattenOptions( $info['rows'] );
3403 $prefix = $info['prefix'] ?? $name;
3404
3405 foreach ( $columns as $column ) {
3406 foreach ( $rows as $row ) {
3407 $checkmatrixOptions["$prefix$column-$row"] = true;
3408 }
3409 }
3410
3411 unset( $prefs[$name] );
3412 }
3413 }
3414
3415 // $value is ignored
3416 foreach ( $options as $key => $value ) {
3417 if ( isset( $prefs[$key] ) ) {
3418 $mapping[$key] = 'registered';
3419 } elseif ( isset( $multiselectOptions[$key] ) ) {
3420 $mapping[$key] = 'registered-multiselect';
3421 } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3422 $mapping[$key] = 'registered-checkmatrix';
3423 } elseif ( isset( $specialOptions[$key] ) ) {
3424 $mapping[$key] = 'special';
3425 } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3426 $mapping[$key] = 'userjs';
3427 } else {
3428 $mapping[$key] = 'unused';
3429 }
3430 }
3431
3432 return $mapping;
3433 }
3434
3449 public function resetOptions(
3450 $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3452 ) {
3453 $this->load();
3454 $defaultOptions = self::getDefaultOptions();
3455
3456 if ( !is_array( $resetKinds ) ) {
3457 $resetKinds = [ $resetKinds ];
3458 }
3459
3460 if ( in_array( 'all', $resetKinds ) ) {
3461 $newOptions = $defaultOptions;
3462 } else {
3463 if ( $context === null ) {
3464 $context = RequestContext::getMain();
3465 }
3466
3467 $optionKinds = $this->getOptionKinds( $context );
3468 $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3469 $newOptions = [];
3470
3471 // Use default values for the options that should be deleted, and
3472 // copy old values for the ones that shouldn't.
3473 foreach ( $this->mOptions as $key => $value ) {
3474 if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3475 if ( array_key_exists( $key, $defaultOptions ) ) {
3476 $newOptions[$key] = $defaultOptions[$key];
3477 }
3478 } else {
3479 $newOptions[$key] = $value;
3480 }
3481 }
3482 }
3483
3484 Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3485
3486 $this->mOptions = $newOptions;
3487 $this->mOptionsLoaded = true;
3488 }
3489
3494 public function getDatePreference() {
3495 // Important migration for old data rows
3496 if ( is_null( $this->mDatePreference ) ) {
3497 global $wgLang;
3498 $value = $this->getOption( 'date' );
3499 $map = $wgLang->getDatePreferenceMigrationMap();
3500 if ( isset( $map[$value] ) ) {
3501 $value = $map[$value];
3502 }
3503 $this->mDatePreference = $value;
3504 }
3506 }
3507
3514 public function requiresHTTPS() {
3515 global $wgSecureLogin;
3516 if ( !$wgSecureLogin ) {
3517 return false;
3518 } else {
3519 $https = $this->getBoolOption( 'prefershttps' );
3520 Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3521 if ( $https ) {
3522 $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3523 }
3524 return $https;
3525 }
3526 }
3527
3533 public function getStubThreshold() {
3534 global $wgMaxArticleSize; # Maximum article size, in Kb
3535 $threshold = $this->getIntOption( 'stubthreshold' );
3536 if ( $threshold > $wgMaxArticleSize * 1024 ) {
3537 // If they have set an impossible value, disable the preference
3538 // so we can use the parser cache again.
3539 $threshold = 0;
3540 }
3541 return $threshold;
3542 }
3543
3548 public function getRights() {
3549 if ( is_null( $this->mRights ) ) {
3550 $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3551 Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3552
3553 // Deny any rights denied by the user's session, unless this
3554 // endpoint has no sessions.
3555 if ( !defined( 'MW_NO_SESSION' ) ) {
3556 $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3557 if ( $allowedRights !== null ) {
3558 $this->mRights = array_intersect( $this->mRights, $allowedRights );
3559 }
3560 }
3561
3562 Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
3563 // Force reindexation of rights when a hook has unset one of them
3564 $this->mRights = array_values( array_unique( $this->mRights ) );
3565
3566 // If block disables login, we should also remove any
3567 // extra rights blocked users might have, in case the
3568 // blocked user has a pre-existing session (T129738).
3569 // This is checked here for cases where people only call
3570 // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3571 // to give a better error message in the common case.
3572 $config = RequestContext::getMain()->getConfig();
3573 if (
3574 $this->isLoggedIn() &&
3575 $config->get( 'BlockDisablesLogin' ) &&
3576 $this->isBlocked()
3577 ) {
3578 $anon = new User;
3579 $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3580 }
3581 }
3582 return $this->mRights;
3583 }
3584
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->initEditCount();
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 } else {
3907 global $wgRequest;
3908 return $wgRequest;
3909 }
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 (
4082 $editCount < $wgLearnerEdits ||
4083 $registration > $learnerRegistration
4084 ) {
4085 return 'newcomer';
4086 } elseif (
4087 $editCount > $wgExperiencedUserEdits &&
4088 $registration <= $experiencedRegistration
4089 ) {
4090 return 'experienced';
4091 } else {
4092 return 'learner';
4093 }
4094 }
4095
4104 public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
4105 $this->load();
4106 if ( 0 == $this->mId ) {
4107 return;
4108 }
4109
4110 $session = $this->getRequest()->getSession();
4111 if ( $request && $session->getRequest() !== $request ) {
4112 $session = $session->sessionWithRequest( $request );
4113 }
4114 $delay = $session->delaySave();
4115
4116 if ( !$session->getUser()->equals( $this ) ) {
4117 if ( !$session->canSetUser() ) {
4118 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
4119 ->warning( __METHOD__ .
4120 ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
4121 );
4122 return;
4123 }
4124 $session->setUser( $this );
4125 }
4126
4127 $session->setRememberUser( $rememberMe );
4128 if ( $secure !== null ) {
4129 $session->setForceHTTPS( $secure );
4130 }
4131
4132 $session->persist();
4133
4134 ScopedCallback::consume( $delay );
4135 }
4136
4140 public function logout() {
4141 // Avoid PHP 7.1 warning of passing $this by reference
4142 $user = $this;
4143 if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
4144 $this->doLogout();
4145 }
4146 }
4147
4152 public function doLogout() {
4153 $session = $this->getRequest()->getSession();
4154 if ( !$session->canSetUser() ) {
4155 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
4156 ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
4157 $error = 'immutable';
4158 } elseif ( !$session->getUser()->equals( $this ) ) {
4159 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
4160 ->warning( __METHOD__ .
4161 ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
4162 );
4163 // But we still may as well make this user object anon
4164 $this->clearInstanceCache( 'defaults' );
4165 $error = 'wronguser';
4166 } else {
4167 $this->clearInstanceCache( 'defaults' );
4168 $delay = $session->delaySave();
4169 $session->unpersist(); // Clear cookies (T127436)
4170 $session->setLoggedOutTimestamp( time() );
4171 $session->setUser( new User );
4172 $session->set( 'wsUserID', 0 ); // Other code expects this
4173 $session->resetAllTokens();
4174 ScopedCallback::consume( $delay );
4175 $error = false;
4176 }
4177 \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
4178 'event' => 'logout',
4179 'successful' => $error === false,
4180 'status' => $error ?: 'success',
4181 ] );
4182 }
4183
4188 public function saveSettings() {
4189 if ( wfReadOnly() ) {
4190 // @TODO: caller should deal with this instead!
4191 // This should really just be an exception.
4192 MWExceptionHandler::logException( new DBExpectedError(
4193 null,
4194 "Could not update user with ID '{$this->mId}'; DB is read-only."
4195 ) );
4196 return;
4197 }
4198
4199 $this->load();
4200 if ( 0 == $this->mId ) {
4201 return; // anon
4202 }
4203
4204 // Get a new user_touched that is higher than the old one.
4205 // This will be used for a CAS check as a last-resort safety
4206 // check against race conditions and replica DB lag.
4207 $newTouched = $this->newTouchedTimestamp();
4208
4209 $dbw = wfGetDB( DB_MASTER );
4210 $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
4212
4213 $dbw->update( 'user',
4214 [ /* SET */
4215 'user_name' => $this->mName,
4216 'user_real_name' => $this->mRealName,
4217 'user_email' => $this->mEmail,
4218 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4219 'user_touched' => $dbw->timestamp( $newTouched ),
4220 'user_token' => strval( $this->mToken ),
4221 'user_email_token' => $this->mEmailToken,
4222 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4223 ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4224 'user_id' => $this->mId,
4225 ] ), $fname
4226 );
4227
4228 if ( !$dbw->affectedRows() ) {
4229 // Maybe the problem was a missed cache update; clear it to be safe
4230 $this->clearSharedCache( 'refresh' );
4231 // User was changed in the meantime or loaded with stale data
4232 $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4233 LoggerFactory::getInstance( 'preferences' )->warning(
4234 "CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
4235 [ 'user_id' => $this->mId, 'db_flag' => $from ]
4236 );
4237 throw new MWException( "CAS update failed on user_touched. " .
4238 "The version of the user to be saved is older than the current version."
4239 );
4240 }
4241
4243 $dbw->update(
4244 'actor',
4245 [ 'actor_name' => $this->mName ],
4246 [ 'actor_user' => $this->mId ],
4247 $fname
4248 );
4249 }
4250 } );
4251
4252 $this->mTouched = $newTouched;
4253 $this->saveOptions();
4254
4255 Hooks::run( 'UserSaveSettings', [ $this ] );
4256 $this->clearSharedCache();
4257 $this->getUserPage()->invalidateCache();
4258 }
4259
4266 public function idForName( $flags = 0 ) {
4267 $s = trim( $this->getName() );
4268 if ( $s === '' ) {
4269 return 0;
4270 }
4271
4272 $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4273 ? wfGetDB( DB_MASTER )
4274 : wfGetDB( DB_REPLICA );
4275
4276 $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4277 ? [ 'LOCK IN SHARE MODE' ]
4278 : [];
4279
4280 $id = $db->selectField( 'user',
4281 'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4282
4283 return (int)$id;
4284 }
4285
4301 public static function createNew( $name, $params = [] ) {
4302 foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4303 if ( isset( $params[$field] ) ) {
4304 wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4305 unset( $params[$field] );
4306 }
4307 }
4308
4309 $user = new User;
4310 $user->load();
4311 $user->setToken(); // init token
4312 if ( isset( $params['options'] ) ) {
4313 $user->mOptions = $params['options'] + (array)$user->mOptions;
4314 unset( $params['options'] );
4315 }
4316 $dbw = wfGetDB( DB_MASTER );
4317
4318 $noPass = PasswordFactory::newInvalidPassword()->toString();
4319
4320 $fields = [
4321 'user_name' => $name,
4322 'user_password' => $noPass,
4323 'user_newpassword' => $noPass,
4324 'user_email' => $user->mEmail,
4325 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4326 'user_real_name' => $user->mRealName,
4327 'user_token' => strval( $user->mToken ),
4328 'user_registration' => $dbw->timestamp( $user->mRegistration ),
4329 'user_editcount' => 0,
4330 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4331 ];
4332 foreach ( $params as $name => $value ) {
4333 $fields["user_$name"] = $value;
4334 }
4335
4336 return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
4337 $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
4338 if ( $dbw->affectedRows() ) {
4339 $newUser = self::newFromId( $dbw->insertId() );
4340 $newUser->mName = $fields['user_name'];
4341 $newUser->updateActorId( $dbw );
4342 // Load the user from master to avoid replica lag
4343 $newUser->load( self::READ_LATEST );
4344 } else {
4345 $newUser = null;
4346 }
4347 return $newUser;
4348 } );
4349 }
4350
4377 public function addToDatabase() {
4378 $this->load();
4379 if ( !$this->mToken ) {
4380 $this->setToken(); // init token
4381 }
4382
4383 if ( !is_string( $this->mName ) ) {
4384 throw new RuntimeException( "User name field is not set." );
4385 }
4386
4387 $this->mTouched = $this->newTouchedTimestamp();
4388
4389 $dbw = wfGetDB( DB_MASTER );
4390 $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
4391 $noPass = PasswordFactory::newInvalidPassword()->toString();
4392 $dbw->insert( 'user',
4393 [
4394 'user_name' => $this->mName,
4395 'user_password' => $noPass,
4396 'user_newpassword' => $noPass,
4397 'user_email' => $this->mEmail,
4398 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4399 'user_real_name' => $this->mRealName,
4400 'user_token' => strval( $this->mToken ),
4401 'user_registration' => $dbw->timestamp( $this->mRegistration ),
4402 'user_editcount' => 0,
4403 'user_touched' => $dbw->timestamp( $this->mTouched ),
4404 ], $fname,
4405 [ 'IGNORE' ]
4406 );
4407 if ( !$dbw->affectedRows() ) {
4408 // Use locking reads to bypass any REPEATABLE-READ snapshot.
4409 $this->mId = $dbw->selectField(
4410 'user',
4411 'user_id',
4412 [ 'user_name' => $this->mName ],
4413 $fname,
4414 [ 'LOCK IN SHARE MODE' ]
4415 );
4416 $loaded = false;
4417 if ( $this->mId ) {
4418 if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4419 $loaded = true;
4420 }
4421 }
4422 if ( !$loaded ) {
4423 throw new MWException( $fname . ": hit a key conflict attempting " .
4424 "to insert user '{$this->mName}' row, but it was not present in select!" );
4425 }
4426 return Status::newFatal( 'userexists' );
4427 }
4428 $this->mId = $dbw->insertId();
4429 self::$idCacheByName[$this->mName] = $this->mId;
4430 $this->updateActorId( $dbw );
4431
4432 return Status::newGood();
4433 } );
4434 if ( !$status->isGood() ) {
4435 return $status;
4436 }
4437
4438 // Clear instance cache other than user table data and actor, which is already accurate
4439 $this->clearInstanceCache();
4440
4441 $this->saveOptions();
4442 return Status::newGood();
4443 }
4444
4449 private function updateActorId( IDatabase $dbw ) {
4451
4453 $dbw->insert(
4454 'actor',
4455 [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
4456 __METHOD__
4457 );
4458 $this->mActorId = (int)$dbw->insertId();
4459 }
4460 }
4461
4467 public function spreadAnyEditBlock() {
4468 if ( $this->isLoggedIn() && $this->isBlocked() ) {
4469 return $this->spreadBlock();
4470 }
4471
4472 return false;
4473 }
4474
4480 protected function spreadBlock() {
4481 wfDebug( __METHOD__ . "()\n" );
4482 $this->load();
4483 if ( $this->mId == 0 ) {
4484 return false;
4485 }
4486
4487 $userblock = Block::newFromTarget( $this->getName() );
4488 if ( !$userblock ) {
4489 return false;
4490 }
4491
4492 return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4493 }
4494
4499 public function isBlockedFromCreateAccount() {
4500 $this->getBlockedStatus();
4501 if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4502 return $this->mBlock;
4503 }
4504
4505 # T15611: if the IP address the user is trying to create an account from is
4506 # blocked with createaccount disabled, prevent new account creation there even
4507 # when the user is logged in
4508 if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4509 $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4510 }
4511 return $this->mBlockedFromCreateAccount instanceof Block
4512 && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4513 ? $this->mBlockedFromCreateAccount
4514 : false;
4515 }
4516
4521 public function isBlockedFromEmailuser() {
4522 $this->getBlockedStatus();
4523 return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4524 }
4525
4530 public function isAllowedToCreateAccount() {
4531 return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4532 }
4533
4539 public function getUserPage() {
4540 return Title::makeTitle( NS_USER, $this->getName() );
4541 }
4542
4548 public function getTalkPage() {
4549 $title = $this->getUserPage();
4550 return $title->getTalkPage();
4551 }
4552
4558 public function isNewbie() {
4559 return !$this->isAllowed( 'autoconfirmed' );
4560 }
4561
4568 public function checkPassword( $password ) {
4569 wfDeprecated( __METHOD__, '1.27' );
4570
4571 $manager = AuthManager::singleton();
4572 $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4573 $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4574 [
4575 'username' => $this->getName(),
4576 'password' => $password,
4577 ]
4578 );
4579 $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4580 switch ( $res->status ) {
4581 case AuthenticationResponse::PASS:
4582 return true;
4583 case AuthenticationResponse::FAIL:
4584 // Hope it's not a PreAuthenticationProvider that failed...
4585 \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
4586 ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4587 return false;
4588 default:
4589 throw new BadMethodCallException(
4590 'AuthManager returned a response unsupported by ' . __METHOD__
4591 );
4592 }
4593 }
4594
4603 public function checkTemporaryPassword( $plaintext ) {
4604 wfDeprecated( __METHOD__, '1.27' );
4605 // Can't check the temporary password individually.
4606 return $this->checkPassword( $plaintext );
4607 }
4608
4620 public function getEditTokenObject( $salt = '', $request = null ) {
4621 if ( $this->isAnon() ) {
4622 return new LoggedOutEditToken();
4623 }
4624
4625 if ( !$request ) {
4626 $request = $this->getRequest();
4627 }
4628 return $request->getSession()->getToken( $salt );
4629 }
4630
4644 public function getEditToken( $salt = '', $request = null ) {
4645 return $this->getEditTokenObject( $salt, $request )->toString();
4646 }
4647
4660 public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4661 return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4662 }
4663
4674 public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4675 $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4676 return $this->matchEditToken( $val, $salt, $request, $maxage );
4677 }
4678
4686 public function sendConfirmationMail( $type = 'created' ) {
4687 global $wgLang;
4688 $expiration = null; // gets passed-by-ref and defined in next line.
4689 $token = $this->confirmationToken( $expiration );
4690 $url = $this->confirmationTokenUrl( $token );
4691 $invalidateURL = $this->invalidationTokenUrl( $token );
4692 $this->saveSettings();
4693
4694 if ( $type == 'created' || $type === false ) {
4695 $message = 'confirmemail_body';
4696 } elseif ( $type === true ) {
4697 $message = 'confirmemail_body_changed';
4698 } else {
4699 // Messages: confirmemail_body_changed, confirmemail_body_set
4700 $message = 'confirmemail_body_' . $type;
4701 }
4702
4703 return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4704 wfMessage( $message,
4705 $this->getRequest()->getIP(),
4706 $this->getName(),
4707 $url,
4708 $wgLang->userTimeAndDate( $expiration, $this ),
4709 $invalidateURL,
4710 $wgLang->userDate( $expiration, $this ),
4711 $wgLang->userTime( $expiration, $this ) )->text() );
4712 }
4713
4725 public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4726 global $wgPasswordSender;
4727
4728 if ( $from instanceof User ) {
4729 $sender = MailAddress::newFromUser( $from );
4730 } else {
4731 $sender = new MailAddress( $wgPasswordSender,
4732 wfMessage( 'emailsender' )->inContentLanguage()->text() );
4733 }
4734 $to = MailAddress::newFromUser( $this );
4735
4736 return UserMailer::send( $to, $sender, $subject, $body, [
4737 'replyTo' => $replyto,
4738 ] );
4739 }
4740
4751 protected function confirmationToken( &$expiration ) {
4753 $now = time();
4754 $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4755 $expiration = wfTimestamp( TS_MW, $expires );
4756 $this->load();
4757 $token = MWCryptRand::generateHex( 32 );
4758 $hash = md5( $token );
4759 $this->mEmailToken = $hash;
4760 $this->mEmailTokenExpires = $expiration;
4761 return $token;
4762 }
4763
4769 protected function confirmationTokenUrl( $token ) {
4770 return $this->getTokenUrl( 'ConfirmEmail', $token );
4771 }
4772
4778 protected function invalidationTokenUrl( $token ) {
4779 return $this->getTokenUrl( 'InvalidateEmail', $token );
4780 }
4781
4796 protected function getTokenUrl( $page, $token ) {
4797 // Hack to bypass localization of 'Special:'
4798 $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4799 return $title->getCanonicalURL();
4800 }
4801
4809 public function confirmEmail() {
4810 // Check if it's already confirmed, so we don't touch the database
4811 // and fire the ConfirmEmailComplete hook on redundant confirmations.
4812 if ( !$this->isEmailConfirmed() ) {
4814 Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4815 }
4816 return true;
4817 }
4818
4826 public function invalidateEmail() {
4827 $this->load();
4828 $this->mEmailToken = null;
4829 $this->mEmailTokenExpires = null;
4830 $this->setEmailAuthenticationTimestamp( null );
4831 $this->mEmail = '';
4832 Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4833 return true;
4834 }
4835
4840 public function setEmailAuthenticationTimestamp( $timestamp ) {
4841 $this->load();
4842 $this->mEmailAuthenticated = $timestamp;
4843 Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4844 }
4845
4851 public function canSendEmail() {
4853 if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4854 return false;
4855 }
4856 $canSend = $this->isEmailConfirmed();
4857 // Avoid PHP 7.1 warning of passing $this by reference
4858 $user = $this;
4859 Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4860 return $canSend;
4861 }
4862
4868 public function canReceiveEmail() {
4869 return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4870 }
4871
4882 public function isEmailConfirmed() {
4884 $this->load();
4885 // Avoid PHP 7.1 warning of passing $this by reference
4886 $user = $this;
4887 $confirmed = true;
4888 if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4889 if ( $this->isAnon() ) {
4890 return false;
4891 }
4892 if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4893 return false;
4894 }
4896 return false;
4897 }
4898 return true;
4899 } else {
4900 return $confirmed;
4901 }
4902 }
4903
4908 public function isEmailConfirmationPending() {
4910 return $wgEmailAuthentication &&
4911 !$this->isEmailConfirmed() &&
4912 $this->mEmailToken &&
4913 $this->mEmailTokenExpires > wfTimestamp();
4914 }
4915
4923 public function getRegistration() {
4924 if ( $this->isAnon() ) {
4925 return false;
4926 }
4927 $this->load();
4928 return $this->mRegistration;
4929 }
4930
4937 public function getFirstEditTimestamp() {
4938 if ( $this->getId() == 0 ) {
4939 return false; // anons
4940 }
4941 $dbr = wfGetDB( DB_REPLICA );
4942 $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
4943 $time = $dbr->selectField(
4944 [ 'revision' ] + $actorWhere['tables'],
4945 'rev_timestamp',
4946 [ $actorWhere['conds'] ],
4947 __METHOD__,
4948 [ 'ORDER BY' => 'rev_timestamp ASC' ],
4949 $actorWhere['joins']
4950 );
4951 if ( !$time ) {
4952 return false; // no edits
4953 }
4954 return wfTimestamp( TS_MW, $time );
4955 }
4956
4963 public static function getGroupPermissions( $groups ) {
4965 $rights = [];
4966 // grant every granted permission first
4967 foreach ( $groups as $group ) {
4968 if ( isset( $wgGroupPermissions[$group] ) ) {
4969 $rights = array_merge( $rights,
4970 // array_filter removes empty items
4971 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4972 }
4973 }
4974 // now revoke the revoked permissions
4975 foreach ( $groups as $group ) {
4976 if ( isset( $wgRevokePermissions[$group] ) ) {
4977 $rights = array_diff( $rights,
4978 array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4979 }
4980 }
4981 return array_unique( $rights );
4982 }
4983
4990 public static function getGroupsWithPermission( $role ) {
4991 global $wgGroupPermissions;
4992 $allowedGroups = [];
4993 foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4994 if ( self::groupHasPermission( $group, $role ) ) {
4995 $allowedGroups[] = $group;
4996 }
4997 }
4998 return $allowedGroups;
4999 }
5000
5013 public static function groupHasPermission( $group, $role ) {
5015 return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
5016 && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
5017 }
5018
5033 public static function isEveryoneAllowed( $right ) {
5035 static $cache = [];
5036
5037 // Use the cached results, except in unit tests which rely on
5038 // being able change the permission mid-request
5039 if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
5040 return $cache[$right];
5041 }
5042
5043 if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
5044 $cache[$right] = false;
5045 return false;
5046 }
5047
5048 // If it's revoked anywhere, then everyone doesn't have it
5049 foreach ( $wgRevokePermissions as $rights ) {
5050 if ( isset( $rights[$right] ) && $rights[$right] ) {
5051 $cache[$right] = false;
5052 return false;
5053 }
5054 }
5055
5056 // Remove any rights that aren't allowed to the global-session user,
5057 // unless there are no sessions for this endpoint.
5058 if ( !defined( 'MW_NO_SESSION' ) ) {
5059 $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
5060 if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
5061 $cache[$right] = false;
5062 return false;
5063 }
5064 }
5065
5066 // Allow extensions to say false
5067 if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
5068 $cache[$right] = false;
5069 return false;
5070 }
5071
5072 $cache[$right] = true;
5073 return true;
5074 }
5075
5083 public static function getGroupName( $group ) {
5084 wfDeprecated( __METHOD__, '1.29' );
5085 return UserGroupMembership::getGroupName( $group );
5086 }
5087
5096 public static function getGroupMember( $group, $username = '#' ) {
5097 wfDeprecated( __METHOD__, '1.29' );
5098 return UserGroupMembership::getGroupMemberName( $group, $username );
5099 }
5100
5107 public static function getAllGroups() {
5109 return array_values( array_diff(
5110 array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
5111 self::getImplicitGroups()
5112 ) );
5113 }
5114
5119 public static function getAllRights() {
5120 if ( self::$mAllRights === false ) {
5121 global $wgAvailableRights;
5122 if ( count( $wgAvailableRights ) ) {
5123 self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
5124 } else {
5125 self::$mAllRights = self::$mCoreRights;
5126 }
5127 Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
5128 }
5129 return self::$mAllRights;
5130 }
5131
5138 public static function getImplicitGroups() {
5139 global $wgImplicitGroups;
5140 return $wgImplicitGroups;
5141 }
5142
5150 public static function getGroupPage( $group ) {
5151 wfDeprecated( __METHOD__, '1.29' );
5152 return UserGroupMembership::getGroupPage( $group );
5153 }
5154
5165 public static function makeGroupLinkHTML( $group, $text = '' ) {
5166 wfDeprecated( __METHOD__, '1.29' );
5167
5168 if ( $text == '' ) {
5169 $text = UserGroupMembership::getGroupName( $group );
5170 }
5171 $title = UserGroupMembership::getGroupPage( $group );
5172 if ( $title ) {
5173 return MediaWikiServices::getInstance()
5174 ->getLinkRenderer()->makeLink( $title, $text );
5175 } else {
5176 return htmlspecialchars( $text );
5177 }
5178 }
5179
5190 public static function makeGroupLinkWiki( $group, $text = '' ) {
5191 wfDeprecated( __METHOD__, '1.29' );
5192
5193 if ( $text == '' ) {
5194 $text = UserGroupMembership::getGroupName( $group );
5195 }
5196 $title = UserGroupMembership::getGroupPage( $group );
5197 if ( $title ) {
5198 $page = $title->getFullText();
5199 return "[[$page|$text]]";
5200 } else {
5201 return $text;
5202 }
5203 }
5204
5214 public static function changeableByGroup( $group ) {
5216
5217 $groups = [
5218 'add' => [],
5219 'remove' => [],
5220 'add-self' => [],
5221 'remove-self' => []
5222 ];
5223
5224 if ( empty( $wgAddGroups[$group] ) ) {
5225 // Don't add anything to $groups
5226 } elseif ( $wgAddGroups[$group] === true ) {
5227 // You get everything
5228 $groups['add'] = self::getAllGroups();
5229 } elseif ( is_array( $wgAddGroups[$group] ) ) {
5230 $groups['add'] = $wgAddGroups[$group];
5231 }
5232
5233 // Same thing for remove
5234 if ( empty( $wgRemoveGroups[$group] ) ) {
5235 // Do nothing
5236 } elseif ( $wgRemoveGroups[$group] === true ) {
5237 $groups['remove'] = self::getAllGroups();
5238 } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5239 $groups['remove'] = $wgRemoveGroups[$group];
5240 }
5241
5242 // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5243 if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5244 foreach ( $wgGroupsAddToSelf as $key => $value ) {
5245 if ( is_int( $key ) ) {
5246 $wgGroupsAddToSelf['user'][] = $value;
5247 }
5248 }
5249 }
5250
5251 if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5252 foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5253 if ( is_int( $key ) ) {
5254 $wgGroupsRemoveFromSelf['user'][] = $value;
5255 }
5256 }
5257 }
5258
5259 // Now figure out what groups the user can add to him/herself
5260 if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5261 // Do nothing
5262 } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5263 // No idea WHY this would be used, but it's there
5264 $groups['add-self'] = self::getAllGroups();
5265 } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5266 $groups['add-self'] = $wgGroupsAddToSelf[$group];
5267 }
5268
5269 if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5270 // Do nothing
5271 } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5272 $groups['remove-self'] = self::getAllGroups();
5273 } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5274 $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5275 }
5276
5277 return $groups;
5278 }
5279
5287 public function changeableGroups() {
5288 if ( $this->isAllowed( 'userrights' ) ) {
5289 // This group gives the right to modify everything (reverse-
5290 // compatibility with old "userrights lets you change
5291 // everything")
5292 // Using array_merge to make the groups reindexed
5293 $all = array_merge( self::getAllGroups() );
5294 return [
5295 'add' => $all,
5296 'remove' => $all,
5297 'add-self' => [],
5298 'remove-self' => []
5299 ];
5300 }
5301
5302 // Okay, it's not so simple, we will have to go through the arrays
5303 $groups = [
5304 'add' => [],
5305 'remove' => [],
5306 'add-self' => [],
5307 'remove-self' => []
5308 ];
5309 $addergroups = $this->getEffectiveGroups();
5310
5311 foreach ( $addergroups as $addergroup ) {
5312 $groups = array_merge_recursive(
5313 $groups, $this->changeableByGroup( $addergroup )
5314 );
5315 $groups['add'] = array_unique( $groups['add'] );
5316 $groups['remove'] = array_unique( $groups['remove'] );
5317 $groups['add-self'] = array_unique( $groups['add-self'] );
5318 $groups['remove-self'] = array_unique( $groups['remove-self'] );
5319 }
5320 return $groups;
5321 }
5322
5329 public function incEditCount() {
5330 wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
5331 function () {
5332 $this->incEditCountImmediate();
5333 },
5334 __METHOD__
5335 );
5336 }
5337
5343 public function incEditCountImmediate() {
5344 if ( $this->isAnon() ) {
5345 return;
5346 }
5347
5348 $dbw = wfGetDB( DB_MASTER );
5349 // No rows will be "affected" if user_editcount is NULL
5350 $dbw->update(
5351 'user',
5352 [ 'user_editcount=user_editcount+1' ],
5353 [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
5354 __METHOD__
5355 );
5356 // Lazy initialization check...
5357 if ( $dbw->affectedRows() == 0 ) {
5358 // Now here's a goddamn hack...
5359 $dbr = wfGetDB( DB_REPLICA );
5360 if ( $dbr !== $dbw ) {
5361 // If we actually have a replica DB server, the count is
5362 // at least one behind because the current transaction
5363 // has not been committed and replicated.
5364 $this->mEditCount = $this->initEditCount( 1 );
5365 } else {
5366 // But if DB_REPLICA is selecting the master, then the
5367 // count we just read includes the revision that was
5368 // just added in the working transaction.
5369 $this->mEditCount = $this->initEditCount();
5370 }
5371 } else {
5372 if ( $this->mEditCount === null ) {
5373 $this->getEditCount();
5374 $dbr = wfGetDB( DB_REPLICA );
5375 $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
5376 } else {
5377 $this->mEditCount++;
5378 }
5379 }
5380 // Edit count in user cache too
5381 $this->invalidateCache();
5382 }
5383
5390 protected function initEditCount( $add = 0 ) {
5391 // Pull from a replica DB to be less cruel to servers
5392 // Accuracy isn't the point anyway here
5393 $dbr = wfGetDB( DB_REPLICA );
5394 $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
5395 $count = (int)$dbr->selectField(
5396 [ 'revision' ] + $actorWhere['tables'],
5397 'COUNT(*)',
5398 [ $actorWhere['conds'] ],
5399 __METHOD__,
5400 [],
5401 $actorWhere['joins']
5402 );
5403 $count = $count + $add;
5404
5405 $dbw = wfGetDB( DB_MASTER );
5406 $dbw->update(
5407 'user',
5408 [ 'user_editcount' => $count ],
5409 [ 'user_id' => $this->getId() ],
5410 __METHOD__
5411 );
5412
5413 return $count;
5414 }
5415
5423 public static function getRightDescription( $right ) {
5424 $key = "right-$right";
5425 $msg = wfMessage( $key );
5426 return $msg->isDisabled() ? $right : $msg->text();
5427 }
5428
5436 public static function getGrantName( $grant ) {
5437 $key = "grant-$grant";
5438 $msg = wfMessage( $key );
5439 return $msg->isDisabled() ? $grant : $msg->text();
5440 }
5441
5462 public function addNewUserLogEntry( $action = false, $reason = '' ) {
5463 return true; // disabled
5464 }
5465
5475 $this->addNewUserLogEntry( 'autocreate' );
5476
5477 return true;
5478 }
5479
5485 protected function loadOptions( $data = null ) {
5486 $this->load();
5487
5488 if ( $this->mOptionsLoaded ) {
5489 return;
5490 }
5491
5492 $this->mOptions = self::getDefaultOptions();
5493
5494 if ( !$this->getId() ) {
5495 // For unlogged-in users, load language/variant options from request.
5496 // There's no need to do it for logged-in users: they can set preferences,
5497 // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5498 // so don't override user's choice (especially when the user chooses site default).
5499 $variant = MediaWikiServices::getInstance()->getContentLanguage()->getDefaultVariant();
5500 $this->mOptions['variant'] = $variant;
5501 $this->mOptions['language'] = $variant;
5502 $this->mOptionsLoaded = true;
5503 return;
5504 }
5505
5506 // Maybe load from the object
5507 if ( !is_null( $this->mOptionOverrides ) ) {
5508 wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5509 foreach ( $this->mOptionOverrides as $key => $value ) {
5510 $this->mOptions[$key] = $value;
5511 }
5512 } else {
5513 if ( !is_array( $data ) ) {
5514 wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5515 // Load from database
5516 $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5517 ? wfGetDB( DB_MASTER )
5518 : wfGetDB( DB_REPLICA );
5519
5520 $res = $dbr->select(
5521 'user_properties',
5522 [ 'up_property', 'up_value' ],
5523 [ 'up_user' => $this->getId() ],
5524 __METHOD__
5525 );
5526
5527 $this->mOptionOverrides = [];
5528 $data = [];
5529 foreach ( $res as $row ) {
5530 // Convert '0' to 0. PHP's boolean conversion considers them both
5531 // false, but e.g. JavaScript considers the former as true.
5532 // @todo: T54542 Somehow determine the desired type (string/int/bool)
5533 // and convert all values here.
5534 if ( $row->up_value === '0' ) {
5535 $row->up_value = 0;
5536 }
5537 $data[$row->up_property] = $row->up_value;
5538 }
5539 }
5540
5541 foreach ( $data as $property => $value ) {
5542 $this->mOptionOverrides[$property] = $value;
5543 $this->mOptions[$property] = $value;
5544 }
5545 }
5546
5547 // Replace deprecated language codes
5548 $this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
5549 $this->mOptions['language']
5550 );
5551
5552 $this->mOptionsLoaded = true;
5553
5554 Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5555 }
5556
5562 protected function saveOptions() {
5563 $this->loadOptions();
5564
5565 // Not using getOptions(), to keep hidden preferences in database
5566 $saveOptions = $this->mOptions;
5567
5568 // Allow hooks to abort, for instance to save to a global profile.
5569 // Reset options to default state before saving.
5570 if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5571 return;
5572 }
5573
5574 $userId = $this->getId();
5575
5576 $insert_rows = []; // all the new preference rows
5577 foreach ( $saveOptions as $key => $value ) {
5578 // Don't bother storing default values
5579 $defaultOption = self::getDefaultOption( $key );
5580 if ( ( $defaultOption === null && $value !== false && $value !== null )
5581 || $value != $defaultOption
5582 ) {
5583 $insert_rows[] = [
5584 'up_user' => $userId,
5585 'up_property' => $key,
5586 'up_value' => $value,
5587 ];
5588 }
5589 }
5590
5591 $dbw = wfGetDB( DB_MASTER );
5592
5593 $res = $dbw->select( 'user_properties',
5594 [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5595
5596 // Find prior rows that need to be removed or updated. These rows will
5597 // all be deleted (the latter so that INSERT IGNORE applies the new values).
5598 $keysDelete = [];
5599 foreach ( $res as $row ) {
5600 if ( !isset( $saveOptions[$row->up_property] )
5601 || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5602 ) {
5603 $keysDelete[] = $row->up_property;
5604 }
5605 }
5606
5607 if ( count( $keysDelete ) ) {
5608 // Do the DELETE by PRIMARY KEY for prior rows.
5609 // In the past a very large portion of calls to this function are for setting
5610 // 'rememberpassword' for new accounts (a preference that has since been removed).
5611 // Doing a blanket per-user DELETE for new accounts with no rows in the table
5612 // caused gap locks on [max user ID,+infinity) which caused high contention since
5613 // updates would pile up on each other as they are for higher (newer) user IDs.
5614 // It might not be necessary these days, but it shouldn't hurt either.
5615 $dbw->delete( 'user_properties',
5616 [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5617 }
5618 // Insert the new preference rows
5619 $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5620 }
5621
5628 public static function selectFields() {
5629 wfDeprecated( __METHOD__, '1.31' );
5630 return [
5631 'user_id',
5632 'user_name',
5633 'user_real_name',
5634 'user_email',
5635 'user_touched',
5636 'user_token',
5637 'user_email_authenticated',
5638 'user_email_token',
5639 'user_email_token_expires',
5640 'user_registration',
5641 'user_editcount',
5642 ];
5643 }
5644
5654 public static function getQueryInfo() {
5656
5657 $ret = [
5658 'tables' => [ 'user' ],
5659 'fields' => [
5660 'user_id',
5661 'user_name',
5662 'user_real_name',
5663 'user_email',
5664 'user_touched',
5665 'user_token',
5666 'user_email_authenticated',
5667 'user_email_token',
5668 'user_email_token_expires',
5669 'user_registration',
5670 'user_editcount',
5671 ],
5672 'joins' => [],
5673 ];
5674
5675 // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
5676 // but it does little harm and might be needed for write callers loading a User.
5678 $ret['tables']['user_actor'] = 'actor';
5679 $ret['fields'][] = 'user_actor.actor_id';
5680 $ret['joins']['user_actor'] = [
5681 ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
5682 [ 'user_actor.actor_user = user_id' ]
5683 ];
5684 }
5685
5686 return $ret;
5687 }
5688
5696 static function newFatalPermissionDeniedStatus( $permission ) {
5697 global $wgLang;
5698
5699 $groups = [];
5700 foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5701 $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5702 }
5703
5704 if ( $groups ) {
5705 return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5706 } else {
5707 return Status::newFatal( 'badaccess-group0' );
5708 }
5709 }
5710
5720 public function getInstanceForUpdate() {
5721 if ( !$this->getId() ) {
5722 return null; // anon
5723 }
5724
5725 $user = self::newFromId( $this->getId() );
5726 if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5727 return null;
5728 }
5729
5730 return $user;
5731 }
5732
5740 public function equals( UserIdentity $user ) {
5741 // XXX it's not clear whether central ID providers are supposed to obey this
5742 return $this->getName() === $user->getName();
5743 }
5744}
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 local wiki users.
$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...
$wgClockSkewFudge
Clock skew or the one-second resolution of time() can occasionally cause cache problems when the user...
$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...
$wgBlockAllowsUTEdit
Set this to true to allow blocked users to edit their own user talk page.
$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.
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
$messages
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition Setup.php:121
foreach( $wgExtensionFunctions as $func) if(!defined('MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition Setup.php:964
$wgUseEnotif
Definition Setup.php:440
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:747
$wgLang
Definition Setup.php:910
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:1553
prevents( $action, $x=null)
Get/set whether the Block prevents a given action.
Definition Block.php:1071
static newFromID( $id)
Load a blocked user from their block id.
Definition Block.php:184
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition Block.php:1213
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition Block.php:1294
const TYPE_RANGE
Definition Block.php:85
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the 'BlockID' cookie.
Definition Block.php:1589
const TYPE_USER
Definition Block.php:83
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:1174
const TYPE_IP
Definition Block.php:84
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:437
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:291
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition Revision.php:114
static newFromIDs( $ids)
Definition UserArray.php:45
static singleton()
Definition UserCache.php:34
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:47
loadFromSession()
Load user data from the session.
Definition User.php:1367
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition User.php:3934
string $mEmailToken
Definition User.php:228
string $mToken
Definition User.php:224
string $mTouched
TS_MW timestamp from the DB.
Definition User.php:220
logout()
Log this user out.
Definition User.php:4140
getOptions( $flags=0)
Get all user's options.
Definition User.php:3199
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:1162
Block $mBlockedFromCreateAccount
Definition User.php:304
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2462
addToDatabase()
Add this existing user object to the database.
Definition User.php:4377
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition User.php:3514
isBlocked( $bFromSlave=true)
Check if user is blocked.
Definition User.php:2280
updateActorId(IDatabase $dbw)
Update the actor ID after an insert.
Definition User.php:4449
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:5033
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:592
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition User.php:3449
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition User.php:901
invalidateCache()
Immediately touch the user data cache for this account.
Definition User.php:2807
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition User.php:92
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition User.php:3062
array $mOptionOverrides
Definition User.php:238
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition User.php:4521
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition User.php:5654
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition User.php:245
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition User.php:1121
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition User.php:4937
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:3364
getBlockedStatus( $bFromSlave=true)
Get blocking information.
Definition User.php:1833
int null $mActorId
Definition User.php:213
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition User.php:5214
const VERSION
@const int Serialized record version.
Definition User.php:68
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:4620
static getAllGroups()
Return the set of defined explicit groups.
Definition User.php:5107
bool $mAllowUsertalk
Definition User.php:301
string $mEmailTokenExpires
Definition User.php:230
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition User.php:4104
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:4644
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition User.php:2000
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition User.php:307
static $mAllRights
String Cached results of getAllRights()
Definition User.php:204
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition User.php:5740
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition User.php:4796
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition User.php:3727
array $mEffectiveGroups
Definition User.php:280
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2354
string $mQuickTouched
TS_MW timestamp from cache.
Definition User.php:222
const INVALID_TOKEN
@const string An invalid value for user_token
Definition User.php:56
isSafeToLoad()
Test if it's safe to load this User object.
Definition User.php:347
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition User.php:5013
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition User.php:4882
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition User.php:4467
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:464
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:2873
setName( $str)
Set the user name.
Definition User.php:2489
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition User.php:2959
Block $mGlobalBlock
Definition User.php:286
static $mCoreRights
Array of Strings Core rights.
Definition User.php:119
getId()
Get the user's ID.
Definition User.php:2437
getRealName()
Get the user's real name.
Definition User.php:3144
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition User.php:5485
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition User.php:3230
getRegistration()
Get the timestamp of account creation.
Definition User.php:4923
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition User.php:2067
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2368
string $mRealName
Definition User.php:215
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition User.php:682
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition User.php:1346
getMutableCacheKeys(WANObjectCache $cache)
Definition User.php:517
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition User.php:3279
isNewbie()
Determine whether the user is a newbie.
Definition User.php:4558
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:951
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition User.php:2705
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition User.php:1436
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition User.php:4778
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:1294
static purge( $wikiId, $userId)
Definition User.php:494
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition User.php:3242
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition User.php:1748
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition User.php:4868
string $mEmail
Definition User.php:218
loadDefaults( $name=false)
Set cached properties to default.
Definition User.php:1307
trackBlockWithCookie()
Set the 'BlockID' cookie depending on block type and user authentication status.
Definition User.php:1402
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition User.php:3171
touch()
Update the "touched" timestamp for the user.
Definition User.php:2824
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition User.php:4603
isPingLimitable()
Is this user subject to rate limiting?
Definition User.php:2113
clearSharedCache( $mode='changed')
Clear user data from memcached.
Definition User.php:2778
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition User.php:1238
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition User.php:778
getToken( $forceCreation=true)
Get the user's current token.
Definition User.php:2986
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:615
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition User.php:2908
confirmEmail()
Mark the e-mail address confirmed.
Definition User.php:4809
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition User.php:4990
setId( $v)
Set the user and reload all fields according to a given ID.
Definition User.php:2453
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition User.php:4963
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition User.php:268
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition User.php:1773
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition User.php:1081
bool $mLocked
Definition User.php:288
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:5628
isHidden()
Check if user account is hidden.
Definition User.php:2418
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition User.php:732
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:2021
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition User.php:1606
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition User.php:1046
static getDefaultOption( $opt)
Get a given default option value.
Definition User.php:1818
const IGNORE_USER_RIGHTS
Definition User.php:84
getDatePreference()
Get the user's preferred date format.
Definition User.php:3494
string $mHash
Definition User.php:274
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition User.php:1198
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition User.php:5150
array $mRights
Definition User.php:276
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition User.php:4266
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:4674
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:1488
setPassword( $str)
Set the password and reset the random token.
Definition User.php:2895
string $mBlockedby
Definition User.php:272
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition User.php:4769
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition User.php:4530
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition User.php:756
incEditCountImmediate()
Increment the user's edit-count field.
Definition User.php:5343
static getAllRights()
Get a list of all available permissions.
Definition User.php:5119
getNewtalk()
Check if the user has new messages.
Definition User.php:2575
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:5462
validateCache( $timestamp)
Validate the cache for this account.
Definition User.php:2839
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition User.php:3869
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition User.php:1616
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition User.php:891
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition User.php:4826
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition User.php:4840
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:819
setOption( $oname, $val)
Set the given option for a user.
Definition User.php:3258
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition User.php:5562
static getRightDescription( $right)
Get the description of a given right.
Definition User.php:5423
getEditCount()
Get the user's edit count.
Definition User.php:3692
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition User.php:2500
const CHECK_USER_RIGHTS
Definition User.php:79
UserGroupMembership[] $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition User.php:236
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition User.php:1640
isValidPassword( $password)
Is the input a valid password for this user?
Definition User.php:1151
getBlock( $bFromSlave=true)
Get the block affecting the user, or null if the user is not blocked.
Definition User.php:2290
array $mFormerGroups
Definition User.php:284
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition User.php:4499
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition User.php:4480
getFormerGroups()
Returns the groups the user has belonged to.
Definition User.php:3668
setRealName( $str)
Set the user's real name.
Definition User.php:3156
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition User.php:658
int $mId
Cache variables.
Definition User.php:209
getTitleKey()
Get the user's name escaped by underscores.
Definition User.php:2567
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:971
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name.
Definition User.php:5190
getTouched()
Get the user touched timestamp.
Definition User.php:2851
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition User.php:4725
Block $mBlock
Definition User.php:298
isLocked()
Check if user account is locked.
Definition User.php:2401
array $mOptions
Definition User.php:292
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition User.php:1710
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:2921
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition User.php:4908
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition User.php:2665
__toString()
Definition User.php:329
isBlockedFrom( $title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition User.php:2302
getUserPage()
Get this user's personal page title.
Definition User.php:4539
isIPRange()
Is the user an IP range?
Definition User.php:982
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition User.php:263
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition User.php:4751
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition User.php:4851
getBlockId()
If user is blocked, return the ID for the block.
Definition User.php:2341
isBot()
Definition User.php:3809
string $mRegistration
Definition User.php:232
__construct()
Lightweight constructor for an anonymous user.
Definition User.php:322
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition User.php:2725
getStubThreshold()
Get the user preferred stub threshold.
Definition User.php:3533
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition User.php:2138
string $mDatePreference
Definition User.php:270
static isValidUserName( $name)
Is the input a valid username?
Definition User.php:997
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition User.php:4686
getTalkPage()
Get this user's talk page title.
Definition User.php:4548
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:911
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition User.php:529
getCacheKey(WANObjectCache $cache)
Definition User.php:505
initEditCount( $add=0)
Initialize user_editcount from data out of the revision table.
Definition User.php:5390
saveSettings()
Save this user's settings into the database.
Definition User.php:4188
static $idCacheByName
Definition User.php:309
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition User.php:2638
getBlockFromCookieValue( $blockCookieVal)
Try to load a Block from an ID given in a cookie value.
Definition User.php:1949
static getGrantName( $grant)
Get the name of a given grant.
Definition User.php:5436
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:5696
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
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition User.php:364
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition User.php:2323
getRights()
Get the permissions this user has.
Definition User.php:3548
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition User.php:4660
string $mEmailAuthenticated
Definition User.php:226
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition User.php:74
const TOKEN_LENGTH
@const int Number of characters in user_token field.
Definition User.php:51
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:2680
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition User.php:4301
static getGroupMember( $group, $username='#')
Get the localized descriptive name for a member of a group, if it exists.
Definition User.php:5096
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition User.php:5474
doLogout()
Clear the user's session, and reset the instance cache.
Definition User.php:4152
setItemLoaded( $item)
Set that an item has been loaded.
Definition User.php:1356
incEditCount()
Deferred version of incEditCountImmediate()
Definition User.php:5329
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition User.php:250
blockedFor()
If user is blocked, return the specified reason for the block.
Definition User.php:2332
static getGroupName( $group)
Get the localized descriptive name for a group, if it exists.
Definition User.php:5083
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition User.php:3341
int $mEditCount
Definition User.php:234
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition User.php:3307
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition User.php:5165
bool $mHideName
Definition User.php:290
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:5138
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition User.php:5287
string $mName
Definition User.php:211
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition User.php:2613
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:278
WebRequest $mRequest
Definition User.php:295
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition User.php:4568
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition User.php:5720
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition User.php:1692
const EDIT_TOKEN_SUFFIX
Global constant made accessible as class constants so that autoloader magic can be used.
Definition User.php:63
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition User.php:630
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition User.php:4036
array $mImplicitGroups
Definition User.php:282
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:48
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
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
const SCHEMA_COMPAT_READ_NEW
Definition Defines.php:287
const NS_USER
Definition Defines.php:66
const NS_MAIN
Definition Defines.php:64
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:286
const SCHEMA_COMPAT_NEW
Definition Defines.php:291
const NS_USER_TALK
Definition Defines.php:67
this hook is for auditing only $req
Definition hooks.txt:1018
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:2857
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:2880
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1841
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED since 1.16! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED 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:2042
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:2105
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:1305
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:2050
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:2885
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:994
null for the local wiki Added in
Definition hooks.txt:1627
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition hooks.txt:895
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:2055
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:2054
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:302
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:815
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
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:1818
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:247
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
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