MediaWiki REL1_30
User.php
Go to the documentation of this file.
1<?php
23use IPSet\IPSet;
30use Wikimedia\ScopedCallback;
33
39define( 'EDIT_TOKEN_SUFFIX', Token::SUFFIX );
40
51class User implements IDBAccessObject {
55 const TOKEN_LENGTH = 32;
56
60 const INVALID_TOKEN = '*** INVALID ***';
61
68
72 const VERSION = 11;
73
79
83 const CHECK_USER_RIGHTS = true;
84
88 const IGNORE_USER_RIGHTS = false;
89
96 protected static $mCacheVars = [
97 // user table
98 'mId',
99 'mName',
100 'mRealName',
101 'mEmail',
102 'mTouched',
103 'mToken',
104 'mEmailAuthenticated',
105 'mEmailToken',
106 'mEmailTokenExpires',
107 'mRegistration',
108 'mEditCount',
109 // user_groups table
110 'mGroupMemberships',
111 // user_properties table
112 'mOptionOverrides',
113 ];
114
121 protected static $mCoreRights = [
122 'apihighlimits',
123 'applychangetags',
124 'autoconfirmed',
125 'autocreateaccount',
126 'autopatrol',
127 'bigdelete',
128 'block',
129 'blockemail',
130 'bot',
131 'browsearchive',
132 'changetags',
133 'createaccount',
134 'createpage',
135 'createtalk',
136 'delete',
137 'deletechangetags',
138 'deletedhistory',
139 'deletedtext',
140 'deletelogentry',
141 'deleterevision',
142 'edit',
143 'editcontentmodel',
144 'editinterface',
145 'editprotected',
146 'editmyoptions',
147 'editmyprivateinfo',
148 'editmyusercss',
149 'editmyuserjs',
150 'editmywatchlist',
151 'editsemiprotected',
152 'editusercss',
153 'edituserjs',
154 'hideuser',
155 'import',
156 'importupload',
157 'ipblock-exempt',
158 'managechangetags',
159 'markbotedits',
160 'mergehistory',
161 'minoredit',
162 'move',
163 'movefile',
164 'move-categorypages',
165 'move-rootuserpages',
166 'move-subpages',
167 'nominornewtalk',
168 'noratelimit',
169 'override-export-depth',
170 'pagelang',
171 'patrol',
172 'patrolmarks',
173 'protect',
174 'purge',
175 'read',
176 'reupload',
177 'reupload-own',
178 'reupload-shared',
179 'rollback',
180 'sendemail',
181 'siteadmin',
182 'suppressionlog',
183 'suppressredirect',
184 'suppressrevision',
185 'unblockself',
186 'undelete',
187 'unwatchedpages',
188 'upload',
189 'upload_by_url',
190 'userrights',
191 'userrights-interwiki',
192 'viewmyprivateinfo',
193 'viewmywatchlist',
194 'viewsuppressed',
195 'writeapi',
196 ];
197
201 protected static $mAllRights = false;
202
204 // @{
206 public $mId;
208 public $mName;
211
213 public $mEmail;
215 public $mTouched;
217 protected $mQuickTouched;
219 protected $mToken;
223 protected $mEmailToken;
227 protected $mRegistration;
229 protected $mEditCount;
234 private $mGroups;
239 // @}
240
244 // @{
246
250 protected $mLoadedItems = [];
251 // @}
252
262 public $mFrom;
263
267 protected $mNewtalk;
273 protected $mHash;
275 public $mRights;
277 protected $mBlockreason;
283 protected $mFormerGroups;
285 protected $mGlobalBlock;
287 protected $mLocked;
291 public $mOptions;
292
294 private $mRequest;
295
297 public $mBlock;
298
301
304
306 protected $queryFlagsUsed = self::READ_NORMAL;
307
308 public static $idCacheByName = [];
309
320 public function __construct() {
321 $this->clearInstanceCache( 'defaults' );
322 }
323
327 public function __toString() {
328 return (string)$this->getName();
329 }
330
345 public function isSafeToLoad() {
346 global $wgFullyInitialised;
347
348 // The user is safe to load if:
349 // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
350 // * mLoadedItems === true (already loaded)
351 // * mFrom !== 'session' (sessions not involved at all)
352
353 return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
354 $this->mLoadedItems === true || $this->mFrom !== 'session';
355 }
356
362 public function load( $flags = self::READ_NORMAL ) {
363 global $wgFullyInitialised;
364
365 if ( $this->mLoadedItems === true ) {
366 return;
367 }
368
369 // Set it now to avoid infinite recursion in accessors
370 $oldLoadedItems = $this->mLoadedItems;
371 $this->mLoadedItems = true;
372 $this->queryFlagsUsed = $flags;
373
374 // If this is called too early, things are likely to break.
375 if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
376 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
377 ->warning( 'User::loadFromSession called before the end of Setup.php', [
378 'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
379 ] );
380 $this->loadDefaults();
381 $this->mLoadedItems = $oldLoadedItems;
382 return;
383 }
384
385 switch ( $this->mFrom ) {
386 case 'defaults':
387 $this->loadDefaults();
388 break;
389 case 'name':
390 // Make sure this thread sees its own changes
391 if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
392 $flags |= self::READ_LATEST;
393 $this->queryFlagsUsed = $flags;
394 }
395
396 $this->mId = self::idFromName( $this->mName, $flags );
397 if ( !$this->mId ) {
398 // Nonexistent user placeholder object
399 $this->loadDefaults( $this->mName );
400 } else {
401 $this->loadFromId( $flags );
402 }
403 break;
404 case 'id':
405 $this->loadFromId( $flags );
406 break;
407 case 'session':
408 if ( !$this->loadFromSession() ) {
409 // Loading from session failed. Load defaults.
410 $this->loadDefaults();
411 }
412 Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
413 break;
414 default:
415 throw new UnexpectedValueException(
416 "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
417 }
418 }
419
425 public function loadFromId( $flags = self::READ_NORMAL ) {
426 if ( $this->mId == 0 ) {
427 // Anonymous users are not in the database (don't need cache)
428 $this->loadDefaults();
429 return false;
430 }
431
432 // Try cache (unless this needs data from the master DB).
433 // NOTE: if this thread called saveSettings(), the cache was cleared.
434 $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
435 if ( $latest ) {
436 if ( !$this->loadFromDatabase( $flags ) ) {
437 // Can't load from ID
438 return false;
439 }
440 } else {
441 $this->loadFromCache();
442 }
443
444 $this->mLoadedItems = true;
445 $this->queryFlagsUsed = $flags;
446
447 return true;
448 }
449
455 public static function purge( $wikiId, $userId ) {
456 $cache = ObjectCache::getMainWANInstance();
457 $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
458 $cache->delete( $key );
459 }
460
466 protected function getCacheKey( WANObjectCache $cache ) {
467 return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId );
468 }
469
476 $id = $this->getId();
477
478 return $id ? [ $this->getCacheKey( $cache ) ] : [];
479 }
480
487 protected function loadFromCache() {
488 $cache = ObjectCache::getMainWANInstance();
489 $data = $cache->getWithSetCallback(
490 $this->getCacheKey( $cache ),
491 $cache::TTL_HOUR,
492 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
493 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
494 wfDebug( "User: cache miss for user {$this->mId}\n" );
495
496 $this->loadFromDatabase( self::READ_NORMAL );
497 $this->loadGroups();
498 $this->loadOptions();
499
500 $data = [];
501 foreach ( self::$mCacheVars as $name ) {
502 $data[$name] = $this->$name;
503 }
504
505 $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
506
507 // if a user group membership is about to expire, the cache needs to
508 // expire at that time (T163691)
509 foreach ( $this->mGroupMemberships as $ugm ) {
510 if ( $ugm->getExpiry() ) {
511 $secondsUntilExpiry = wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
512 if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
513 $ttl = $secondsUntilExpiry;
514 }
515 }
516 }
517
518 return $data;
519 },
520 [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
521 );
522
523 // Restore from cache
524 foreach ( self::$mCacheVars as $name ) {
525 $this->$name = $data[$name];
526 }
527
528 return true;
529 }
530
532 // @{
533
550 public static function newFromName( $name, $validate = 'valid' ) {
551 if ( $validate === true ) {
552 $validate = 'valid';
553 }
554 $name = self::getCanonicalName( $name, $validate );
555 if ( $name === false ) {
556 return false;
557 } else {
558 // Create unloaded user object
559 $u = new User;
560 $u->mName = $name;
561 $u->mFrom = 'name';
562 $u->setItemLoaded( 'name' );
563 return $u;
564 }
565 }
566
573 public static function newFromId( $id ) {
574 $u = new User;
575 $u->mId = $id;
576 $u->mFrom = 'id';
577 $u->setItemLoaded( 'id' );
578 return $u;
579 }
580
592 public static function newFromConfirmationCode( $code, $flags = 0 ) {
593 $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
594 ? wfGetDB( DB_MASTER )
595 : wfGetDB( DB_REPLICA );
596
597 $id = $db->selectField(
598 'user',
599 'user_id',
600 [
601 'user_email_token' => md5( $code ),
602 'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
603 ]
604 );
605
606 return $id ? self::newFromId( $id ) : null;
607 }
608
616 public static function newFromSession( WebRequest $request = null ) {
617 $user = new User;
618 $user->mFrom = 'session';
619 $user->mRequest = $request;
620 return $user;
621 }
622
637 public static function newFromRow( $row, $data = null ) {
638 $user = new User;
639 $user->loadFromRow( $row, $data );
640 return $user;
641 }
642
678 public static function newSystemUser( $name, $options = [] ) {
679 $options += [
680 'validate' => 'valid',
681 'create' => true,
682 'steal' => false,
683 ];
684
685 $name = self::getCanonicalName( $name, $options['validate'] );
686 if ( $name === false ) {
687 return null;
688 }
689
691 $row = $dbr->selectRow(
692 'user',
693 self::selectFields(),
694 [ 'user_name' => $name ],
695 __METHOD__
696 );
697 if ( !$row ) {
698 // Try the master database...
699 $dbw = wfGetDB( DB_MASTER );
700 $row = $dbw->selectRow(
701 'user',
702 self::selectFields(),
703 [ 'user_name' => $name ],
704 __METHOD__
705 );
706 }
707
708 if ( !$row ) {
709 // No user. Create it?
710 return $options['create']
711 ? self::createNew( $name, [ 'token' => self::INVALID_TOKEN ] )
712 : null;
713 }
714
715 $user = self::newFromRow( $row );
716
717 // A user is considered to exist as a non-system user if it can
718 // authenticate, or has an email set, or has a non-invalid token.
719 if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
720 AuthManager::singleton()->userCanAuthenticate( $name )
721 ) {
722 // User exists. Steal it?
723 if ( !$options['steal'] ) {
724 return null;
725 }
726
727 AuthManager::singleton()->revokeAccessForUser( $name );
728
729 $user->invalidateEmail();
730 $user->mToken = self::INVALID_TOKEN;
731 $user->saveSettings();
732 SessionManager::singleton()->preventSessionsForUser( $user->getName() );
733 }
734
735 return $user;
736 }
737
738 // @}
739
745 public static function whoIs( $id ) {
746 return UserCache::singleton()->getProp( $id, 'name' );
747 }
748
755 public static function whoIsReal( $id ) {
756 return UserCache::singleton()->getProp( $id, 'real_name' );
757 }
758
765 public static function idFromName( $name, $flags = self::READ_NORMAL ) {
766 $nt = Title::makeTitleSafe( NS_USER, $name );
767 if ( is_null( $nt ) ) {
768 // Illegal name
769 return null;
770 }
771
772 if ( !( $flags & self::READ_LATEST ) && isset( self::$idCacheByName[$name] ) ) {
773 return self::$idCacheByName[$name];
774 }
775
777 $db = wfGetDB( $index );
778
779 $s = $db->selectRow(
780 'user',
781 [ 'user_id' ],
782 [ 'user_name' => $nt->getText() ],
783 __METHOD__,
785 );
786
787 if ( $s === false ) {
788 $result = null;
789 } else {
790 $result = $s->user_id;
791 }
792
793 self::$idCacheByName[$name] = $result;
794
795 if ( count( self::$idCacheByName ) > 1000 ) {
796 self::$idCacheByName = [];
797 }
798
799 return $result;
800 }
801
805 public static function resetIdByNameCache() {
806 self::$idCacheByName = [];
807 }
808
825 public static function isIP( $name ) {
826 return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
827 || IP::isIPv6( $name );
828 }
829
836 public function isIPRange() {
837 return IP::isValidRange( $this->mName );
838 }
839
851 public static function isValidUserName( $name ) {
853
854 if ( $name == ''
855 || self::isIP( $name )
856 || strpos( $name, '/' ) !== false
857 || strlen( $name ) > $wgMaxNameChars
858 || $name != $wgContLang->ucfirst( $name )
859 ) {
860 return false;
861 }
862
863 // Ensure that the name can't be misresolved as a different title,
864 // such as with extra namespace keys at the start.
865 $parsed = Title::newFromText( $name );
866 if ( is_null( $parsed )
867 || $parsed->getNamespace()
868 || strcmp( $name, $parsed->getPrefixedText() ) ) {
869 return false;
870 }
871
872 // Check an additional blacklist of troublemaker characters.
873 // Should these be merged into the title char list?
874 $unicodeBlacklist = '/[' .
875 '\x{0080}-\x{009f}' . # iso-8859-1 control chars
876 '\x{00a0}' . # non-breaking space
877 '\x{2000}-\x{200f}' . # various whitespace
878 '\x{2028}-\x{202f}' . # breaks and control chars
879 '\x{3000}' . # ideographic space
880 '\x{e000}-\x{f8ff}' . # private use
881 ']/u';
882 if ( preg_match( $unicodeBlacklist, $name ) ) {
883 return false;
884 }
885
886 return true;
887 }
888
900 public static function isUsableName( $name ) {
902 // Must be a valid username, obviously ;)
903 if ( !self::isValidUserName( $name ) ) {
904 return false;
905 }
906
907 static $reservedUsernames = false;
908 if ( !$reservedUsernames ) {
909 $reservedUsernames = $wgReservedUsernames;
910 Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
911 }
912
913 // Certain names may be reserved for batch processes.
914 foreach ( $reservedUsernames as $reserved ) {
915 if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
916 $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
917 }
918 if ( $reserved == $name ) {
919 return false;
920 }
921 }
922 return true;
923 }
924
935 public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
936 if ( $groups === [] ) {
938 }
939
940 $groups = array_unique( (array)$groups );
941 $limit = min( 5000, $limit );
942
943 $conds = [ 'ug_group' => $groups ];
944 if ( $after !== null ) {
945 $conds[] = 'ug_user > ' . (int)$after;
946 }
947
949 $ids = $dbr->selectFieldValues(
950 'user_groups',
951 'ug_user',
952 $conds,
953 __METHOD__,
954 [
955 'DISTINCT' => true,
956 'ORDER BY' => 'ug_user',
957 'LIMIT' => $limit,
958 ]
959 ) ?: [];
960 return UserArray::newFromIDs( $ids );
961 }
962
975 public static function isCreatableName( $name ) {
977
978 // Ensure that the username isn't longer than 235 bytes, so that
979 // (at least for the builtin skins) user javascript and css files
980 // will work. (T25080)
981 if ( strlen( $name ) > 235 ) {
982 wfDebugLog( 'username', __METHOD__ .
983 ": '$name' invalid due to length" );
984 return false;
985 }
986
987 // Preg yells if you try to give it an empty string
988 if ( $wgInvalidUsernameCharacters !== '' ) {
989 if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
990 wfDebugLog( 'username', __METHOD__ .
991 ": '$name' invalid due to wgInvalidUsernameCharacters" );
992 return false;
993 }
994 }
995
996 return self::isUsableName( $name );
997 }
998
1005 public function isValidPassword( $password ) {
1006 // simple boolean wrapper for getPasswordValidity
1007 return $this->getPasswordValidity( $password ) === true;
1008 }
1009
1016 public function getPasswordValidity( $password ) {
1017 $result = $this->checkPasswordValidity( $password );
1018 if ( $result->isGood() ) {
1019 return true;
1020 } else {
1021 $messages = [];
1022 foreach ( $result->getErrorsByType( 'error' ) as $error ) {
1023 $messages[] = $error['message'];
1024 }
1025 foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
1026 $messages[] = $warning['message'];
1027 }
1028 if ( count( $messages ) === 1 ) {
1029 return $messages[0];
1030 }
1031 return $messages;
1032 }
1033 }
1034
1052 public function checkPasswordValidity( $password ) {
1053 global $wgPasswordPolicy;
1054
1055 $upp = new UserPasswordPolicy(
1056 $wgPasswordPolicy['policies'],
1057 $wgPasswordPolicy['checks']
1058 );
1059
1060 $status = Status::newGood();
1061 $result = false; // init $result to false for the internal checks
1062
1063 if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1064 $status->error( $result );
1065 return $status;
1066 }
1067
1068 if ( $result === false ) {
1069 $status->merge( $upp->checkUserPassword( $this, $password ) );
1070 return $status;
1071 } elseif ( $result === true ) {
1072 return $status;
1073 } else {
1074 $status->error( $result );
1075 return $status; // the isValidPassword hook set a string $result and returned true
1076 }
1077 }
1078
1092 public static function getCanonicalName( $name, $validate = 'valid' ) {
1093 // Force usernames to capital
1094 global $wgContLang;
1095 $name = $wgContLang->ucfirst( $name );
1096
1097 # Reject names containing '#'; these will be cleaned up
1098 # with title normalisation, but then it's too late to
1099 # check elsewhere
1100 if ( strpos( $name, '#' ) !== false ) {
1101 return false;
1102 }
1103
1104 // Clean up name according to title rules,
1105 // but only when validation is requested (T14654)
1106 $t = ( $validate !== false ) ?
1107 Title::newFromText( $name, NS_USER ) : Title::makeTitle( NS_USER, $name );
1108 // Check for invalid titles
1109 if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1110 return false;
1111 }
1112
1113 // Reject various classes of invalid names
1114 $name = AuthManager::callLegacyAuthPlugin(
1115 'getCanonicalName', [ $t->getText() ], $t->getText()
1116 );
1117
1118 switch ( $validate ) {
1119 case false:
1120 break;
1121 case 'valid':
1122 if ( !self::isValidUserName( $name ) ) {
1123 $name = false;
1124 }
1125 break;
1126 case 'usable':
1127 if ( !self::isUsableName( $name ) ) {
1128 $name = false;
1129 }
1130 break;
1131 case 'creatable':
1132 if ( !self::isCreatableName( $name ) ) {
1133 $name = false;
1134 }
1135 break;
1136 default:
1137 throw new InvalidArgumentException(
1138 'Invalid parameter value for $validate in ' . __METHOD__ );
1139 }
1140 return $name;
1141 }
1142
1149 public static function randomPassword() {
1151 return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1152 }
1153
1162 public function loadDefaults( $name = false ) {
1163 $this->mId = 0;
1164 $this->mName = $name;
1165 $this->mRealName = '';
1166 $this->mEmail = '';
1167 $this->mOptionOverrides = null;
1168 $this->mOptionsLoaded = false;
1169
1170 $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1171 ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1172 if ( $loggedOut !== 0 ) {
1173 $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1174 } else {
1175 $this->mTouched = '1'; # Allow any pages to be cached
1176 }
1177
1178 $this->mToken = null; // Don't run cryptographic functions till we need a token
1179 $this->mEmailAuthenticated = null;
1180 $this->mEmailToken = '';
1181 $this->mEmailTokenExpires = null;
1182 $this->mRegistration = wfTimestamp( TS_MW );
1183 $this->mGroupMemberships = [];
1184
1185 Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1186 }
1187
1200 public function isItemLoaded( $item, $all = 'all' ) {
1201 return ( $this->mLoadedItems === true && $all === 'all' ) ||
1202 ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1203 }
1204
1210 protected function setItemLoaded( $item ) {
1211 if ( is_array( $this->mLoadedItems ) ) {
1212 $this->mLoadedItems[$item] = true;
1213 }
1214 }
1215
1221 private function loadFromSession() {
1222 // Deprecated hook
1223 $result = null;
1224 Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1225 if ( $result !== null ) {
1226 return $result;
1227 }
1228
1229 // MediaWiki\Session\Session already did the necessary authentication of the user
1230 // returned here, so just use it if applicable.
1231 $session = $this->getRequest()->getSession();
1232 $user = $session->getUser();
1233 if ( $user->isLoggedIn() ) {
1234 $this->loadFromUserObject( $user );
1235
1236 // If this user is autoblocked, set a cookie to track the Block. This has to be done on
1237 // every session load, because an autoblocked editor might not edit again from the same
1238 // IP address after being blocked.
1239 $config = RequestContext::getMain()->getConfig();
1240 if ( $config->get( 'CookieSetOnAutoblock' ) === true ) {
1241 $block = $this->getBlock();
1242 $shouldSetCookie = $this->getRequest()->getCookie( 'BlockID' ) === null
1243 && $block
1244 && $block->getType() === Block::TYPE_USER
1245 && $block->isAutoblocking();
1246 if ( $shouldSetCookie ) {
1247 wfDebug( __METHOD__ . ': User is autoblocked, setting cookie to track' );
1248 $block->setCookie( $this->getRequest()->response() );
1249 }
1250 }
1251
1252 // Other code expects these to be set in the session, so set them.
1253 $session->set( 'wsUserID', $this->getId() );
1254 $session->set( 'wsUserName', $this->getName() );
1255 $session->set( 'wsToken', $this->getToken() );
1256 return true;
1257 }
1258 return false;
1259 }
1260
1268 public function loadFromDatabase( $flags = self::READ_LATEST ) {
1269 // Paranoia
1270 $this->mId = intval( $this->mId );
1271
1272 if ( !$this->mId ) {
1273 // Anonymous users are not in the database
1274 $this->loadDefaults();
1275 return false;
1276 }
1277
1279 $db = wfGetDB( $index );
1280
1281 $s = $db->selectRow(
1282 'user',
1283 self::selectFields(),
1284 [ 'user_id' => $this->mId ],
1285 __METHOD__,
1286 $options
1287 );
1288
1289 $this->queryFlagsUsed = $flags;
1290 Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1291
1292 if ( $s !== false ) {
1293 // Initialise user table data
1294 $this->loadFromRow( $s );
1295 $this->mGroupMemberships = null; // deferred
1296 $this->getEditCount(); // revalidation for nulls
1297 return true;
1298 } else {
1299 // Invalid user_id
1300 $this->mId = 0;
1301 $this->loadDefaults();
1302 return false;
1303 }
1304 }
1305
1318 protected function loadFromRow( $row, $data = null ) {
1319 $all = true;
1320
1321 $this->mGroupMemberships = null; // deferred
1322
1323 if ( isset( $row->user_name ) ) {
1324 $this->mName = $row->user_name;
1325 $this->mFrom = 'name';
1326 $this->setItemLoaded( 'name' );
1327 } else {
1328 $all = false;
1329 }
1330
1331 if ( isset( $row->user_real_name ) ) {
1332 $this->mRealName = $row->user_real_name;
1333 $this->setItemLoaded( 'realname' );
1334 } else {
1335 $all = false;
1336 }
1337
1338 if ( isset( $row->user_id ) ) {
1339 $this->mId = intval( $row->user_id );
1340 $this->mFrom = 'id';
1341 $this->setItemLoaded( 'id' );
1342 } else {
1343 $all = false;
1344 }
1345
1346 if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
1347 self::$idCacheByName[$row->user_name] = $row->user_id;
1348 }
1349
1350 if ( isset( $row->user_editcount ) ) {
1351 $this->mEditCount = $row->user_editcount;
1352 } else {
1353 $all = false;
1354 }
1355
1356 if ( isset( $row->user_touched ) ) {
1357 $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1358 } else {
1359 $all = false;
1360 }
1361
1362 if ( isset( $row->user_token ) ) {
1363 // The definition for the column is binary(32), so trim the NULs
1364 // that appends. The previous definition was char(32), so trim
1365 // spaces too.
1366 $this->mToken = rtrim( $row->user_token, " \0" );
1367 if ( $this->mToken === '' ) {
1368 $this->mToken = null;
1369 }
1370 } else {
1371 $all = false;
1372 }
1373
1374 if ( isset( $row->user_email ) ) {
1375 $this->mEmail = $row->user_email;
1376 $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1377 $this->mEmailToken = $row->user_email_token;
1378 $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1379 $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1380 } else {
1381 $all = false;
1382 }
1383
1384 if ( $all ) {
1385 $this->mLoadedItems = true;
1386 }
1387
1388 if ( is_array( $data ) ) {
1389 if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1390 if ( !count( $data['user_groups'] ) ) {
1391 $this->mGroupMemberships = [];
1392 } else {
1393 $firstGroup = reset( $data['user_groups'] );
1394 if ( is_array( $firstGroup ) || is_object( $firstGroup ) ) {
1395 $this->mGroupMemberships = [];
1396 foreach ( $data['user_groups'] as $row ) {
1397 $ugm = UserGroupMembership::newFromRow( (object)$row );
1398 $this->mGroupMemberships[$ugm->getGroup()] = $ugm;
1399 }
1400 }
1401 }
1402 }
1403 if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1404 $this->loadOptions( $data['user_properties'] );
1405 }
1406 }
1407 }
1408
1414 protected function loadFromUserObject( $user ) {
1415 $user->load();
1416 foreach ( self::$mCacheVars as $var ) {
1417 $this->$var = $user->$var;
1418 }
1419 }
1420
1424 private function loadGroups() {
1425 if ( is_null( $this->mGroupMemberships ) ) {
1426 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1427 ? wfGetDB( DB_MASTER )
1428 : wfGetDB( DB_REPLICA );
1429 $this->mGroupMemberships = UserGroupMembership::getMembershipsForUser(
1430 $this->mId, $db );
1431 }
1432 }
1433
1448 public function addAutopromoteOnceGroups( $event ) {
1450
1451 if ( wfReadOnly() || !$this->getId() ) {
1452 return [];
1453 }
1454
1455 $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1456 if ( !count( $toPromote ) ) {
1457 return [];
1458 }
1459
1460 if ( !$this->checkAndSetTouched() ) {
1461 return []; // raced out (bug T48834)
1462 }
1463
1464 $oldGroups = $this->getGroups(); // previous groups
1465 foreach ( $toPromote as $group ) {
1466 $this->addGroup( $group );
1467 }
1468 // update groups in external authentication database
1469 Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false ] );
1470 AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1471
1472 $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1473
1474 $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1475 $logEntry->setPerformer( $this );
1476 $logEntry->setTarget( $this->getUserPage() );
1477 $logEntry->setParameters( [
1478 '4::oldgroups' => $oldGroups,
1479 '5::newgroups' => $newGroups,
1480 ] );
1481 $logid = $logEntry->insert();
1483 $logEntry->publish( $logid );
1484 }
1485
1486 return $toPromote;
1487 }
1488
1498 protected function makeUpdateConditions( Database $db, array $conditions ) {
1499 if ( $this->mTouched ) {
1500 // CAS check: only update if the row wasn't changed sicne it was loaded.
1501 $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1502 }
1503
1504 return $conditions;
1505 }
1506
1516 protected function checkAndSetTouched() {
1517 $this->load();
1518
1519 if ( !$this->mId ) {
1520 return false; // anon
1521 }
1522
1523 // Get a new user_touched that is higher than the old one
1524 $newTouched = $this->newTouchedTimestamp();
1525
1526 $dbw = wfGetDB( DB_MASTER );
1527 $dbw->update( 'user',
1528 [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1529 $this->makeUpdateConditions( $dbw, [
1530 'user_id' => $this->mId,
1531 ] ),
1532 __METHOD__
1533 );
1534 $success = ( $dbw->affectedRows() > 0 );
1535
1536 if ( $success ) {
1537 $this->mTouched = $newTouched;
1538 $this->clearSharedCache();
1539 } else {
1540 // Clears on failure too since that is desired if the cache is stale
1541 $this->clearSharedCache( 'refresh' );
1542 }
1543
1544 return $success;
1545 }
1546
1554 public function clearInstanceCache( $reloadFrom = false ) {
1555 $this->mNewtalk = -1;
1556 $this->mDatePreference = null;
1557 $this->mBlockedby = -1; # Unset
1558 $this->mHash = false;
1559 $this->mRights = null;
1560 $this->mEffectiveGroups = null;
1561 $this->mImplicitGroups = null;
1562 $this->mGroupMemberships = null;
1563 $this->mOptions = null;
1564 $this->mOptionsLoaded = false;
1565 $this->mEditCount = null;
1566
1567 if ( $reloadFrom ) {
1568 $this->mLoadedItems = [];
1569 $this->mFrom = $reloadFrom;
1570 }
1571 }
1572
1579 public static function getDefaultOptions() {
1581
1582 static $defOpt = null;
1583 static $defOptLang = null;
1584
1585 if ( $defOpt !== null && $defOptLang === $wgContLang->getCode() ) {
1586 // $wgContLang does not change (and should not change) mid-request,
1587 // but the unit tests change it anyway, and expect this method to
1588 // return values relevant to the current $wgContLang.
1589 return $defOpt;
1590 }
1591
1592 $defOpt = $wgDefaultUserOptions;
1593 // Default language setting
1594 $defOptLang = $wgContLang->getCode();
1595 $defOpt['language'] = $defOptLang;
1596 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1597 $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
1598 }
1599
1600 // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1601 // since extensions may change the set of searchable namespaces depending
1602 // on user groups/permissions.
1603 foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1604 $defOpt['searchNs' . $nsnum] = (bool)$val;
1605 }
1606 $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1607
1608 Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1609
1610 return $defOpt;
1611 }
1612
1619 public static function getDefaultOption( $opt ) {
1620 $defOpts = self::getDefaultOptions();
1621 if ( isset( $defOpts[$opt] ) ) {
1622 return $defOpts[$opt];
1623 } else {
1624 return null;
1625 }
1626 }
1627
1634 private function getBlockedStatus( $bFromSlave = true ) {
1636
1637 if ( -1 != $this->mBlockedby ) {
1638 return;
1639 }
1640
1641 wfDebug( __METHOD__ . ": checking...\n" );
1642
1643 // Initialize data...
1644 // Otherwise something ends up stomping on $this->mBlockedby when
1645 // things get lazy-loaded later, causing false positive block hits
1646 // due to -1 !== 0. Probably session-related... Nothing should be
1647 // overwriting mBlockedby, surely?
1648 $this->load();
1649
1650 # We only need to worry about passing the IP address to the Block generator if the
1651 # user is not immune to autoblocks/hardblocks, and they are the current user so we
1652 # know which IP address they're actually coming from
1653 $ip = null;
1654 $sessionUser = RequestContext::getMain()->getUser();
1655 // the session user is set up towards the end of Setup.php. Until then,
1656 // assume it's a logged-out user.
1657 $globalUserName = $sessionUser->isSafeToLoad()
1658 ? $sessionUser->getName()
1659 : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
1660 if ( $this->getName() === $globalUserName && !$this->isAllowed( 'ipblock-exempt' ) ) {
1661 $ip = $this->getRequest()->getIP();
1662 }
1663
1664 // User/IP blocking
1665 $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1666
1667 // Cookie blocking
1668 if ( !$block instanceof Block ) {
1669 $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
1670 }
1671
1672 // Proxy blocking
1673 if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1674 // Local list
1675 if ( self::isLocallyBlockedProxy( $ip ) ) {
1676 $block = new Block( [
1677 'byText' => wfMessage( 'proxyblocker' )->text(),
1678 'reason' => wfMessage( 'proxyblockreason' )->text(),
1679 'address' => $ip,
1680 'systemBlock' => 'proxy',
1681 ] );
1682 } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1683 $block = new Block( [
1684 'byText' => wfMessage( 'sorbs' )->text(),
1685 'reason' => wfMessage( 'sorbsreason' )->text(),
1686 'address' => $ip,
1687 'systemBlock' => 'dnsbl',
1688 ] );
1689 }
1690 }
1691
1692 // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
1693 if ( !$block instanceof Block
1695 && $ip !== null
1696 && !in_array( $ip, $wgProxyWhitelist )
1697 ) {
1698 $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1699 $xff = array_map( 'trim', explode( ',', $xff ) );
1700 $xff = array_diff( $xff, [ $ip ] );
1701 $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1702 $block = Block::chooseBlock( $xffblocks, $xff );
1703 if ( $block instanceof Block ) {
1704 # Mangle the reason to alert the user that the block
1705 # originated from matching the X-Forwarded-For header.
1706 $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
1707 }
1708 }
1709
1710 if ( !$block instanceof Block
1711 && $ip !== null
1712 && $this->isAnon()
1713 && IP::isInRanges( $ip, $wgSoftBlockRanges )
1714 ) {
1715 $block = new Block( [
1716 'address' => $ip,
1717 'byText' => 'MediaWiki default',
1718 'reason' => wfMessage( 'softblockrangesreason', $ip )->text(),
1719 'anonOnly' => true,
1720 'systemBlock' => 'wgSoftBlockRanges',
1721 ] );
1722 }
1723
1724 if ( $block instanceof Block ) {
1725 wfDebug( __METHOD__ . ": Found block.\n" );
1726 $this->mBlock = $block;
1727 $this->mBlockedby = $block->getByName();
1728 $this->mBlockreason = $block->mReason;
1729 $this->mHideName = $block->mHideName;
1730 $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1731 } else {
1732 $this->mBlockedby = '';
1733 $this->mHideName = 0;
1734 $this->mAllowUsertalk = false;
1735 }
1736
1737 // Avoid PHP 7.1 warning of passing $this by reference
1738 $thisUser = $this;
1739 // Extensions
1740 Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
1741 }
1742
1748 protected function getBlockFromCookieValue( $blockCookieVal ) {
1749 // Make sure there's something to check. The cookie value must start with a number.
1750 if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
1751 return false;
1752 }
1753 // Load the Block from the ID in the cookie.
1754 $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
1755 if ( $blockCookieId !== null ) {
1756 // An ID was found in the cookie.
1757 $tmpBlock = Block::newFromID( $blockCookieId );
1758 if ( $tmpBlock instanceof Block ) {
1759 // Check the validity of the block.
1760 $blockIsValid = $tmpBlock->getType() == Block::TYPE_USER
1761 && !$tmpBlock->isExpired()
1762 && $tmpBlock->isAutoblocking();
1763 $config = RequestContext::getMain()->getConfig();
1764 $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
1765 if ( $blockIsValid && $useBlockCookie ) {
1766 // Use the block.
1767 return $tmpBlock;
1768 } else {
1769 // If the block is not valid, remove the cookie.
1770 Block::clearCookie( $this->getRequest()->response() );
1771 }
1772 } else {
1773 // If the block doesn't exist, remove the cookie.
1774 Block::clearCookie( $this->getRequest()->response() );
1775 }
1776 }
1777 return false;
1778 }
1779
1787 public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1789
1790 if ( !$wgEnableDnsBlacklist ) {
1791 return false;
1792 }
1793
1794 if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
1795 return false;
1796 }
1797
1798 return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
1799 }
1800
1808 public function inDnsBlacklist( $ip, $bases ) {
1809 $found = false;
1810 // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
1811 if ( IP::isIPv4( $ip ) ) {
1812 // Reverse IP, T23255
1813 $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1814
1815 foreach ( (array)$bases as $base ) {
1816 // Make hostname
1817 // If we have an access key, use that too (ProjectHoneypot, etc.)
1818 $basename = $base;
1819 if ( is_array( $base ) ) {
1820 if ( count( $base ) >= 2 ) {
1821 // Access key is 1, base URL is 0
1822 $host = "{$base[1]}.$ipReversed.{$base[0]}";
1823 } else {
1824 $host = "$ipReversed.{$base[0]}";
1825 }
1826 $basename = $base[0];
1827 } else {
1828 $host = "$ipReversed.$base";
1829 }
1830
1831 // Send query
1832 $ipList = gethostbynamel( $host );
1833
1834 if ( $ipList ) {
1835 wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1836 $found = true;
1837 break;
1838 } else {
1839 wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1840 }
1841 }
1842 }
1843
1844 return $found;
1845 }
1846
1854 public static function isLocallyBlockedProxy( $ip ) {
1855 global $wgProxyList;
1856
1857 if ( !$wgProxyList ) {
1858 return false;
1859 }
1860
1861 if ( !is_array( $wgProxyList ) ) {
1862 // Load values from the specified file
1863 $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1864 }
1865
1866 $resultProxyList = [];
1867 $deprecatedIPEntries = [];
1868
1869 // backward compatibility: move all ip addresses in keys to values
1870 foreach ( $wgProxyList as $key => $value ) {
1871 $keyIsIP = IP::isIPAddress( $key );
1872 $valueIsIP = IP::isIPAddress( $value );
1873 if ( $keyIsIP && !$valueIsIP ) {
1874 $deprecatedIPEntries[] = $key;
1875 $resultProxyList[] = $key;
1876 } elseif ( $keyIsIP && $valueIsIP ) {
1877 $deprecatedIPEntries[] = $key;
1878 $resultProxyList[] = $key;
1879 $resultProxyList[] = $value;
1880 } else {
1881 $resultProxyList[] = $value;
1882 }
1883 }
1884
1885 if ( $deprecatedIPEntries ) {
1887 'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' .
1888 implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' );
1889 }
1890
1891 $proxyListIPSet = new IPSet( $resultProxyList );
1892 return $proxyListIPSet->match( $ip );
1893 }
1894
1900 public function isPingLimitable() {
1902 if ( IP::isInRanges( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1903 // No other good way currently to disable rate limits
1904 // for specific IPs. :P
1905 // But this is a crappy hack and should die.
1906 return false;
1907 }
1908 return !$this->isAllowed( 'noratelimit' );
1909 }
1910
1925 public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1926 // Avoid PHP 7.1 warning of passing $this by reference
1927 $user = $this;
1928 // Call the 'PingLimiter' hook
1929 $result = false;
1930 if ( !Hooks::run( 'PingLimiter', [ &$user, $action, &$result, $incrBy ] ) ) {
1931 return $result;
1932 }
1933
1934 global $wgRateLimits;
1935 if ( !isset( $wgRateLimits[$action] ) ) {
1936 return false;
1937 }
1938
1939 $limits = array_merge(
1940 [ '&can-bypass' => true ],
1941 $wgRateLimits[$action]
1942 );
1943
1944 // Some groups shouldn't trigger the ping limiter, ever
1945 if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1946 return false;
1947 }
1948
1949 $keys = [];
1950 $id = $this->getId();
1951 $userLimit = false;
1952 $isNewbie = $this->isNewbie();
1953 $cache = ObjectCache::getLocalClusterInstance();
1954
1955 if ( $id == 0 ) {
1956 // limits for anons
1957 if ( isset( $limits['anon'] ) ) {
1958 $keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1959 }
1960 } else {
1961 // limits for logged-in users
1962 if ( isset( $limits['user'] ) ) {
1963 $userLimit = $limits['user'];
1964 }
1965 }
1966
1967 // limits for anons and for newbie logged-in users
1968 if ( $isNewbie ) {
1969 // ip-based limits
1970 if ( isset( $limits['ip'] ) ) {
1971 $ip = $this->getRequest()->getIP();
1972 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1973 }
1974 // subnet-based limits
1975 if ( isset( $limits['subnet'] ) ) {
1976 $ip = $this->getRequest()->getIP();
1977 $subnet = IP::getSubnet( $ip );
1978 if ( $subnet !== false ) {
1979 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1980 }
1981 }
1982 }
1983
1984 // Check for group-specific permissions
1985 // If more than one group applies, use the group with the highest limit ratio (max/period)
1986 foreach ( $this->getGroups() as $group ) {
1987 if ( isset( $limits[$group] ) ) {
1988 if ( $userLimit === false
1989 || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1990 ) {
1991 $userLimit = $limits[$group];
1992 }
1993 }
1994 }
1995
1996 // limits for newbie logged-in users (override all the normal user limits)
1997 if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
1998 $userLimit = $limits['newbie'];
1999 }
2000
2001 // Set the user limit key
2002 if ( $userLimit !== false ) {
2003 list( $max, $period ) = $userLimit;
2004 wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
2005 $keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
2006 }
2007
2008 // ip-based limits for all ping-limitable users
2009 if ( isset( $limits['ip-all'] ) ) {
2010 $ip = $this->getRequest()->getIP();
2011 // ignore if user limit is more permissive
2012 if ( $isNewbie || $userLimit === false
2013 || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
2014 $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
2015 }
2016 }
2017
2018 // subnet-based limits for all ping-limitable users
2019 if ( isset( $limits['subnet-all'] ) ) {
2020 $ip = $this->getRequest()->getIP();
2021 $subnet = IP::getSubnet( $ip );
2022 if ( $subnet !== false ) {
2023 // ignore if user limit is more permissive
2024 if ( $isNewbie || $userLimit === false
2025 || $limits['ip-all'][0] / $limits['ip-all'][1]
2026 > $userLimit[0] / $userLimit[1] ) {
2027 $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
2028 }
2029 }
2030 }
2031
2032 $triggered = false;
2033 foreach ( $keys as $key => $limit ) {
2034 list( $max, $period ) = $limit;
2035 $summary = "(limit $max in {$period}s)";
2036 $count = $cache->get( $key );
2037 // Already pinged?
2038 if ( $count ) {
2039 if ( $count >= $max ) {
2040 wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
2041 "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
2042 $triggered = true;
2043 } else {
2044 wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
2045 }
2046 } else {
2047 wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
2048 if ( $incrBy > 0 ) {
2049 $cache->add( $key, 0, intval( $period ) ); // first ping
2050 }
2051 }
2052 if ( $incrBy > 0 ) {
2053 $cache->incr( $key, $incrBy );
2054 }
2055 }
2056
2057 return $triggered;
2058 }
2059
2067 public function isBlocked( $bFromSlave = true ) {
2068 return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
2069 }
2070
2077 public function getBlock( $bFromSlave = true ) {
2078 $this->getBlockedStatus( $bFromSlave );
2079 return $this->mBlock instanceof Block ? $this->mBlock : null;
2080 }
2081
2089 public function isBlockedFrom( $title, $bFromSlave = false ) {
2090 global $wgBlockAllowsUTEdit;
2091
2092 $blocked = $this->isBlocked( $bFromSlave );
2093 $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
2094 // If a user's name is suppressed, they cannot make edits anywhere
2095 if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
2096 && $title->getNamespace() == NS_USER_TALK ) {
2097 $blocked = false;
2098 wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
2099 }
2100
2101 Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
2102
2103 return $blocked;
2104 }
2105
2110 public function blockedBy() {
2111 $this->getBlockedStatus();
2112 return $this->mBlockedby;
2113 }
2114
2119 public function blockedFor() {
2120 $this->getBlockedStatus();
2121 return $this->mBlockreason;
2122 }
2123
2128 public function getBlockId() {
2129 $this->getBlockedStatus();
2130 return ( $this->mBlock ? $this->mBlock->getId() : false );
2131 }
2132
2141 public function isBlockedGlobally( $ip = '' ) {
2142 return $this->getGlobalBlock( $ip ) instanceof Block;
2143 }
2144
2155 public function getGlobalBlock( $ip = '' ) {
2156 if ( $this->mGlobalBlock !== null ) {
2157 return $this->mGlobalBlock ?: null;
2158 }
2159 // User is already an IP?
2160 if ( IP::isIPAddress( $this->getName() ) ) {
2161 $ip = $this->getName();
2162 } elseif ( !$ip ) {
2163 $ip = $this->getRequest()->getIP();
2164 }
2165 // Avoid PHP 7.1 warning of passing $this by reference
2166 $user = $this;
2167 $blocked = false;
2168 $block = null;
2169 Hooks::run( 'UserIsBlockedGlobally', [ &$user, $ip, &$blocked, &$block ] );
2170
2171 if ( $blocked && $block === null ) {
2172 // back-compat: UserIsBlockedGlobally didn't have $block param first
2173 $block = new Block( [
2174 'address' => $ip,
2175 'systemBlock' => 'global-block'
2176 ] );
2177 }
2178
2179 $this->mGlobalBlock = $blocked ? $block : false;
2180 return $this->mGlobalBlock ?: null;
2181 }
2182
2188 public function isLocked() {
2189 if ( $this->mLocked !== null ) {
2190 return $this->mLocked;
2191 }
2192 // Avoid PHP 7.1 warning of passing $this by reference
2193 $user = $this;
2194 $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2195 $this->mLocked = $authUser && $authUser->isLocked();
2196 Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2197 return $this->mLocked;
2198 }
2199
2205 public function isHidden() {
2206 if ( $this->mHideName !== null ) {
2207 return $this->mHideName;
2208 }
2209 $this->getBlockedStatus();
2210 if ( !$this->mHideName ) {
2211 // Avoid PHP 7.1 warning of passing $this by reference
2212 $user = $this;
2213 $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
2214 $this->mHideName = $authUser && $authUser->isHidden();
2215 Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2216 }
2217 return $this->mHideName;
2218 }
2219
2224 public function getId() {
2225 if ( $this->mId === null && $this->mName !== null && self::isIP( $this->mName ) ) {
2226 // Special case, we know the user is anonymous
2227 return 0;
2228 } elseif ( !$this->isItemLoaded( 'id' ) ) {
2229 // Don't load if this was initialized from an ID
2230 $this->load();
2231 }
2232
2233 return (int)$this->mId;
2234 }
2235
2240 public function setId( $v ) {
2241 $this->mId = $v;
2242 $this->clearInstanceCache( 'id' );
2243 }
2244
2249 public function getName() {
2250 if ( $this->isItemLoaded( 'name', 'only' ) ) {
2251 // Special case optimisation
2252 return $this->mName;
2253 } else {
2254 $this->load();
2255 if ( $this->mName === false ) {
2256 // Clean up IPs
2257 $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2258 }
2259 return $this->mName;
2260 }
2261 }
2262
2276 public function setName( $str ) {
2277 $this->load();
2278 $this->mName = $str;
2279 }
2280
2285 public function getTitleKey() {
2286 return str_replace( ' ', '_', $this->getName() );
2287 }
2288
2293 public function getNewtalk() {
2294 $this->load();
2295
2296 // Load the newtalk status if it is unloaded (mNewtalk=-1)
2297 if ( $this->mNewtalk === -1 ) {
2298 $this->mNewtalk = false; # reset talk page status
2299
2300 // Check memcached separately for anons, who have no
2301 // entire User object stored in there.
2302 if ( !$this->mId ) {
2303 global $wgDisableAnonTalk;
2304 if ( $wgDisableAnonTalk ) {
2305 // Anon newtalk disabled by configuration.
2306 $this->mNewtalk = false;
2307 } else {
2308 $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2309 }
2310 } else {
2311 $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2312 }
2313 }
2314
2315 return (bool)$this->mNewtalk;
2316 }
2317
2331 public function getNewMessageLinks() {
2332 // Avoid PHP 7.1 warning of passing $this by reference
2333 $user = $this;
2334 $talks = [];
2335 if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
2336 return $talks;
2337 } elseif ( !$this->getNewtalk() ) {
2338 return [];
2339 }
2340 $utp = $this->getTalkPage();
2341 $dbr = wfGetDB( DB_REPLICA );
2342 // Get the "last viewed rev" timestamp from the oldest message notification
2343 $timestamp = $dbr->selectField( 'user_newtalk',
2344 'MIN(user_last_timestamp)',
2345 $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2346 __METHOD__ );
2347 $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
2348 return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2349 }
2350
2356 public function getNewMessageRevisionId() {
2357 $newMessageRevisionId = null;
2358 $newMessageLinks = $this->getNewMessageLinks();
2359 if ( $newMessageLinks ) {
2360 // Note: getNewMessageLinks() never returns more than a single link
2361 // and it is always for the same wiki, but we double-check here in
2362 // case that changes some time in the future.
2363 if ( count( $newMessageLinks ) === 1
2364 && $newMessageLinks[0]['wiki'] === wfWikiID()
2365 && $newMessageLinks[0]['rev']
2366 ) {
2368 $newMessageRevision = $newMessageLinks[0]['rev'];
2369 $newMessageRevisionId = $newMessageRevision->getId();
2370 }
2371 }
2372 return $newMessageRevisionId;
2373 }
2374
2383 protected function checkNewtalk( $field, $id ) {
2384 $dbr = wfGetDB( DB_REPLICA );
2385
2386 $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2387
2388 return $ok !== false;
2389 }
2390
2398 protected function updateNewtalk( $field, $id, $curRev = null ) {
2399 // Get timestamp of the talk page revision prior to the current one
2400 $prevRev = $curRev ? $curRev->getPrevious() : false;
2401 $ts = $prevRev ? $prevRev->getTimestamp() : null;
2402 // Mark the user as having new messages since this revision
2403 $dbw = wfGetDB( DB_MASTER );
2404 $dbw->insert( 'user_newtalk',
2405 [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2406 __METHOD__,
2407 'IGNORE' );
2408 if ( $dbw->affectedRows() ) {
2409 wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2410 return true;
2411 } else {
2412 wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2413 return false;
2414 }
2415 }
2416
2423 protected function deleteNewtalk( $field, $id ) {
2424 $dbw = wfGetDB( DB_MASTER );
2425 $dbw->delete( 'user_newtalk',
2426 [ $field => $id ],
2427 __METHOD__ );
2428 if ( $dbw->affectedRows() ) {
2429 wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2430 return true;
2431 } else {
2432 wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2433 return false;
2434 }
2435 }
2436
2443 public function setNewtalk( $val, $curRev = null ) {
2444 if ( wfReadOnly() ) {
2445 return;
2446 }
2447
2448 $this->load();
2449 $this->mNewtalk = $val;
2450
2451 if ( $this->isAnon() ) {
2452 $field = 'user_ip';
2453 $id = $this->getName();
2454 } else {
2455 $field = 'user_id';
2456 $id = $this->getId();
2457 }
2458
2459 if ( $val ) {
2460 $changed = $this->updateNewtalk( $field, $id, $curRev );
2461 } else {
2462 $changed = $this->deleteNewtalk( $field, $id );
2463 }
2464
2465 if ( $changed ) {
2466 $this->invalidateCache();
2467 }
2468 }
2469
2475 private function newTouchedTimestamp() {
2476 global $wgClockSkewFudge;
2477
2478 $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
2479 if ( $this->mTouched && $time <= $this->mTouched ) {
2480 $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2481 }
2482
2483 return $time;
2484 }
2485
2496 public function clearSharedCache( $mode = 'changed' ) {
2497 if ( !$this->getId() ) {
2498 return;
2499 }
2500
2501 $cache = ObjectCache::getMainWANInstance();
2502 $key = $this->getCacheKey( $cache );
2503 if ( $mode === 'refresh' ) {
2504 $cache->delete( $key, 1 );
2505 } else {
2506 wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
2507 function () use ( $cache, $key ) {
2508 $cache->delete( $key );
2509 },
2510 __METHOD__
2511 );
2512 }
2513 }
2514
2520 public function invalidateCache() {
2521 $this->touch();
2522 $this->clearSharedCache();
2523 }
2524
2537 public function touch() {
2538 $id = $this->getId();
2539 if ( $id ) {
2540 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2541 $key = $cache->makeKey( 'user-quicktouched', 'id', $id );
2542 $cache->touchCheckKey( $key );
2543 $this->mQuickTouched = null;
2544 }
2545 }
2546
2552 public function validateCache( $timestamp ) {
2553 return ( $timestamp >= $this->getTouched() );
2554 }
2555
2564 public function getTouched() {
2565 $this->load();
2566
2567 if ( $this->mId ) {
2568 if ( $this->mQuickTouched === null ) {
2569 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2570 $key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
2571
2572 $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2573 }
2574
2575 return max( $this->mTouched, $this->mQuickTouched );
2576 }
2577
2578 return $this->mTouched;
2579 }
2580
2586 public function getDBTouched() {
2587 $this->load();
2588
2589 return $this->mTouched;
2590 }
2591
2608 public function setPassword( $str ) {
2609 return $this->setPasswordInternal( $str );
2610 }
2611
2620 public function setInternalPassword( $str ) {
2621 $this->setPasswordInternal( $str );
2622 }
2623
2632 private function setPasswordInternal( $str ) {
2633 $manager = AuthManager::singleton();
2634
2635 // If the user doesn't exist yet, fail
2636 if ( !$manager->userExists( $this->getName() ) ) {
2637 throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2638 }
2639
2641 'username' => $this->getName(),
2642 'password' => $str,
2643 'retype' => $str,
2644 ] );
2645 if ( !$status->isGood() ) {
2646 \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
2647 ->info( __METHOD__ . ': Password change rejected: '
2648 . $status->getWikiText( null, null, 'en' ) );
2649 return false;
2650 }
2651
2652 $this->setOption( 'watchlisttoken', false );
2653 SessionManager::singleton()->invalidateSessionsForUser( $this );
2654
2655 return true;
2656 }
2657
2670 public function changeAuthenticationData( array $data ) {
2671 $manager = AuthManager::singleton();
2672 $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2673 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2674
2675 $status = Status::newGood( 'ignored' );
2676 foreach ( $reqs as $req ) {
2677 $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2678 }
2679 if ( $status->getValue() === 'ignored' ) {
2680 $status->warning( 'authenticationdatachange-ignored' );
2681 }
2682
2683 if ( $status->isGood() ) {
2684 foreach ( $reqs as $req ) {
2685 $manager->changeAuthenticationData( $req );
2686 }
2687 }
2688 return $status;
2689 }
2690
2697 public function getToken( $forceCreation = true ) {
2699
2700 $this->load();
2701 if ( !$this->mToken && $forceCreation ) {
2702 $this->setToken();
2703 }
2704
2705 if ( !$this->mToken ) {
2706 // The user doesn't have a token, return null to indicate that.
2707 return null;
2708 } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2709 // We return a random value here so existing token checks are very
2710 // likely to fail.
2711 return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2712 } elseif ( $wgAuthenticationTokenVersion === null ) {
2713 // $wgAuthenticationTokenVersion not in use, so return the raw secret
2714 return $this->mToken;
2715 } else {
2716 // $wgAuthenticationTokenVersion in use, so hmac it.
2717 $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2718
2719 // The raw hash can be overly long. Shorten it up.
2720 $len = max( 32, self::TOKEN_LENGTH );
2721 if ( strlen( $ret ) < $len ) {
2722 // Should never happen, even md5 is 128 bits
2723 throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2724 }
2725 return substr( $ret, -$len );
2726 }
2727 }
2728
2735 public function setToken( $token = false ) {
2736 $this->load();
2737 if ( $this->mToken === self::INVALID_TOKEN ) {
2738 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
2739 ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2740 } elseif ( !$token ) {
2741 $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2742 } else {
2743 $this->mToken = $token;
2744 }
2745 }
2746
2755 public function setNewpassword( $str, $throttle = true ) {
2756 throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2757 }
2758
2763 public function getEmail() {
2764 $this->load();
2765 Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2766 return $this->mEmail;
2767 }
2768
2774 $this->load();
2775 Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2776 return $this->mEmailAuthenticated;
2777 }
2778
2783 public function setEmail( $str ) {
2784 $this->load();
2785 if ( $str == $this->mEmail ) {
2786 return;
2787 }
2788 $this->invalidateEmail();
2789 $this->mEmail = $str;
2790 Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2791 }
2792
2800 public function setEmailWithConfirmation( $str ) {
2802
2803 if ( !$wgEnableEmail ) {
2804 return Status::newFatal( 'emaildisabled' );
2805 }
2806
2807 $oldaddr = $this->getEmail();
2808 if ( $str === $oldaddr ) {
2809 return Status::newGood( true );
2810 }
2811
2812 $type = $oldaddr != '' ? 'changed' : 'set';
2813 $notificationResult = null;
2814
2815 if ( $wgEmailAuthentication ) {
2816 // Send the user an email notifying the user of the change in registered
2817 // email address on their previous email address
2818 if ( $type == 'changed' ) {
2819 $change = $str != '' ? 'changed' : 'removed';
2820 $notificationResult = $this->sendMail(
2821 wfMessage( 'notificationemail_subject_' . $change )->text(),
2822 wfMessage( 'notificationemail_body_' . $change,
2823 $this->getRequest()->getIP(),
2824 $this->getName(),
2825 $str )->text()
2826 );
2827 }
2828 }
2829
2830 $this->setEmail( $str );
2831
2832 if ( $str !== '' && $wgEmailAuthentication ) {
2833 // Send a confirmation request to the new address if needed
2834 $result = $this->sendConfirmationMail( $type );
2835
2836 if ( $notificationResult !== null ) {
2837 $result->merge( $notificationResult );
2838 }
2839
2840 if ( $result->isGood() ) {
2841 // Say to the caller that a confirmation and notification mail has been sent
2842 $result->value = 'eauth';
2843 }
2844 } else {
2845 $result = Status::newGood( true );
2846 }
2847
2848 return $result;
2849 }
2850
2855 public function getRealName() {
2856 if ( !$this->isItemLoaded( 'realname' ) ) {
2857 $this->load();
2858 }
2859
2860 return $this->mRealName;
2861 }
2862
2867 public function setRealName( $str ) {
2868 $this->load();
2869 $this->mRealName = $str;
2870 }
2871
2882 public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2883 global $wgHiddenPrefs;
2884 $this->loadOptions();
2885
2886 # We want 'disabled' preferences to always behave as the default value for
2887 # users, even if they have set the option explicitly in their settings (ie they
2888 # set it, and then it was disabled removing their ability to change it). But
2889 # we don't want to erase the preferences in the database in case the preference
2890 # is re-enabled again. So don't touch $mOptions, just override the returned value
2891 if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2892 return self::getDefaultOption( $oname );
2893 }
2894
2895 if ( array_key_exists( $oname, $this->mOptions ) ) {
2896 return $this->mOptions[$oname];
2897 } else {
2898 return $defaultOverride;
2899 }
2900 }
2901
2910 public function getOptions( $flags = 0 ) {
2911 global $wgHiddenPrefs;
2912 $this->loadOptions();
2913 $options = $this->mOptions;
2914
2915 # We want 'disabled' preferences to always behave as the default value for
2916 # users, even if they have set the option explicitly in their settings (ie they
2917 # set it, and then it was disabled removing their ability to change it). But
2918 # we don't want to erase the preferences in the database in case the preference
2919 # is re-enabled again. So don't touch $mOptions, just override the returned value
2920 foreach ( $wgHiddenPrefs as $pref ) {
2921 $default = self::getDefaultOption( $pref );
2922 if ( $default !== null ) {
2923 $options[$pref] = $default;
2924 }
2925 }
2926
2927 if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2928 $options = array_diff_assoc( $options, self::getDefaultOptions() );
2929 }
2930
2931 return $options;
2932 }
2933
2941 public function getBoolOption( $oname ) {
2942 return (bool)$this->getOption( $oname );
2943 }
2944
2953 public function getIntOption( $oname, $defaultOverride = 0 ) {
2954 $val = $this->getOption( $oname );
2955 if ( $val == '' ) {
2956 $val = $defaultOverride;
2957 }
2958 return intval( $val );
2959 }
2960
2969 public function setOption( $oname, $val ) {
2970 $this->loadOptions();
2971
2972 // Explicitly NULL values should refer to defaults
2973 if ( is_null( $val ) ) {
2974 $val = self::getDefaultOption( $oname );
2975 }
2976
2977 $this->mOptions[$oname] = $val;
2978 }
2979
2990 public function getTokenFromOption( $oname ) {
2991 global $wgHiddenPrefs;
2992
2993 $id = $this->getId();
2994 if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2995 return false;
2996 }
2997
2998 $token = $this->getOption( $oname );
2999 if ( !$token ) {
3000 // Default to a value based on the user token to avoid space
3001 // wasted on storing tokens for all users. When this option
3002 // is set manually by the user, only then is it stored.
3003 $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
3004 }
3005
3006 return $token;
3007 }
3008
3018 public function resetTokenFromOption( $oname ) {
3019 global $wgHiddenPrefs;
3020 if ( in_array( $oname, $wgHiddenPrefs ) ) {
3021 return false;
3022 }
3023
3024 $token = MWCryptRand::generateHex( 40 );
3025 $this->setOption( $oname, $token );
3026 return $token;
3027 }
3028
3052 public static function listOptionKinds() {
3053 return [
3054 'registered',
3055 'registered-multiselect',
3056 'registered-checkmatrix',
3057 'userjs',
3058 'special',
3059 'unused'
3060 ];
3061 }
3062
3075 public function getOptionKinds( IContextSource $context, $options = null ) {
3076 $this->loadOptions();
3077 if ( $options === null ) {
3078 $options = $this->mOptions;
3079 }
3080
3081 $prefs = Preferences::getPreferences( $this, $context );
3082 $mapping = [];
3083
3084 // Pull out the "special" options, so they don't get converted as
3085 // multiselect or checkmatrix.
3086 $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
3087 foreach ( $specialOptions as $name => $value ) {
3088 unset( $prefs[$name] );
3089 }
3090
3091 // Multiselect and checkmatrix options are stored in the database with
3092 // one key per option, each having a boolean value. Extract those keys.
3093 $multiselectOptions = [];
3094 foreach ( $prefs as $name => $info ) {
3095 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
3096 ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
3097 $opts = HTMLFormField::flattenOptions( $info['options'] );
3098 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
3099
3100 foreach ( $opts as $value ) {
3101 $multiselectOptions["$prefix$value"] = true;
3102 }
3103
3104 unset( $prefs[$name] );
3105 }
3106 }
3107 $checkmatrixOptions = [];
3108 foreach ( $prefs as $name => $info ) {
3109 if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
3110 ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
3111 $columns = HTMLFormField::flattenOptions( $info['columns'] );
3112 $rows = HTMLFormField::flattenOptions( $info['rows'] );
3113 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
3114
3115 foreach ( $columns as $column ) {
3116 foreach ( $rows as $row ) {
3117 $checkmatrixOptions["$prefix$column-$row"] = true;
3118 }
3119 }
3120
3121 unset( $prefs[$name] );
3122 }
3123 }
3124
3125 // $value is ignored
3126 foreach ( $options as $key => $value ) {
3127 if ( isset( $prefs[$key] ) ) {
3128 $mapping[$key] = 'registered';
3129 } elseif ( isset( $multiselectOptions[$key] ) ) {
3130 $mapping[$key] = 'registered-multiselect';
3131 } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3132 $mapping[$key] = 'registered-checkmatrix';
3133 } elseif ( isset( $specialOptions[$key] ) ) {
3134 $mapping[$key] = 'special';
3135 } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3136 $mapping[$key] = 'userjs';
3137 } else {
3138 $mapping[$key] = 'unused';
3139 }
3140 }
3141
3142 return $mapping;
3143 }
3144
3159 public function resetOptions(
3160 $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3162 ) {
3163 $this->load();
3164 $defaultOptions = self::getDefaultOptions();
3165
3166 if ( !is_array( $resetKinds ) ) {
3167 $resetKinds = [ $resetKinds ];
3168 }
3169
3170 if ( in_array( 'all', $resetKinds ) ) {
3171 $newOptions = $defaultOptions;
3172 } else {
3173 if ( $context === null ) {
3175 }
3176
3177 $optionKinds = $this->getOptionKinds( $context );
3178 $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3179 $newOptions = [];
3180
3181 // Use default values for the options that should be deleted, and
3182 // copy old values for the ones that shouldn't.
3183 foreach ( $this->mOptions as $key => $value ) {
3184 if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3185 if ( array_key_exists( $key, $defaultOptions ) ) {
3186 $newOptions[$key] = $defaultOptions[$key];
3187 }
3188 } else {
3189 $newOptions[$key] = $value;
3190 }
3191 }
3192 }
3193
3194 Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3195
3196 $this->mOptions = $newOptions;
3197 $this->mOptionsLoaded = true;
3198 }
3199
3204 public function getDatePreference() {
3205 // Important migration for old data rows
3206 if ( is_null( $this->mDatePreference ) ) {
3207 global $wgLang;
3208 $value = $this->getOption( 'date' );
3209 $map = $wgLang->getDatePreferenceMigrationMap();
3210 if ( isset( $map[$value] ) ) {
3211 $value = $map[$value];
3212 }
3213 $this->mDatePreference = $value;
3214 }
3215 return $this->mDatePreference;
3216 }
3217
3224 public function requiresHTTPS() {
3225 global $wgSecureLogin;
3226 if ( !$wgSecureLogin ) {
3227 return false;
3228 } else {
3229 $https = $this->getBoolOption( 'prefershttps' );
3230 Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3231 if ( $https ) {
3232 $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3233 }
3234 return $https;
3235 }
3236 }
3237
3243 public function getStubThreshold() {
3244 global $wgMaxArticleSize; # Maximum article size, in Kb
3245 $threshold = $this->getIntOption( 'stubthreshold' );
3246 if ( $threshold > $wgMaxArticleSize * 1024 ) {
3247 // If they have set an impossible value, disable the preference
3248 // so we can use the parser cache again.
3249 $threshold = 0;
3250 }
3251 return $threshold;
3252 }
3253
3258 public function getRights() {
3259 if ( is_null( $this->mRights ) ) {
3260 $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3261 Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3262
3263 // Deny any rights denied by the user's session, unless this
3264 // endpoint has no sessions.
3265 if ( !defined( 'MW_NO_SESSION' ) ) {
3266 $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3267 if ( $allowedRights !== null ) {
3268 $this->mRights = array_intersect( $this->mRights, $allowedRights );
3269 }
3270 }
3271
3272 // Force reindexation of rights when a hook has unset one of them
3273 $this->mRights = array_values( array_unique( $this->mRights ) );
3274
3275 // If block disables login, we should also remove any
3276 // extra rights blocked users might have, in case the
3277 // blocked user has a pre-existing session (T129738).
3278 // This is checked here for cases where people only call
3279 // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3280 // to give a better error message in the common case.
3281 $config = RequestContext::getMain()->getConfig();
3282 if (
3283 $this->isLoggedIn() &&
3284 $config->get( 'BlockDisablesLogin' ) &&
3285 $this->isBlocked()
3286 ) {
3287 $anon = new User;
3288 $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3289 }
3290 }
3291 return $this->mRights;
3292 }
3293
3299 public function getGroups() {
3300 $this->load();
3301 $this->loadGroups();
3302 return array_keys( $this->mGroupMemberships );
3303 }
3304
3312 public function getGroupMemberships() {
3313 $this->load();
3314 $this->loadGroups();
3315 return $this->mGroupMemberships;
3316 }
3317
3325 public function getEffectiveGroups( $recache = false ) {
3326 if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3327 $this->mEffectiveGroups = array_unique( array_merge(
3328 $this->getGroups(), // explicit groups
3329 $this->getAutomaticGroups( $recache ) // implicit groups
3330 ) );
3331 // Avoid PHP 7.1 warning of passing $this by reference
3332 $user = $this;
3333 // Hook for additional groups
3334 Hooks::run( 'UserEffectiveGroups', [ &$user, &$this->mEffectiveGroups ] );
3335 // Force reindexation of groups when a hook has unset one of them
3336 $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3337 }
3338 return $this->mEffectiveGroups;
3339 }
3340
3348 public function getAutomaticGroups( $recache = false ) {
3349 if ( $recache || is_null( $this->mImplicitGroups ) ) {
3350 $this->mImplicitGroups = [ '*' ];
3351 if ( $this->getId() ) {
3352 $this->mImplicitGroups[] = 'user';
3353
3354 $this->mImplicitGroups = array_unique( array_merge(
3355 $this->mImplicitGroups,
3357 ) );
3358 }
3359 if ( $recache ) {
3360 // Assure data consistency with rights/groups,
3361 // as getEffectiveGroups() depends on this function
3362 $this->mEffectiveGroups = null;
3363 }
3364 }
3365 return $this->mImplicitGroups;
3366 }
3367
3377 public function getFormerGroups() {
3378 $this->load();
3379
3380 if ( is_null( $this->mFormerGroups ) ) {
3381 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3382 ? wfGetDB( DB_MASTER )
3383 : wfGetDB( DB_REPLICA );
3384 $res = $db->select( 'user_former_groups',
3385 [ 'ufg_group' ],
3386 [ 'ufg_user' => $this->mId ],
3387 __METHOD__ );
3388 $this->mFormerGroups = [];
3389 foreach ( $res as $row ) {
3390 $this->mFormerGroups[] = $row->ufg_group;
3391 }
3392 }
3393
3394 return $this->mFormerGroups;
3395 }
3396
3401 public function getEditCount() {
3402 if ( !$this->getId() ) {
3403 return null;
3404 }
3405
3406 if ( $this->mEditCount === null ) {
3407 /* Populate the count, if it has not been populated yet */
3408 $dbr = wfGetDB( DB_REPLICA );
3409 // check if the user_editcount field has been initialized
3410 $count = $dbr->selectField(
3411 'user', 'user_editcount',
3412 [ 'user_id' => $this->mId ],
3413 __METHOD__
3414 );
3415
3416 if ( $count === null ) {
3417 // it has not been initialized. do so.
3418 $count = $this->initEditCount();
3419 }
3420 $this->mEditCount = $count;
3421 }
3422 return (int)$this->mEditCount;
3423 }
3424
3436 public function addGroup( $group, $expiry = null ) {
3437 $this->load();
3438 $this->loadGroups();
3439
3440 if ( $expiry ) {
3441 $expiry = wfTimestamp( TS_MW, $expiry );
3442 }
3443
3444 if ( !Hooks::run( 'UserAddGroup', [ $this, &$group, &$expiry ] ) ) {
3445 return false;
3446 }
3447
3448 // create the new UserGroupMembership and put it in the DB
3449 $ugm = new UserGroupMembership( $this->mId, $group, $expiry );
3450 if ( !$ugm->insert( true ) ) {
3451 return false;
3452 }
3453
3454 $this->mGroupMemberships[$group] = $ugm;
3455
3456 // Refresh the groups caches, and clear the rights cache so it will be
3457 // refreshed on the next call to $this->getRights().
3458 $this->getEffectiveGroups( true );
3459 $this->mRights = null;
3460
3461 $this->invalidateCache();
3462
3463 return true;
3464 }
3465
3472 public function removeGroup( $group ) {
3473 $this->load();
3474
3475 if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3476 return false;
3477 }
3478
3479 $ugm = UserGroupMembership::getMembership( $this->mId, $group );
3480 // delete the membership entry
3481 if ( !$ugm || !$ugm->delete() ) {
3482 return false;
3483 }
3484
3485 $this->loadGroups();
3486 unset( $this->mGroupMemberships[$group] );
3487
3488 // Refresh the groups caches, and clear the rights cache so it will be
3489 // refreshed on the next call to $this->getRights().
3490 $this->getEffectiveGroups( true );
3491 $this->mRights = null;
3492
3493 $this->invalidateCache();
3494
3495 return true;
3496 }
3497
3502 public function isLoggedIn() {
3503 return $this->getId() != 0;
3504 }
3505
3510 public function isAnon() {
3511 return !$this->isLoggedIn();
3512 }
3513
3518 public function isBot() {
3519 if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3520 return true;
3521 }
3522
3523 $isBot = false;
3524 Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3525
3526 return $isBot;
3527 }
3528
3535 public function isAllowedAny() {
3536 $permissions = func_get_args();
3537 foreach ( $permissions as $permission ) {
3538 if ( $this->isAllowed( $permission ) ) {
3539 return true;
3540 }
3541 }
3542 return false;
3543 }
3544
3550 public function isAllowedAll() {
3551 $permissions = func_get_args();
3552 foreach ( $permissions as $permission ) {
3553 if ( !$this->isAllowed( $permission ) ) {
3554 return false;
3555 }
3556 }
3557 return true;
3558 }
3559
3565 public function isAllowed( $action = '' ) {
3566 if ( $action === '' ) {
3567 return true; // In the spirit of DWIM
3568 }
3569 // Use strict parameter to avoid matching numeric 0 accidentally inserted
3570 // by misconfiguration: 0 == 'foo'
3571 return in_array( $action, $this->getRights(), true );
3572 }
3573
3578 public function useRCPatrol() {
3579 global $wgUseRCPatrol;
3580 return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3581 }
3582
3587 public function useNPPatrol() {
3589 return (
3591 && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3592 );
3593 }
3594
3599 public function useFilePatrol() {
3601 return (
3603 && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3604 );
3605 }
3606
3612 public function getRequest() {
3613 if ( $this->mRequest ) {
3614 return $this->mRequest;
3615 } else {
3616 global $wgRequest;
3617 return $wgRequest;
3618 }
3619 }
3620
3629 public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3630 if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3631 return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3632 }
3633 return false;
3634 }
3635
3643 public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3644 if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3645 MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3646 $this,
3647 [ $title->getSubjectPage(), $title->getTalkPage() ]
3648 );
3649 }
3650 $this->invalidateCache();
3651 }
3652
3660 public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3661 if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3662 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3663 $store->removeWatch( $this, $title->getSubjectPage() );
3664 $store->removeWatch( $this, $title->getTalkPage() );
3665 }
3666 $this->invalidateCache();
3667 }
3668
3677 public function clearNotification( &$title, $oldid = 0 ) {
3679
3680 // Do nothing if the database is locked to writes
3681 if ( wfReadOnly() ) {
3682 return;
3683 }
3684
3685 // Do nothing if not allowed to edit the watchlist
3686 if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3687 return;
3688 }
3689
3690 // If we're working on user's talk page, we should update the talk page message indicator
3691 if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3692 // Avoid PHP 7.1 warning of passing $this by reference
3693 $user = $this;
3694 if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$user, $oldid ] ) ) {
3695 return;
3696 }
3697
3698 // Try to update the DB post-send and only if needed...
3699 DeferredUpdates::addCallableUpdate( function () use ( $title, $oldid ) {
3700 if ( !$this->getNewtalk() ) {
3701 return; // no notifications to clear
3702 }
3703
3704 // Delete the last notifications (they stack up)
3705 $this->setNewtalk( false );
3706
3707 // If there is a new, unseen, revision, use its timestamp
3708 $nextid = $oldid
3709 ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3710 : null;
3711 if ( $nextid ) {
3712 $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3713 }
3714 } );
3715 }
3716
3718 return;
3719 }
3720
3721 if ( $this->isAnon() ) {
3722 // Nothing else to do...
3723 return;
3724 }
3725
3726 // Only update the timestamp if the page is being watched.
3727 // The query to find out if it is watched is cached both in memcached and per-invocation,
3728 // and when it does have to be executed, it can be on a replica DB
3729 // If this is the user's newtalk page, we always update the timestamp
3730 $force = '';
3731 if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3732 $force = 'force';
3733 }
3734
3735 MediaWikiServices::getInstance()->getWatchedItemStore()
3736 ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3737 }
3738
3745 public function clearAllNotifications() {
3747 // Do nothing if not allowed to edit the watchlist
3748 if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3749 return;
3750 }
3751
3753 $this->setNewtalk( false );
3754 return;
3755 }
3756
3757 $id = $this->getId();
3758 if ( !$id ) {
3759 return;
3760 }
3761
3762 $dbw = wfGetDB( DB_MASTER );
3763 $asOfTimes = array_unique( $dbw->selectFieldValues(
3764 'watchlist',
3765 'wl_notificationtimestamp',
3766 [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3767 __METHOD__,
3768 [ 'ORDER BY' => 'wl_notificationtimestamp DESC', 'LIMIT' => 500 ]
3769 ) );
3770 if ( !$asOfTimes ) {
3771 return;
3772 }
3773 // Immediately update the most recent touched rows, which hopefully covers what
3774 // the user sees on the watchlist page before pressing "mark all pages visited"....
3775 $dbw->update(
3776 'watchlist',
3777 [ 'wl_notificationtimestamp' => null ],
3778 [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimes ],
3779 __METHOD__
3780 );
3781 // ...and finish the older ones in a post-send update with lag checks...
3782 DeferredUpdates::addUpdate( new AutoCommitUpdate(
3783 $dbw,
3784 __METHOD__,
3785 function () use ( $dbw, $id ) {
3786 global $wgUpdateRowsPerQuery;
3787
3788 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
3789 $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
3790 $asOfTimes = array_unique( $dbw->selectFieldValues(
3791 'watchlist',
3792 'wl_notificationtimestamp',
3793 [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3794 __METHOD__
3795 ) );
3796 foreach ( array_chunk( $asOfTimes, $wgUpdateRowsPerQuery ) as $asOfTimeBatch ) {
3797 $dbw->update(
3798 'watchlist',
3799 [ 'wl_notificationtimestamp' => null ],
3800 [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimeBatch ],
3801 __METHOD__
3802 );
3803 $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
3804 }
3805 }
3806 ) );
3807 // We also need to clear here the "you have new message" notification for the own
3808 // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3809 }
3810
3816 public function getExperienceLevel() {
3817 global $wgLearnerEdits,
3821
3822 if ( $this->isAnon() ) {
3823 return false;
3824 }
3825
3826 $editCount = $this->getEditCount();
3827 $registration = $this->getRegistration();
3828 $now = time();
3829 $learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
3830 $experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
3831
3832 if (
3833 $editCount < $wgLearnerEdits ||
3834 $registration > $learnerRegistration
3835 ) {
3836 return 'newcomer';
3837 } elseif (
3838 $editCount > $wgExperiencedUserEdits &&
3839 $registration <= $experiencedRegistration
3840 ) {
3841 return 'experienced';
3842 } else {
3843 return 'learner';
3844 }
3845 }
3846
3863 protected function setCookie(
3864 $name, $value, $exp = 0, $secure = null, $params = [], $request = null
3865 ) {
3866 wfDeprecated( __METHOD__, '1.27' );
3867 if ( $request === null ) {
3868 $request = $this->getRequest();
3869 }
3870 $params['secure'] = $secure;
3871 $request->response()->setCookie( $name, $value, $exp, $params );
3872 }
3873
3884 protected function clearCookie( $name, $secure = null, $params = [] ) {
3885 wfDeprecated( __METHOD__, '1.27' );
3886 $this->setCookie( $name, '', time() - 86400, $secure, $params );
3887 }
3888
3904 protected function setExtendedLoginCookie( $name, $value, $secure ) {
3906
3907 wfDeprecated( __METHOD__, '1.27' );
3908
3909 $exp = time();
3910 $exp += $wgExtendedLoginCookieExpiration !== null
3913
3914 $this->setCookie( $name, $value, $exp, $secure );
3915 }
3916
3925 public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3926 $this->load();
3927 if ( 0 == $this->mId ) {
3928 return;
3929 }
3930
3931 $session = $this->getRequest()->getSession();
3932 if ( $request && $session->getRequest() !== $request ) {
3933 $session = $session->sessionWithRequest( $request );
3934 }
3935 $delay = $session->delaySave();
3936
3937 if ( !$session->getUser()->equals( $this ) ) {
3938 if ( !$session->canSetUser() ) {
3939 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3940 ->warning( __METHOD__ .
3941 ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3942 );
3943 return;
3944 }
3945 $session->setUser( $this );
3946 }
3947
3948 $session->setRememberUser( $rememberMe );
3949 if ( $secure !== null ) {
3950 $session->setForceHTTPS( $secure );
3951 }
3952
3953 $session->persist();
3954
3955 ScopedCallback::consume( $delay );
3956 }
3957
3961 public function logout() {
3962 // Avoid PHP 7.1 warning of passing $this by reference
3963 $user = $this;
3964 if ( Hooks::run( 'UserLogout', [ &$user ] ) ) {
3965 $this->doLogout();
3966 }
3967 }
3968
3973 public function doLogout() {
3974 $session = $this->getRequest()->getSession();
3975 if ( !$session->canSetUser() ) {
3976 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3977 ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3978 $error = 'immutable';
3979 } elseif ( !$session->getUser()->equals( $this ) ) {
3980 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3981 ->warning( __METHOD__ .
3982 ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3983 );
3984 // But we still may as well make this user object anon
3985 $this->clearInstanceCache( 'defaults' );
3986 $error = 'wronguser';
3987 } else {
3988 $this->clearInstanceCache( 'defaults' );
3989 $delay = $session->delaySave();
3990 $session->unpersist(); // Clear cookies (T127436)
3991 $session->setLoggedOutTimestamp( time() );
3992 $session->setUser( new User );
3993 $session->set( 'wsUserID', 0 ); // Other code expects this
3994 $session->resetAllTokens();
3995 ScopedCallback::consume( $delay );
3996 $error = false;
3997 }
3998 \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3999 'event' => 'logout',
4000 'successful' => $error === false,
4001 'status' => $error ?: 'success',
4002 ] );
4003 }
4004
4009 public function saveSettings() {
4010 if ( wfReadOnly() ) {
4011 // @TODO: caller should deal with this instead!
4012 // This should really just be an exception.
4013 MWExceptionHandler::logException( new DBExpectedError(
4014 null,
4015 "Could not update user with ID '{$this->mId}'; DB is read-only."
4016 ) );
4017 return;
4018 }
4019
4020 $this->load();
4021 if ( 0 == $this->mId ) {
4022 return; // anon
4023 }
4024
4025 // Get a new user_touched that is higher than the old one.
4026 // This will be used for a CAS check as a last-resort safety
4027 // check against race conditions and replica DB lag.
4028 $newTouched = $this->newTouchedTimestamp();
4029
4030 $dbw = wfGetDB( DB_MASTER );
4031 $dbw->update( 'user',
4032 [ /* SET */
4033 'user_name' => $this->mName,
4034 'user_real_name' => $this->mRealName,
4035 'user_email' => $this->mEmail,
4036 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4037 'user_touched' => $dbw->timestamp( $newTouched ),
4038 'user_token' => strval( $this->mToken ),
4039 'user_email_token' => $this->mEmailToken,
4040 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
4041 ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
4042 'user_id' => $this->mId,
4043 ] ), __METHOD__
4044 );
4045
4046 if ( !$dbw->affectedRows() ) {
4047 // Maybe the problem was a missed cache update; clear it to be safe
4048 $this->clearSharedCache( 'refresh' );
4049 // User was changed in the meantime or loaded with stale data
4050 $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
4051 throw new MWException(
4052 "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
4053 " the version of the user to be saved is older than the current version."
4054 );
4055 }
4056
4057 $this->mTouched = $newTouched;
4058 $this->saveOptions();
4059
4060 Hooks::run( 'UserSaveSettings', [ $this ] );
4061 $this->clearSharedCache();
4062 $this->getUserPage()->invalidateCache();
4063 }
4064
4071 public function idForName( $flags = 0 ) {
4072 $s = trim( $this->getName() );
4073 if ( $s === '' ) {
4074 return 0;
4075 }
4076
4077 $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
4078 ? wfGetDB( DB_MASTER )
4079 : wfGetDB( DB_REPLICA );
4080
4081 $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
4082 ? [ 'LOCK IN SHARE MODE' ]
4083 : [];
4084
4085 $id = $db->selectField( 'user',
4086 'user_id', [ 'user_name' => $s ], __METHOD__, $options );
4087
4088 return (int)$id;
4089 }
4090
4106 public static function createNew( $name, $params = [] ) {
4107 foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
4108 if ( isset( $params[$field] ) ) {
4109 wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
4110 unset( $params[$field] );
4111 }
4112 }
4113
4114 $user = new User;
4115 $user->load();
4116 $user->setToken(); // init token
4117 if ( isset( $params['options'] ) ) {
4118 $user->mOptions = $params['options'] + (array)$user->mOptions;
4119 unset( $params['options'] );
4120 }
4121 $dbw = wfGetDB( DB_MASTER );
4122
4123 $noPass = PasswordFactory::newInvalidPassword()->toString();
4124
4125 $fields = [
4126 'user_name' => $name,
4127 'user_password' => $noPass,
4128 'user_newpassword' => $noPass,
4129 'user_email' => $user->mEmail,
4130 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
4131 'user_real_name' => $user->mRealName,
4132 'user_token' => strval( $user->mToken ),
4133 'user_registration' => $dbw->timestamp( $user->mRegistration ),
4134 'user_editcount' => 0,
4135 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
4136 ];
4137 foreach ( $params as $name => $value ) {
4138 $fields["user_$name"] = $value;
4139 }
4140 $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
4141 if ( $dbw->affectedRows() ) {
4142 $newUser = self::newFromId( $dbw->insertId() );
4143 } else {
4144 $newUser = null;
4145 }
4146 return $newUser;
4147 }
4148
4175 public function addToDatabase() {
4176 $this->load();
4177 if ( !$this->mToken ) {
4178 $this->setToken(); // init token
4179 }
4180
4181 if ( !is_string( $this->mName ) ) {
4182 throw new RuntimeException( "User name field is not set." );
4183 }
4184
4185 $this->mTouched = $this->newTouchedTimestamp();
4186
4187 $noPass = PasswordFactory::newInvalidPassword()->toString();
4188
4189 $dbw = wfGetDB( DB_MASTER );
4190 $dbw->insert( 'user',
4191 [
4192 'user_name' => $this->mName,
4193 'user_password' => $noPass,
4194 'user_newpassword' => $noPass,
4195 'user_email' => $this->mEmail,
4196 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4197 'user_real_name' => $this->mRealName,
4198 'user_token' => strval( $this->mToken ),
4199 'user_registration' => $dbw->timestamp( $this->mRegistration ),
4200 'user_editcount' => 0,
4201 'user_touched' => $dbw->timestamp( $this->mTouched ),
4202 ], __METHOD__,
4203 [ 'IGNORE' ]
4204 );
4205 if ( !$dbw->affectedRows() ) {
4206 // Use locking reads to bypass any REPEATABLE-READ snapshot.
4207 $this->mId = $dbw->selectField(
4208 'user',
4209 'user_id',
4210 [ 'user_name' => $this->mName ],
4211 __METHOD__,
4212 [ 'LOCK IN SHARE MODE' ]
4213 );
4214 $loaded = false;
4215 if ( $this->mId ) {
4216 if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4217 $loaded = true;
4218 }
4219 }
4220 if ( !$loaded ) {
4221 throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
4222 "to insert user '{$this->mName}' row, but it was not present in select!" );
4223 }
4224 return Status::newFatal( 'userexists' );
4225 }
4226 $this->mId = $dbw->insertId();
4227 self::$idCacheByName[$this->mName] = $this->mId;
4228
4229 // Clear instance cache other than user table data, which is already accurate
4230 $this->clearInstanceCache();
4231
4232 $this->saveOptions();
4233 return Status::newGood();
4234 }
4235
4241 public function spreadAnyEditBlock() {
4242 if ( $this->isLoggedIn() && $this->isBlocked() ) {
4243 return $this->spreadBlock();
4244 }
4245
4246 return false;
4247 }
4248
4254 protected function spreadBlock() {
4255 wfDebug( __METHOD__ . "()\n" );
4256 $this->load();
4257 if ( $this->mId == 0 ) {
4258 return false;
4259 }
4260
4261 $userblock = Block::newFromTarget( $this->getName() );
4262 if ( !$userblock ) {
4263 return false;
4264 }
4265
4266 return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4267 }
4268
4273 public function isBlockedFromCreateAccount() {
4274 $this->getBlockedStatus();
4275 if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4276 return $this->mBlock;
4277 }
4278
4279 # T15611: if the IP address the user is trying to create an account from is
4280 # blocked with createaccount disabled, prevent new account creation there even
4281 # when the user is logged in
4282 if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4283 $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4284 }
4285 return $this->mBlockedFromCreateAccount instanceof Block
4286 && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4287 ? $this->mBlockedFromCreateAccount
4288 : false;
4289 }
4290
4295 public function isBlockedFromEmailuser() {
4296 $this->getBlockedStatus();
4297 return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4298 }
4299
4304 public function isAllowedToCreateAccount() {
4305 return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4306 }
4307
4313 public function getUserPage() {
4314 return Title::makeTitle( NS_USER, $this->getName() );
4315 }
4316
4322 public function getTalkPage() {
4323 $title = $this->getUserPage();
4324 return $title->getTalkPage();
4325 }
4326
4332 public function isNewbie() {
4333 return !$this->isAllowed( 'autoconfirmed' );
4334 }
4335
4342 public function checkPassword( $password ) {
4343 $manager = AuthManager::singleton();
4344 $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4345 $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4346 [
4347 'username' => $this->getName(),
4348 'password' => $password,
4349 ]
4350 );
4351 $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4352 switch ( $res->status ) {
4353 case AuthenticationResponse::PASS:
4354 return true;
4355 case AuthenticationResponse::FAIL:
4356 // Hope it's not a PreAuthenticationProvider that failed...
4357 \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
4358 ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4359 return false;
4360 default:
4361 throw new BadMethodCallException(
4362 'AuthManager returned a response unsupported by ' . __METHOD__
4363 );
4364 }
4365 }
4366
4375 public function checkTemporaryPassword( $plaintext ) {
4376 // Can't check the temporary password individually.
4377 return $this->checkPassword( $plaintext );
4378 }
4379
4391 public function getEditTokenObject( $salt = '', $request = null ) {
4392 if ( $this->isAnon() ) {
4393 return new LoggedOutEditToken();
4394 }
4395
4396 if ( !$request ) {
4397 $request = $this->getRequest();
4398 }
4399 return $request->getSession()->getToken( $salt );
4400 }
4401
4415 public function getEditToken( $salt = '', $request = null ) {
4416 return $this->getEditTokenObject( $salt, $request )->toString();
4417 }
4418
4425 public static function getEditTokenTimestamp( $val ) {
4426 wfDeprecated( __METHOD__, '1.27' );
4427 return MediaWiki\Session\Token::getTimestamp( $val );
4428 }
4429
4442 public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4443 return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4444 }
4445
4456 public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4457 $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4458 return $this->matchEditToken( $val, $salt, $request, $maxage );
4459 }
4460
4468 public function sendConfirmationMail( $type = 'created' ) {
4469 global $wgLang;
4470 $expiration = null; // gets passed-by-ref and defined in next line.
4471 $token = $this->confirmationToken( $expiration );
4472 $url = $this->confirmationTokenUrl( $token );
4473 $invalidateURL = $this->invalidationTokenUrl( $token );
4474 $this->saveSettings();
4475
4476 if ( $type == 'created' || $type === false ) {
4477 $message = 'confirmemail_body';
4478 } elseif ( $type === true ) {
4479 $message = 'confirmemail_body_changed';
4480 } else {
4481 // Messages: confirmemail_body_changed, confirmemail_body_set
4482 $message = 'confirmemail_body_' . $type;
4483 }
4484
4485 return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4486 wfMessage( $message,
4487 $this->getRequest()->getIP(),
4488 $this->getName(),
4489 $url,
4490 $wgLang->userTimeAndDate( $expiration, $this ),
4491 $invalidateURL,
4492 $wgLang->userDate( $expiration, $this ),
4493 $wgLang->userTime( $expiration, $this ) )->text() );
4494 }
4495
4507 public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4508 global $wgPasswordSender;
4509
4510 if ( $from instanceof User ) {
4511 $sender = MailAddress::newFromUser( $from );
4512 } else {
4513 $sender = new MailAddress( $wgPasswordSender,
4514 wfMessage( 'emailsender' )->inContentLanguage()->text() );
4515 }
4516 $to = MailAddress::newFromUser( $this );
4517
4518 return UserMailer::send( $to, $sender, $subject, $body, [
4519 'replyTo' => $replyto,
4520 ] );
4521 }
4522
4533 protected function confirmationToken( &$expiration ) {
4535 $now = time();
4536 $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4537 $expiration = wfTimestamp( TS_MW, $expires );
4538 $this->load();
4539 $token = MWCryptRand::generateHex( 32 );
4540 $hash = md5( $token );
4541 $this->mEmailToken = $hash;
4542 $this->mEmailTokenExpires = $expiration;
4543 return $token;
4544 }
4545
4551 protected function confirmationTokenUrl( $token ) {
4552 return $this->getTokenUrl( 'ConfirmEmail', $token );
4553 }
4554
4560 protected function invalidationTokenUrl( $token ) {
4561 return $this->getTokenUrl( 'InvalidateEmail', $token );
4562 }
4563
4578 protected function getTokenUrl( $page, $token ) {
4579 // Hack to bypass localization of 'Special:'
4580 $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4581 return $title->getCanonicalURL();
4582 }
4583
4591 public function confirmEmail() {
4592 // Check if it's already confirmed, so we don't touch the database
4593 // and fire the ConfirmEmailComplete hook on redundant confirmations.
4594 if ( !$this->isEmailConfirmed() ) {
4596 Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4597 }
4598 return true;
4599 }
4600
4608 public function invalidateEmail() {
4609 $this->load();
4610 $this->mEmailToken = null;
4611 $this->mEmailTokenExpires = null;
4612 $this->setEmailAuthenticationTimestamp( null );
4613 $this->mEmail = '';
4614 Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4615 return true;
4616 }
4617
4622 public function setEmailAuthenticationTimestamp( $timestamp ) {
4623 $this->load();
4624 $this->mEmailAuthenticated = $timestamp;
4625 Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4626 }
4627
4633 public function canSendEmail() {
4635 if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4636 return false;
4637 }
4638 $canSend = $this->isEmailConfirmed();
4639 // Avoid PHP 7.1 warning of passing $this by reference
4640 $user = $this;
4641 Hooks::run( 'UserCanSendEmail', [ &$user, &$canSend ] );
4642 return $canSend;
4643 }
4644
4650 public function canReceiveEmail() {
4651 return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4652 }
4653
4664 public function isEmailConfirmed() {
4666 $this->load();
4667 // Avoid PHP 7.1 warning of passing $this by reference
4668 $user = $this;
4669 $confirmed = true;
4670 if ( Hooks::run( 'EmailConfirmed', [ &$user, &$confirmed ] ) ) {
4671 if ( $this->isAnon() ) {
4672 return false;
4673 }
4674 if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4675 return false;
4676 }
4678 return false;
4679 }
4680 return true;
4681 } else {
4682 return $confirmed;
4683 }
4684 }
4685
4690 public function isEmailConfirmationPending() {
4692 return $wgEmailAuthentication &&
4693 !$this->isEmailConfirmed() &&
4694 $this->mEmailToken &&
4695 $this->mEmailTokenExpires > wfTimestamp();
4696 }
4697
4705 public function getRegistration() {
4706 if ( $this->isAnon() ) {
4707 return false;
4708 }
4709 $this->load();
4710 return $this->mRegistration;
4711 }
4712
4719 public function getFirstEditTimestamp() {
4720 if ( $this->getId() == 0 ) {
4721 return false; // anons
4722 }
4723 $dbr = wfGetDB( DB_REPLICA );
4724 $time = $dbr->selectField( 'revision', 'rev_timestamp',
4725 [ 'rev_user' => $this->getId() ],
4726 __METHOD__,
4727 [ 'ORDER BY' => 'rev_timestamp ASC' ]
4728 );
4729 if ( !$time ) {
4730 return false; // no edits
4731 }
4732 return wfTimestamp( TS_MW, $time );
4733 }
4734
4741 public static function getGroupPermissions( $groups ) {
4743 $rights = [];
4744 // grant every granted permission first
4745 foreach ( $groups as $group ) {
4746 if ( isset( $wgGroupPermissions[$group] ) ) {
4747 $rights = array_merge( $rights,
4748 // array_filter removes empty items
4749 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4750 }
4751 }
4752 // now revoke the revoked permissions
4753 foreach ( $groups as $group ) {
4754 if ( isset( $wgRevokePermissions[$group] ) ) {
4755 $rights = array_diff( $rights,
4756 array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4757 }
4758 }
4759 return array_unique( $rights );
4760 }
4761
4768 public static function getGroupsWithPermission( $role ) {
4769 global $wgGroupPermissions;
4770 $allowedGroups = [];
4771 foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4772 if ( self::groupHasPermission( $group, $role ) ) {
4773 $allowedGroups[] = $group;
4774 }
4775 }
4776 return $allowedGroups;
4777 }
4778
4791 public static function groupHasPermission( $group, $role ) {
4793 return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
4794 && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
4795 }
4796
4811 public static function isEveryoneAllowed( $right ) {
4813 static $cache = [];
4814
4815 // Use the cached results, except in unit tests which rely on
4816 // being able change the permission mid-request
4817 if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
4818 return $cache[$right];
4819 }
4820
4821 if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
4822 $cache[$right] = false;
4823 return false;
4824 }
4825
4826 // If it's revoked anywhere, then everyone doesn't have it
4827 foreach ( $wgRevokePermissions as $rights ) {
4828 if ( isset( $rights[$right] ) && $rights[$right] ) {
4829 $cache[$right] = false;
4830 return false;
4831 }
4832 }
4833
4834 // Remove any rights that aren't allowed to the global-session user,
4835 // unless there are no sessions for this endpoint.
4836 if ( !defined( 'MW_NO_SESSION' ) ) {
4837 $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
4838 if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
4839 $cache[$right] = false;
4840 return false;
4841 }
4842 }
4843
4844 // Allow extensions to say false
4845 if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
4846 $cache[$right] = false;
4847 return false;
4848 }
4849
4850 $cache[$right] = true;
4851 return true;
4852 }
4853
4861 public static function getGroupName( $group ) {
4862 wfDeprecated( __METHOD__, '1.29' );
4863 return UserGroupMembership::getGroupName( $group );
4864 }
4865
4874 public static function getGroupMember( $group, $username = '#' ) {
4875 wfDeprecated( __METHOD__, '1.29' );
4877 }
4878
4885 public static function getAllGroups() {
4887 return array_diff(
4888 array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4889 self::getImplicitGroups()
4890 );
4891 }
4892
4897 public static function getAllRights() {
4898 if ( self::$mAllRights === false ) {
4899 global $wgAvailableRights;
4900 if ( count( $wgAvailableRights ) ) {
4901 self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
4902 } else {
4903 self::$mAllRights = self::$mCoreRights;
4904 }
4905 Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
4906 }
4907 return self::$mAllRights;
4908 }
4909
4914 public static function getImplicitGroups() {
4915 global $wgImplicitGroups;
4916
4917 $groups = $wgImplicitGroups;
4918 # Deprecated, use $wgImplicitGroups instead
4919 Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
4920
4921 return $groups;
4922 }
4923
4931 public static function getGroupPage( $group ) {
4932 wfDeprecated( __METHOD__, '1.29' );
4933 return UserGroupMembership::getGroupPage( $group );
4934 }
4935
4946 public static function makeGroupLinkHTML( $group, $text = '' ) {
4947 wfDeprecated( __METHOD__, '1.29' );
4948
4949 if ( $text == '' ) {
4950 $text = UserGroupMembership::getGroupName( $group );
4951 }
4952 $title = UserGroupMembership::getGroupPage( $group );
4953 if ( $title ) {
4954 return MediaWikiServices::getInstance()
4955 ->getLinkRenderer()->makeLink( $title, $text );
4956 } else {
4957 return htmlspecialchars( $text );
4958 }
4959 }
4960
4971 public static function makeGroupLinkWiki( $group, $text = '' ) {
4972 wfDeprecated( __METHOD__, '1.29' );
4973
4974 if ( $text == '' ) {
4975 $text = UserGroupMembership::getGroupName( $group );
4976 }
4977 $title = UserGroupMembership::getGroupPage( $group );
4978 if ( $title ) {
4979 $page = $title->getFullText();
4980 return "[[$page|$text]]";
4981 } else {
4982 return $text;
4983 }
4984 }
4985
4995 public static function changeableByGroup( $group ) {
4997
4998 $groups = [
4999 'add' => [],
5000 'remove' => [],
5001 'add-self' => [],
5002 'remove-self' => []
5003 ];
5004
5005 if ( empty( $wgAddGroups[$group] ) ) {
5006 // Don't add anything to $groups
5007 } elseif ( $wgAddGroups[$group] === true ) {
5008 // You get everything
5009 $groups['add'] = self::getAllGroups();
5010 } elseif ( is_array( $wgAddGroups[$group] ) ) {
5011 $groups['add'] = $wgAddGroups[$group];
5012 }
5013
5014 // Same thing for remove
5015 if ( empty( $wgRemoveGroups[$group] ) ) {
5016 // Do nothing
5017 } elseif ( $wgRemoveGroups[$group] === true ) {
5018 $groups['remove'] = self::getAllGroups();
5019 } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
5020 $groups['remove'] = $wgRemoveGroups[$group];
5021 }
5022
5023 // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
5024 if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
5025 foreach ( $wgGroupsAddToSelf as $key => $value ) {
5026 if ( is_int( $key ) ) {
5027 $wgGroupsAddToSelf['user'][] = $value;
5028 }
5029 }
5030 }
5031
5032 if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
5033 foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
5034 if ( is_int( $key ) ) {
5035 $wgGroupsRemoveFromSelf['user'][] = $value;
5036 }
5037 }
5038 }
5039
5040 // Now figure out what groups the user can add to him/herself
5041 if ( empty( $wgGroupsAddToSelf[$group] ) ) {
5042 // Do nothing
5043 } elseif ( $wgGroupsAddToSelf[$group] === true ) {
5044 // No idea WHY this would be used, but it's there
5045 $groups['add-self'] = self::getAllGroups();
5046 } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
5047 $groups['add-self'] = $wgGroupsAddToSelf[$group];
5048 }
5049
5050 if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
5051 // Do nothing
5052 } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
5053 $groups['remove-self'] = self::getAllGroups();
5054 } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
5055 $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
5056 }
5057
5058 return $groups;
5059 }
5060
5068 public function changeableGroups() {
5069 if ( $this->isAllowed( 'userrights' ) ) {
5070 // This group gives the right to modify everything (reverse-
5071 // compatibility with old "userrights lets you change
5072 // everything")
5073 // Using array_merge to make the groups reindexed
5074 $all = array_merge( self::getAllGroups() );
5075 return [
5076 'add' => $all,
5077 'remove' => $all,
5078 'add-self' => [],
5079 'remove-self' => []
5080 ];
5081 }
5082
5083 // Okay, it's not so simple, we will have to go through the arrays
5084 $groups = [
5085 'add' => [],
5086 'remove' => [],
5087 'add-self' => [],
5088 'remove-self' => []
5089 ];
5090 $addergroups = $this->getEffectiveGroups();
5091
5092 foreach ( $addergroups as $addergroup ) {
5093 $groups = array_merge_recursive(
5094 $groups, $this->changeableByGroup( $addergroup )
5095 );
5096 $groups['add'] = array_unique( $groups['add'] );
5097 $groups['remove'] = array_unique( $groups['remove'] );
5098 $groups['add-self'] = array_unique( $groups['add-self'] );
5099 $groups['remove-self'] = array_unique( $groups['remove-self'] );
5100 }
5101 return $groups;
5102 }
5103
5110 public function incEditCount() {
5111 wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
5112 function () {
5113 $this->incEditCountImmediate();
5114 },
5115 __METHOD__
5116 );
5117 }
5118
5124 public function incEditCountImmediate() {
5125 if ( $this->isAnon() ) {
5126 return;
5127 }
5128
5129 $dbw = wfGetDB( DB_MASTER );
5130 // No rows will be "affected" if user_editcount is NULL
5131 $dbw->update(
5132 'user',
5133 [ 'user_editcount=user_editcount+1' ],
5134 [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
5135 __METHOD__
5136 );
5137 // Lazy initialization check...
5138 if ( $dbw->affectedRows() == 0 ) {
5139 // Now here's a goddamn hack...
5140 $dbr = wfGetDB( DB_REPLICA );
5141 if ( $dbr !== $dbw ) {
5142 // If we actually have a replica DB server, the count is
5143 // at least one behind because the current transaction
5144 // has not been committed and replicated.
5145 $this->mEditCount = $this->initEditCount( 1 );
5146 } else {
5147 // But if DB_REPLICA is selecting the master, then the
5148 // count we just read includes the revision that was
5149 // just added in the working transaction.
5150 $this->mEditCount = $this->initEditCount();
5151 }
5152 } else {
5153 if ( $this->mEditCount === null ) {
5154 $this->getEditCount();
5155 $dbr = wfGetDB( DB_REPLICA );
5156 $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
5157 } else {
5158 $this->mEditCount++;
5159 }
5160 }
5161 // Edit count in user cache too
5162 $this->invalidateCache();
5163 }
5164
5171 protected function initEditCount( $add = 0 ) {
5172 // Pull from a replica DB to be less cruel to servers
5173 // Accuracy isn't the point anyway here
5174 $dbr = wfGetDB( DB_REPLICA );
5175 $count = (int)$dbr->selectField(
5176 'revision',
5177 'COUNT(rev_user)',
5178 [ 'rev_user' => $this->getId() ],
5179 __METHOD__
5180 );
5181 $count = $count + $add;
5182
5183 $dbw = wfGetDB( DB_MASTER );
5184 $dbw->update(
5185 'user',
5186 [ 'user_editcount' => $count ],
5187 [ 'user_id' => $this->getId() ],
5188 __METHOD__
5189 );
5190
5191 return $count;
5192 }
5193
5201 public static function getRightDescription( $right ) {
5202 $key = "right-$right";
5203 $msg = wfMessage( $key );
5204 return $msg->isDisabled() ? $right : $msg->text();
5205 }
5206
5214 public static function getGrantName( $grant ) {
5215 $key = "grant-$grant";
5216 $msg = wfMessage( $key );
5217 return $msg->isDisabled() ? $grant : $msg->text();
5218 }
5219
5240 public function addNewUserLogEntry( $action = false, $reason = '' ) {
5241 return true; // disabled
5242 }
5243
5253 $this->addNewUserLogEntry( 'autocreate' );
5254
5255 return true;
5256 }
5257
5263 protected function loadOptions( $data = null ) {
5264 global $wgContLang;
5265
5266 $this->load();
5267
5268 if ( $this->mOptionsLoaded ) {
5269 return;
5270 }
5271
5272 $this->mOptions = self::getDefaultOptions();
5273
5274 if ( !$this->getId() ) {
5275 // For unlogged-in users, load language/variant options from request.
5276 // There's no need to do it for logged-in users: they can set preferences,
5277 // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5278 // so don't override user's choice (especially when the user chooses site default).
5279 $variant = $wgContLang->getDefaultVariant();
5280 $this->mOptions['variant'] = $variant;
5281 $this->mOptions['language'] = $variant;
5282 $this->mOptionsLoaded = true;
5283 return;
5284 }
5285
5286 // Maybe load from the object
5287 if ( !is_null( $this->mOptionOverrides ) ) {
5288 wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5289 foreach ( $this->mOptionOverrides as $key => $value ) {
5290 $this->mOptions[$key] = $value;
5291 }
5292 } else {
5293 if ( !is_array( $data ) ) {
5294 wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5295 // Load from database
5296 $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5297 ? wfGetDB( DB_MASTER )
5298 : wfGetDB( DB_REPLICA );
5299
5300 $res = $dbr->select(
5301 'user_properties',
5302 [ 'up_property', 'up_value' ],
5303 [ 'up_user' => $this->getId() ],
5304 __METHOD__
5305 );
5306
5307 $this->mOptionOverrides = [];
5308 $data = [];
5309 foreach ( $res as $row ) {
5310 // Convert '0' to 0. PHP's boolean conversion considers them both
5311 // false, but e.g. JavaScript considers the former as true.
5312 // @todo: T54542 Somehow determine the desired type (string/int/bool)
5313 // and convert all values here.
5314 if ( $row->up_value === '0' ) {
5315 $row->up_value = 0;
5316 }
5317 $data[$row->up_property] = $row->up_value;
5318 }
5319 }
5320
5321 // Convert the email blacklist from a new line delimited string
5322 // to an array of ids.
5323 if ( isset( $data['email-blacklist'] ) && $data['email-blacklist'] ) {
5324 $data['email-blacklist'] = array_map( 'intval', explode( "\n", $data['email-blacklist'] ) );
5325 }
5326
5327 foreach ( $data as $property => $value ) {
5328 $this->mOptionOverrides[$property] = $value;
5329 $this->mOptions[$property] = $value;
5330 }
5331 }
5332
5333 $this->mOptionsLoaded = true;
5334
5335 Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5336 }
5337
5343 protected function saveOptions() {
5344 $this->loadOptions();
5345
5346 // Not using getOptions(), to keep hidden preferences in database
5347 $saveOptions = $this->mOptions;
5348
5349 // Convert usernames to ids.
5350 if ( isset( $this->mOptions['email-blacklist'] ) ) {
5351 if ( $this->mOptions['email-blacklist'] ) {
5352 $value = $this->mOptions['email-blacklist'];
5353 // Email Blacklist may be an array of ids or a string of new line
5354 // delimnated user names.
5355 if ( is_array( $value ) ) {
5356 $ids = array_filter( $value, 'is_numeric' );
5357 } else {
5358 $lookup = CentralIdLookup::factory();
5359 $ids = $lookup->centralIdsFromNames( explode( "\n", $value ), $this );
5360 }
5361 $this->mOptions['email-blacklist'] = $ids;
5362 $saveOptions['email-blacklist'] = implode( "\n", $this->mOptions['email-blacklist'] );
5363 } else {
5364 // If the blacklist is empty, set it to null rather than an empty string.
5365 $this->mOptions['email-blacklist'] = null;
5366 }
5367 }
5368
5369 // Allow hooks to abort, for instance to save to a global profile.
5370 // Reset options to default state before saving.
5371 if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5372 return;
5373 }
5374
5375 $userId = $this->getId();
5376
5377 $insert_rows = []; // all the new preference rows
5378 foreach ( $saveOptions as $key => $value ) {
5379 // Don't bother storing default values
5380 $defaultOption = self::getDefaultOption( $key );
5381 if ( ( $defaultOption === null && $value !== false && $value !== null )
5382 || $value != $defaultOption
5383 ) {
5384 $insert_rows[] = [
5385 'up_user' => $userId,
5386 'up_property' => $key,
5387 'up_value' => $value,
5388 ];
5389 }
5390 }
5391
5392 $dbw = wfGetDB( DB_MASTER );
5393
5394 $res = $dbw->select( 'user_properties',
5395 [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5396
5397 // Find prior rows that need to be removed or updated. These rows will
5398 // all be deleted (the latter so that INSERT IGNORE applies the new values).
5399 $keysDelete = [];
5400 foreach ( $res as $row ) {
5401 if ( !isset( $saveOptions[$row->up_property] )
5402 || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5403 ) {
5404 $keysDelete[] = $row->up_property;
5405 }
5406 }
5407
5408 if ( count( $keysDelete ) ) {
5409 // Do the DELETE by PRIMARY KEY for prior rows.
5410 // In the past a very large portion of calls to this function are for setting
5411 // 'rememberpassword' for new accounts (a preference that has since been removed).
5412 // Doing a blanket per-user DELETE for new accounts with no rows in the table
5413 // caused gap locks on [max user ID,+infinity) which caused high contention since
5414 // updates would pile up on each other as they are for higher (newer) user IDs.
5415 // It might not be necessary these days, but it shouldn't hurt either.
5416 $dbw->delete( 'user_properties',
5417 [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5418 }
5419 // Insert the new preference rows
5420 $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5421 }
5422
5429 public static function getPasswordFactory() {
5430 wfDeprecated( __METHOD__, '1.27' );
5431 $ret = new PasswordFactory();
5432 $ret->init( RequestContext::getMain()->getConfig() );
5433 return $ret;
5434 }
5435
5460 public static function passwordChangeInputAttribs() {
5462
5463 if ( $wgMinimalPasswordLength == 0 ) {
5464 return [];
5465 }
5466
5467 # Note that the pattern requirement will always be satisfied if the
5468 # input is empty, so we need required in all cases.
5469
5470 # @todo FIXME: T25769: This needs to not claim the password is required
5471 # if e-mail confirmation is being used. Since HTML5 input validation
5472 # is b0rked anyway in some browsers, just return nothing. When it's
5473 # re-enabled, fix this code to not output required for e-mail
5474 # registration.
5475 # $ret = array( 'required' );
5476 $ret = [];
5477
5478 # We can't actually do this right now, because Opera 9.6 will print out
5479 # the entered password visibly in its error message! When other
5480 # browsers add support for this attribute, or Opera fixes its support,
5481 # we can add support with a version check to avoid doing this on Opera
5482 # versions where it will be a problem. Reported to Opera as
5483 # DSK-262266, but they don't have a public bug tracker for us to follow.
5484 /*
5485 if ( $wgMinimalPasswordLength > 1 ) {
5486 $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}';
5487 $ret['title'] = wfMessage( 'passwordtooshort' )
5488 ->numParams( $wgMinimalPasswordLength )->text();
5489 }
5490 */
5491
5492 return $ret;
5493 }
5494
5500 public static function selectFields() {
5501 return [
5502 'user_id',
5503 'user_name',
5504 'user_real_name',
5505 'user_email',
5506 'user_touched',
5507 'user_token',
5508 'user_email_authenticated',
5509 'user_email_token',
5510 'user_email_token_expires',
5511 'user_registration',
5512 'user_editcount',
5513 ];
5514 }
5515
5523 static function newFatalPermissionDeniedStatus( $permission ) {
5524 global $wgLang;
5525
5526 $groups = [];
5527 foreach ( self::getGroupsWithPermission( $permission ) as $group ) {
5528 $groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
5529 }
5530
5531 if ( $groups ) {
5532 return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5533 } else {
5534 return Status::newFatal( 'badaccess-group0' );
5535 }
5536 }
5537
5547 public function getInstanceForUpdate() {
5548 if ( !$this->getId() ) {
5549 return null; // anon
5550 }
5551
5552 $user = self::newFromId( $this->getId() );
5553 if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5554 return null;
5555 }
5556
5557 return $user;
5558 }
5559
5567 public function equals( User $user ) {
5568 return $this->getName() === $user->getName();
5569 }
5570}
$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.
$wgCookieExpiration
Default cookie lifetime, in seconds.
$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.
$wgExtendedLoginCookieExpiration
Default login cookie lifetime, in seconds.
$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.
$wgUpdateRowsPerQuery
Number of rows to update per query.
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.
wfGetLB( $wiki=false)
Get a load balancer object.
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
foreach( $wgExtensionFunctions as $func) if(!defined('MW_NO_SESSION') &&! $wgCommandLineMode) if(! $wgCommandLineMode) $wgFullyInitialised
Definition Setup.php:892
$wgUseEnotif
Definition Setup.php:368
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:662
Deferrable Update for closure/callback updates that should use auto-commit mode.
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:1481
prevents( $action, $x=null)
Get/set whether the Block prevents a given action.
Definition Block.php:1012
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:1151
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition Block.php:1232
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the 'BlockID' cookie.
Definition Block.php:1517
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:1112
static factory( $providerId=null)
Fetch a CentralIdLookup.
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
static hasFlags( $bitfield, $flags)
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
static array $languagesWithVariants
languages supporting variants
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, $forceStrong=false)
Generate a run of (ideally) 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 log entries manually, to inject them into the database.
Definition LogEntry.php:400
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.
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
Factory class for creating and checking Password objects.
static getSaveBlacklist()
static getPreferences( $user, IContextSource $context)
static getMain()
Static methods.
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition Revision.php:309
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition Revision.php:116
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition Skin.php:95
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 newFromRow( $row)
Creates a new UserGroupMembership object from a database row.
static getMembershipsForUser( $userId, IDatabase $db=null)
Returns UserGroupMembership objects for all the groups a user currently belongs to.
static getGroupPage( $group)
Gets the title of a page describing a particular user group.
static getMembership( $userId, $group, IDatabase $db=null)
Returns a UserGroupMembership object that pertains to the given user and group, or false if the user ...
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
static getGroupName( $group)
Gets the localized friendly name for a group, if it exists.
static getGroupMemberName( $group, $username)
Gets the localized name for a member of a group, if it exists.
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:51
loadFromSession()
Load user data from the session.
Definition User.php:1221
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition User.php:3643
string $mEmailToken
Definition User.php:223
string $mToken
Definition User.php:219
string $mTouched
TS_MW timestamp from the DB.
Definition User.php:215
logout()
Log this user out.
Definition User.php:3961
getOptions( $flags=0)
Get all user's options.
Definition User.php:2910
getRequest()
Get the WebRequest object to use with this object.
Definition User.php:3612
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition User.php:1016
Block $mBlockedFromCreateAccount
Definition User.php:303
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2249
addToDatabase()
Add this existing user object to the database.
Definition User.php:4175
static passwordChangeInputAttribs()
Provide an array of HTML5 attributes to put on an input element intended for the user to enter a new ...
Definition User.php:5460
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition User.php:3224
isBlocked( $bFromSlave=true)
Check if user is blocked.
Definition User.php:2067
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition User.php:3816
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition User.php:4811
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:550
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition User.php:3159
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition User.php:755
invalidateCache()
Immediately touch the user data cache for this account.
Definition User.php:2520
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition User.php:96
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition User.php:2773
array $mOptionOverrides
Definition User.php:238
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition User.php:4295
$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:975
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition User.php:4719
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:3075
getBlockedStatus( $bFromSlave=true)
Get blocking information.
Definition User.php:1634
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition User.php:4995
const VERSION
@const int Serialized record version.
Definition User.php:72
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:4391
static getAllGroups()
Return the set of defined explicit groups.
Definition User.php:4885
bool $mAllowUsertalk
Definition User.php:300
string $mEmailTokenExpires
Definition User.php:225
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition User.php:3925
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:4415
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition User.php:1787
int $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition User.php:306
static $mAllRights
String Cached results of getAllRights()
Definition User.php:201
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition User.php:4578
addGroup( $group, $expiry=null)
Add the user to the given group.
Definition User.php:3436
array $mEffectiveGroups
Definition User.php:279
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2141
string $mQuickTouched
TS_MW timestamp from cache.
Definition User.php:217
const INVALID_TOKEN
@const string An invalid value for user_token
Definition User.php:60
isSafeToLoad()
Test if it's safe to load this User object.
Definition User.php:345
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition User.php:4791
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition User.php:4664
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition User.php:4241
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition User.php:3599
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition User.php:425
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition User.php:3565
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition User.php:2586
array $mGroups
No longer used since 1.29; use User::getGroups() instead.
Definition User.php:234
setName( $str)
Set the user name.
Definition User.php:2276
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition User.php:2670
Block $mGlobalBlock
Definition User.php:285
static $mCoreRights
Array of Strings Core rights.
Definition User.php:121
getId()
Get the user's ID.
Definition User.php:2224
getRealName()
Get the user's real name.
Definition User.php:2855
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition User.php:5263
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition User.php:2941
clearCookie( $name, $secure=null, $params=[])
Clear a cookie on the user's client.
Definition User.php:3884
getRegistration()
Get the timestamp of account creation.
Definition User.php:4705
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition User.php:1854
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2155
string $mRealName
Definition User.php:210
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition User.php:1200
getMutableCacheKeys(WANObjectCache $cache)
Definition User.php:475
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition User.php:2990
isNewbie()
Determine whether the user is a newbie.
Definition User.php:4332
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition User.php:3677
array $mGroupMemberships
Associative array of (group name => UserGroupMembership object)
Definition User.php:236
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition User.php:805
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition User.php:2423
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition User.php:1268
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition User.php:4560
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition User.php:3587
static randomPassword()
Return a random password.
Definition User.php:1149
static purge( $wikiId, $userId)
Definition User.php:455
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition User.php:2953
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition User.php:1554
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition User.php:4650
string $mEmail
Definition User.php:213
loadDefaults( $name=false)
Set cached properties to default.
Definition User.php:1162
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition User.php:2882
touch()
Update the "touched" timestamp for the user.
Definition User.php:2537
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition User.php:4375
isPingLimitable()
Is this user subject to rate limiting?
Definition User.php:1900
clearSharedCache( $mode='changed')
Clear user data from memcached.
Definition User.php:2496
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition User.php:1092
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition User.php:637
getToken( $forceCreation=true)
Get the user's current token.
Definition User.php:2697
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:573
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition User.php:2620
confirmEmail()
Mark the e-mail address confirmed.
Definition User.php:4591
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition User.php:4768
setId( $v)
Set the user and reload all fields according to a given ID.
Definition User.php:2240
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition User.php:4741
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition User.php:267
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition User.php:1579
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition User.php:935
bool $mLocked
Definition User.php:287
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition User.php:3325
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition User.php:5500
isHidden()
Check if user account is hidden.
Definition User.php:2205
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition User.php:592
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition User.php:2800
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition User.php:2475
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition User.php:1808
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition User.php:1414
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition User.php:900
static getDefaultOption( $opt)
Get a given default option value.
Definition User.php:1619
const IGNORE_USER_RIGHTS
Definition User.php:88
getDatePreference()
Get the user's preferred date format.
Definition User.php:3204
string $mHash
Definition User.php:273
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition User.php:1052
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition User.php:4931
array $mRights
Definition User.php:275
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition User.php:4071
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
Definition User.php:3312
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition User.php:4456
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition User.php:3629
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition User.php:1318
setPassword( $str)
Set the password and reset the random token.
Definition User.php:2608
string $mBlockedby
Definition User.php:271
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition User.php:4551
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition User.php:4304
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition User.php:616
incEditCountImmediate()
Increment the user's edit-count field.
Definition User.php:5124
setExtendedLoginCookie( $name, $value, $secure)
Set an extended login cookie on the user's client.
Definition User.php:3904
static getAllRights()
Get a list of all available permissions.
Definition User.php:4897
getNewtalk()
Check if the user has new messages.
Definition User.php:2293
getGroups()
Get the list of explicit group memberships this user has.
Definition User.php:3299
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition User.php:5240
validateCache( $timestamp)
Validate the cache for this account.
Definition User.php:2552
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition User.php:3578
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition User.php:1424
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition User.php:745
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition User.php:4608
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition User.php:4622
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:678
setOption( $oname, $val)
Set the given option for a user.
Definition User.php:2969
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition User.php:5343
static getRightDescription( $right)
Get the description of a given right.
Definition User.php:5201
getEditCount()
Get the user's edit count.
Definition User.php:3401
const CHECK_USER_RIGHTS
Definition User.php:83
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition User.php:1448
isValidPassword( $password)
Is the input a valid password for this user?
Definition User.php:1005
getBlock( $bFromSlave=true)
Get the block affecting the user, or null if the user is not blocked.
Definition User.php:2077
array $mFormerGroups
Definition User.php:283
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition User.php:4273
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition User.php:4254
getFormerGroups()
Returns the groups the user has belonged to.
Definition User.php:3377
setRealName( $str)
Set the user's real name.
Definition User.php:2867
static getPasswordFactory()
Lazily instantiate and return a factory object for making passwords.
Definition User.php:5429
int $mId
Cache variables.
Definition User.php:206
getTitleKey()
Get the user's name escaped by underscores.
Definition User.php:2285
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:825
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name.
Definition User.php:4971
getTouched()
Get the user touched timestamp.
Definition User.php:2564
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition User.php:4507
Block $mBlock
Definition User.php:297
isLocked()
Check if user account is locked.
Definition User.php:2188
array $mOptions
Definition User.php:291
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition User.php:1516
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition User.php:3660
setPasswordInternal( $str)
Actually set the password and such.
Definition User.php:2632
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition User.php:4690
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition User.php:2383
__toString()
Definition User.php:327
isBlockedFrom( $title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition User.php:2089
getUserPage()
Get this user's personal page title.
Definition User.php:4313
isIPRange()
Is the user an IP range?
Definition User.php:836
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition User.php:262
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition User.php:4533
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition User.php:4633
getBlockId()
If user is blocked, return the ID for the block.
Definition User.php:2128
isBot()
Definition User.php:3518
string $mRegistration
Definition User.php:227
__construct()
Lightweight constructor for an anonymous user.
Definition User.php:320
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition User.php:2443
getStubThreshold()
Get the user preferred stub threshold.
Definition User.php:3243
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition User.php:1925
string $mDatePreference
Definition User.php:269
static isValidUserName( $name)
Is the input a valid username?
Definition User.php:851
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition User.php:4468
getTalkPage()
Get this user's talk page title.
Definition User.php:4322
isLoggedIn()
Get whether the user is logged in.
Definition User.php:3502
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition User.php:765
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition User.php:487
getCacheKey(WANObjectCache $cache)
Definition User.php:466
initEditCount( $add=0)
Initialize user_editcount from data out of the revision table.
Definition User.php:5171
saveSettings()
Save this user's settings into the database.
Definition User.php:4009
static $idCacheByName
Definition User.php:308
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition User.php:2356
getBlockFromCookieValue( $blockCookieVal)
Try to load a Block from an ID given in a cookie value.
Definition User.php:1748
static getGrantName( $grant)
Get the name of a given grant.
Definition User.php:5214
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition User.php:3535
getEmail()
Get the user's e-mail address.
Definition User.php:2763
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition User.php:5523
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition User.php:2755
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition User.php:3348
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition User.php:362
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition User.php:2110
getRights()
Get the permissions this user has.
Definition User.php:3258
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition User.php:4442
string $mEmailAuthenticated
Definition User.php:221
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition User.php:78
const TOKEN_LENGTH
@const int Number of characters in user_token field.
Definition User.php:55
equals(User $user)
Checks if two user objects point to the same user.
Definition User.php:5567
isAllowedAll()
Definition User.php:3550
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition User.php:2735
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition User.php:2398
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition User.php:4106
static getGroupMember( $group, $username='#')
Get the localized descriptive name for a member of a group, if it exists.
Definition User.php:4874
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition User.php:5252
doLogout()
Clear the user's session, and reset the instance cache.
Definition User.php:3973
setCookie( $name, $value, $exp=0, $secure=null, $params=[], $request=null)
Set a cookie on the user's client.
Definition User.php:3863
setItemLoaded( $item)
Set that an item has been loaded.
Definition User.php:1210
incEditCount()
Deferred version of incEditCountImmediate()
Definition User.php:5110
$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:2119
static getGroupName( $group)
Get the localized descriptive name for a group, if it exists.
Definition User.php:4861
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition User.php:3052
int $mEditCount
Definition User.php:229
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition User.php:3018
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition User.php:4946
bool $mHideName
Definition User.php:289
static getImplicitGroups()
Get a list of implicit groups.
Definition User.php:4914
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition User.php:5068
string $mName
Definition User.php:208
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition User.php:2331
isAnon()
Get whether the user is anonymous.
Definition User.php:3510
setEmail( $str)
Set the user's e-mail address.
Definition User.php:2783
string $mBlockreason
Definition User.php:277
static getEditTokenTimestamp( $val)
Get the embedded timestamp from a token.
Definition User.php:4425
WebRequest $mRequest
Definition User.php:294
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition User.php:4342
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition User.php:5547
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition User.php:1498
const EDIT_TOKEN_SUFFIX
Global constant made accessible as class constants so that autoloader magic can be used.
Definition User.php:67
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition User.php:3745
array $mImplicitGroups
Definition User.php:281
removeGroup( $group)
Remove the user from the given group.
Definition User.php:3472
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:45
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
if(! $regexes) $dbr
Definition cleanup.php:94
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
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an article
Definition design.txt:25
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition design.txt:57
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition design.txt:18
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition design.txt:56
this hook is for auditing only $req
Definition hooks.txt:988
the array() calling protocol came about after MediaWiki 1.4rc1.
namespace being checked & $result
Definition hooks.txt:2293
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:2746
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:2775
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1778
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:1971
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:863
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:2780
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;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
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2805
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:1976
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:1975
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:783
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action or null $user:User who performed the tagging when the tagging is subsequent to the action or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy: boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). '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:1049
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
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:1760
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
const NS_MAIN
Definition Defines.php:65
const EDIT_TOKEN_SUFFIX
String Some punctuation to prevent editing from broken text-mangling proxies.
Definition User.php:39
const NS_USER_TALK
Definition Defines.php:68
Interface for objects which can provide a MediaWiki context on request.
Interface for database access objects.
$cache
Definition mcc.php:33
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