MediaWiki REL1_28
User.php
Go to the documentation of this file.
1<?php
29use Wikimedia\ScopedCallback;
30
36define( 'EDIT_TOKEN_SUFFIX', Token::SUFFIX );
37
48class User implements IDBAccessObject {
52 const TOKEN_LENGTH = 32;
53
57 const INVALID_TOKEN = '*** INVALID ***';
58
65
69 const VERSION = 10;
70
76
80 const CHECK_USER_RIGHTS = true;
81
85 const IGNORE_USER_RIGHTS = false;
86
93 protected static $mCacheVars = [
94 // user table
95 'mId',
96 'mName',
97 'mRealName',
98 'mEmail',
99 'mTouched',
100 'mToken',
101 'mEmailAuthenticated',
102 'mEmailToken',
103 'mEmailTokenExpires',
104 'mRegistration',
105 'mEditCount',
106 // user_groups table
107 'mGroups',
108 // user_properties table
109 'mOptionOverrides',
110 ];
111
118 protected static $mCoreRights = [
119 'apihighlimits',
120 'applychangetags',
121 'autoconfirmed',
122 'autocreateaccount',
123 'autopatrol',
124 'bigdelete',
125 'block',
126 'blockemail',
127 'bot',
128 'browsearchive',
129 'changetags',
130 'createaccount',
131 'createpage',
132 'createtalk',
133 'delete',
134 'deletechangetags',
135 'deletedhistory',
136 'deletedtext',
137 'deletelogentry',
138 'deleterevision',
139 'edit',
140 'editcontentmodel',
141 'editinterface',
142 'editprotected',
143 'editmyoptions',
144 'editmyprivateinfo',
145 'editmyusercss',
146 'editmyuserjs',
147 'editmywatchlist',
148 'editsemiprotected',
149 'editusercssjs', # deprecated
150 'editusercss',
151 'edituserjs',
152 'hideuser',
153 'import',
154 'importupload',
155 'ipblock-exempt',
156 'managechangetags',
157 'markbotedits',
158 'mergehistory',
159 'minoredit',
160 'move',
161 'movefile',
162 'move-categorypages',
163 'move-rootuserpages',
164 'move-subpages',
165 'nominornewtalk',
166 'noratelimit',
167 'override-export-depth',
168 'pagelang',
169 'passwordreset',
170 'patrol',
171 'patrolmarks',
172 'protect',
173 'purge',
174 'read',
175 'reupload',
176 'reupload-own',
177 'reupload-shared',
178 'rollback',
179 'sendemail',
180 'siteadmin',
181 'suppressionlog',
182 'suppressredirect',
183 'suppressrevision',
184 'unblockself',
185 'undelete',
186 'unwatchedpages',
187 'upload',
188 'upload_by_url',
189 'userrights',
190 'userrights-interwiki',
191 'viewmyprivateinfo',
192 'viewmywatchlist',
193 'viewsuppressed',
194 'writeapi',
195 ];
196
200 protected static $mAllRights = false;
201
203 // @{
205 public $mId;
207 public $mName;
210
212 public $mEmail;
214 public $mTouched;
216 protected $mQuickTouched;
218 protected $mToken;
222 protected $mEmailToken;
226 protected $mRegistration;
228 protected $mEditCount;
230 public $mGroups;
233 // @}
234
238 // @{
240
244 protected $mLoadedItems = [];
245 // @}
246
256 public $mFrom;
257
261 protected $mNewtalk;
267 protected $mHash;
269 public $mRights;
271 protected $mBlockreason;
277 protected $mFormerGroups;
279 protected $mGlobalBlock;
281 protected $mLocked;
285 public $mOptions;
286
290 private $mRequest;
291
293 public $mBlock;
294
297
300
302 protected $queryFlagsUsed = self::READ_NORMAL;
303
304 public static $idCacheByName = [];
305
316 public function __construct() {
317 $this->clearInstanceCache( 'defaults' );
318 }
319
323 public function __toString() {
324 return (string)$this->getName();
325 }
326
341 public function isSafeToLoad() {
343
344 // The user is safe to load if:
345 // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
346 // * mLoadedItems === true (already loaded)
347 // * mFrom !== 'session' (sessions not involved at all)
348
349 return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
350 $this->mLoadedItems === true || $this->mFrom !== 'session';
351 }
352
358 public function load( $flags = self::READ_NORMAL ) {
360
361 if ( $this->mLoadedItems === true ) {
362 return;
363 }
364
365 // Set it now to avoid infinite recursion in accessors
366 $oldLoadedItems = $this->mLoadedItems;
367 $this->mLoadedItems = true;
368 $this->queryFlagsUsed = $flags;
369
370 // If this is called too early, things are likely to break.
371 if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
372 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
373 ->warning( 'User::loadFromSession called before the end of Setup.php', [
374 'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
375 ] );
376 $this->loadDefaults();
377 $this->mLoadedItems = $oldLoadedItems;
378 return;
379 }
380
381 switch ( $this->mFrom ) {
382 case 'defaults':
383 $this->loadDefaults();
384 break;
385 case 'name':
386 // Make sure this thread sees its own changes
387 if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
388 $flags |= self::READ_LATEST;
389 $this->queryFlagsUsed = $flags;
390 }
391
392 $this->mId = self::idFromName( $this->mName, $flags );
393 if ( !$this->mId ) {
394 // Nonexistent user placeholder object
395 $this->loadDefaults( $this->mName );
396 } else {
397 $this->loadFromId( $flags );
398 }
399 break;
400 case 'id':
401 $this->loadFromId( $flags );
402 break;
403 case 'session':
404 if ( !$this->loadFromSession() ) {
405 // Loading from session failed. Load defaults.
406 $this->loadDefaults();
407 }
408 Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] );
409 break;
410 default:
411 throw new UnexpectedValueException(
412 "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
413 }
414 }
415
421 public function loadFromId( $flags = self::READ_NORMAL ) {
422 if ( $this->mId == 0 ) {
423 // Anonymous users are not in the database (don't need cache)
424 $this->loadDefaults();
425 return false;
426 }
427
428 // Try cache (unless this needs data from the master DB).
429 // NOTE: if this thread called saveSettings(), the cache was cleared.
430 $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
431 if ( $latest ) {
432 if ( !$this->loadFromDatabase( $flags ) ) {
433 // Can't load from ID
434 return false;
435 }
436 } else {
437 $this->loadFromCache();
438 }
439
440 $this->mLoadedItems = true;
441 $this->queryFlagsUsed = $flags;
442
443 return true;
444 }
445
451 public static function purge( $wikiId, $userId ) {
452 $cache = ObjectCache::getMainWANInstance();
453 $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId );
454 $cache->delete( $key );
455 }
456
462 protected function getCacheKey( WANObjectCache $cache ) {
463 return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId );
464 }
465
472 protected function loadFromCache() {
473 $cache = ObjectCache::getMainWANInstance();
474 $data = $cache->getWithSetCallback(
475 $this->getCacheKey( $cache ),
476 $cache::TTL_HOUR,
477 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
478 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
479 wfDebug( "User: cache miss for user {$this->mId}\n" );
480
481 $this->loadFromDatabase( self::READ_NORMAL );
482 $this->loadGroups();
483 $this->loadOptions();
484
485 $data = [];
486 foreach ( self::$mCacheVars as $name ) {
487 $data[$name] = $this->$name;
488 }
489
490 $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
491
492 return $data;
493
494 },
495 [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
496 );
497
498 // Restore from cache
499 foreach ( self::$mCacheVars as $name ) {
500 $this->$name = $data[$name];
501 }
502
503 return true;
504 }
505
507 // @{
508
525 public static function newFromName( $name, $validate = 'valid' ) {
526 if ( $validate === true ) {
527 $validate = 'valid';
528 }
529 $name = self::getCanonicalName( $name, $validate );
530 if ( $name === false ) {
531 return false;
532 } else {
533 // Create unloaded user object
534 $u = new User;
535 $u->mName = $name;
536 $u->mFrom = 'name';
537 $u->setItemLoaded( 'name' );
538 return $u;
539 }
540 }
541
548 public static function newFromId( $id ) {
549 $u = new User;
550 $u->mId = $id;
551 $u->mFrom = 'id';
552 $u->setItemLoaded( 'id' );
553 return $u;
554 }
555
567 public static function newFromConfirmationCode( $code, $flags = 0 ) {
568 $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
569 ? wfGetDB( DB_MASTER )
570 : wfGetDB( DB_REPLICA );
571
572 $id = $db->selectField(
573 'user',
574 'user_id',
575 [
576 'user_email_token' => md5( $code ),
577 'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
578 ]
579 );
580
581 return $id ? User::newFromId( $id ) : null;
582 }
583
591 public static function newFromSession( WebRequest $request = null ) {
592 $user = new User;
593 $user->mFrom = 'session';
594 $user->mRequest = $request;
595 return $user;
596 }
597
612 public static function newFromRow( $row, $data = null ) {
613 $user = new User;
614 $user->loadFromRow( $row, $data );
615 return $user;
616 }
617
653 public static function newSystemUser( $name, $options = [] ) {
654 $options += [
655 'validate' => 'valid',
656 'create' => true,
657 'steal' => false,
658 ];
659
660 $name = self::getCanonicalName( $name, $options['validate'] );
661 if ( $name === false ) {
662 return null;
663 }
664
665 $fields = self::selectFields();
666
667 $dbw = wfGetDB( DB_MASTER );
668 $row = $dbw->selectRow(
669 'user',
670 $fields,
671 [ 'user_name' => $name ],
672 __METHOD__
673 );
674 if ( !$row ) {
675 // No user. Create it?
676 return $options['create'] ? self::createNew( $name ) : null;
677 }
678 $user = self::newFromRow( $row );
679
680 // A user is considered to exist as a non-system user if it can
681 // authenticate, or has an email set, or has a non-invalid token.
682 if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN ||
683 AuthManager::singleton()->userCanAuthenticate( $name )
684 ) {
685 // User exists. Steal it?
686 if ( !$options['steal'] ) {
687 return null;
688 }
689
690 AuthManager::singleton()->revokeAccessForUser( $name );
691
692 $user->invalidateEmail();
693 $user->mToken = self::INVALID_TOKEN;
694 $user->saveSettings();
695 SessionManager::singleton()->preventSessionsForUser( $user->getName() );
696 }
697
698 return $user;
699 }
700
701 // @}
702
708 public static function whoIs( $id ) {
709 return UserCache::singleton()->getProp( $id, 'name' );
710 }
711
718 public static function whoIsReal( $id ) {
719 return UserCache::singleton()->getProp( $id, 'real_name' );
720 }
721
728 public static function idFromName( $name, $flags = self::READ_NORMAL ) {
729 $nt = Title::makeTitleSafe( NS_USER, $name );
730 if ( is_null( $nt ) ) {
731 // Illegal name
732 return null;
733 }
734
735 if ( !( $flags & self::READ_LATEST ) && isset( self::$idCacheByName[$name] ) ) {
736 return self::$idCacheByName[$name];
737 }
738
740 $db = wfGetDB( $index );
741
742 $s = $db->selectRow(
743 'user',
744 [ 'user_id' ],
745 [ 'user_name' => $nt->getText() ],
746 __METHOD__,
748 );
749
750 if ( $s === false ) {
751 $result = null;
752 } else {
753 $result = $s->user_id;
754 }
755
756 self::$idCacheByName[$name] = $result;
757
758 if ( count( self::$idCacheByName ) > 1000 ) {
759 self::$idCacheByName = [];
760 }
761
762 return $result;
763 }
764
768 public static function resetIdByNameCache() {
769 self::$idCacheByName = [];
770 }
771
788 public static function isIP( $name ) {
789 return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name )
790 || IP::isIPv6( $name );
791 }
792
804 public static function isValidUserName( $name ) {
806
807 if ( $name == ''
808 || User::isIP( $name )
809 || strpos( $name, '/' ) !== false
810 || strlen( $name ) > $wgMaxNameChars
811 || $name != $wgContLang->ucfirst( $name )
812 ) {
813 return false;
814 }
815
816 // Ensure that the name can't be misresolved as a different title,
817 // such as with extra namespace keys at the start.
818 $parsed = Title::newFromText( $name );
819 if ( is_null( $parsed )
820 || $parsed->getNamespace()
821 || strcmp( $name, $parsed->getPrefixedText() ) ) {
822 return false;
823 }
824
825 // Check an additional blacklist of troublemaker characters.
826 // Should these be merged into the title char list?
827 $unicodeBlacklist = '/[' .
828 '\x{0080}-\x{009f}' . # iso-8859-1 control chars
829 '\x{00a0}' . # non-breaking space
830 '\x{2000}-\x{200f}' . # various whitespace
831 '\x{2028}-\x{202f}' . # breaks and control chars
832 '\x{3000}' . # ideographic space
833 '\x{e000}-\x{f8ff}' . # private use
834 ']/u';
835 if ( preg_match( $unicodeBlacklist, $name ) ) {
836 return false;
837 }
838
839 return true;
840 }
841
853 public static function isUsableName( $name ) {
855 // Must be a valid username, obviously ;)
856 if ( !self::isValidUserName( $name ) ) {
857 return false;
858 }
859
860 static $reservedUsernames = false;
861 if ( !$reservedUsernames ) {
862 $reservedUsernames = $wgReservedUsernames;
863 Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] );
864 }
865
866 // Certain names may be reserved for batch processes.
867 foreach ( $reservedUsernames as $reserved ) {
868 if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
869 $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text();
870 }
871 if ( $reserved == $name ) {
872 return false;
873 }
874 }
875 return true;
876 }
877
888 public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
889 if ( $groups === [] ) {
891 }
892
893 $groups = array_unique( (array)$groups );
894 $limit = min( 5000, $limit );
895
896 $conds = [ 'ug_group' => $groups ];
897 if ( $after !== null ) {
898 $conds[] = 'ug_user > ' . (int)$after;
899 }
900
902 $ids = $dbr->selectFieldValues(
903 'user_groups',
904 'ug_user',
905 $conds,
906 __METHOD__,
907 [
908 'DISTINCT' => true,
909 'ORDER BY' => 'ug_user',
910 'LIMIT' => $limit,
911 ]
912 ) ?: [];
913 return UserArray::newFromIDs( $ids );
914 }
915
928 public static function isCreatableName( $name ) {
930
931 // Ensure that the username isn't longer than 235 bytes, so that
932 // (at least for the builtin skins) user javascript and css files
933 // will work. (bug 23080)
934 if ( strlen( $name ) > 235 ) {
935 wfDebugLog( 'username', __METHOD__ .
936 ": '$name' invalid due to length" );
937 return false;
938 }
939
940 // Preg yells if you try to give it an empty string
941 if ( $wgInvalidUsernameCharacters !== '' ) {
942 if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
943 wfDebugLog( 'username', __METHOD__ .
944 ": '$name' invalid due to wgInvalidUsernameCharacters" );
945 return false;
946 }
947 }
948
949 return self::isUsableName( $name );
950 }
951
958 public function isValidPassword( $password ) {
959 // simple boolean wrapper for getPasswordValidity
960 return $this->getPasswordValidity( $password ) === true;
961 }
962
969 public function getPasswordValidity( $password ) {
970 $result = $this->checkPasswordValidity( $password );
971 if ( $result->isGood() ) {
972 return true;
973 } else {
974 $messages = [];
975 foreach ( $result->getErrorsByType( 'error' ) as $error ) {
976 $messages[] = $error['message'];
977 }
978 foreach ( $result->getErrorsByType( 'warning' ) as $warning ) {
979 $messages[] = $warning['message'];
980 }
981 if ( count( $messages ) === 1 ) {
982 return $messages[0];
983 }
984 return $messages;
985 }
986 }
987
1006 public function checkPasswordValidity( $password, $purpose = 'login' ) {
1008
1009 $upp = new UserPasswordPolicy(
1010 $wgPasswordPolicy['policies'],
1011 $wgPasswordPolicy['checks']
1012 );
1013
1014 $status = Status::newGood();
1015 $result = false; // init $result to false for the internal checks
1016
1017 if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
1018 $status->error( $result );
1019 return $status;
1020 }
1021
1022 if ( $result === false ) {
1023 $status->merge( $upp->checkUserPassword( $this, $password, $purpose ) );
1024 return $status;
1025 } elseif ( $result === true ) {
1026 return $status;
1027 } else {
1028 $status->error( $result );
1029 return $status; // the isValidPassword hook set a string $result and returned true
1030 }
1031 }
1032
1046 public static function getCanonicalName( $name, $validate = 'valid' ) {
1047 // Force usernames to capital
1049 $name = $wgContLang->ucfirst( $name );
1050
1051 # Reject names containing '#'; these will be cleaned up
1052 # with title normalisation, but then it's too late to
1053 # check elsewhere
1054 if ( strpos( $name, '#' ) !== false ) {
1055 return false;
1056 }
1057
1058 // Clean up name according to title rules,
1059 // but only when validation is requested (bug 12654)
1060 $t = ( $validate !== false ) ?
1061 Title::newFromText( $name, NS_USER ) : Title::makeTitle( NS_USER, $name );
1062 // Check for invalid titles
1063 if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
1064 return false;
1065 }
1066
1067 // Reject various classes of invalid names
1068 $name = AuthManager::callLegacyAuthPlugin(
1069 'getCanonicalName', [ $t->getText() ], $t->getText()
1070 );
1071
1072 switch ( $validate ) {
1073 case false:
1074 break;
1075 case 'valid':
1076 if ( !User::isValidUserName( $name ) ) {
1077 $name = false;
1078 }
1079 break;
1080 case 'usable':
1081 if ( !User::isUsableName( $name ) ) {
1082 $name = false;
1083 }
1084 break;
1085 case 'creatable':
1086 if ( !User::isCreatableName( $name ) ) {
1087 $name = false;
1088 }
1089 break;
1090 default:
1091 throw new InvalidArgumentException(
1092 'Invalid parameter value for $validate in ' . __METHOD__ );
1093 }
1094 return $name;
1095 }
1096
1105 public static function edits( $uid ) {
1106 wfDeprecated( __METHOD__, '1.21' );
1107 $user = self::newFromId( $uid );
1108 return $user->getEditCount();
1109 }
1110
1117 public static function randomPassword() {
1119 return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
1120 }
1121
1130 public function loadDefaults( $name = false ) {
1131 $this->mId = 0;
1132 $this->mName = $name;
1133 $this->mRealName = '';
1134 $this->mEmail = '';
1135 $this->mOptionOverrides = null;
1136 $this->mOptionsLoaded = false;
1137
1138 $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
1139 ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
1140 if ( $loggedOut !== 0 ) {
1141 $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
1142 } else {
1143 $this->mTouched = '1'; # Allow any pages to be cached
1144 }
1145
1146 $this->mToken = null; // Don't run cryptographic functions till we need a token
1147 $this->mEmailAuthenticated = null;
1148 $this->mEmailToken = '';
1149 $this->mEmailTokenExpires = null;
1150 $this->mRegistration = wfTimestamp( TS_MW );
1151 $this->mGroups = [];
1152
1153 Hooks::run( 'UserLoadDefaults', [ $this, $name ] );
1154 }
1155
1168 public function isItemLoaded( $item, $all = 'all' ) {
1169 return ( $this->mLoadedItems === true && $all === 'all' ) ||
1170 ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
1171 }
1172
1178 protected function setItemLoaded( $item ) {
1179 if ( is_array( $this->mLoadedItems ) ) {
1180 $this->mLoadedItems[$item] = true;
1181 }
1182 }
1183
1189 private function loadFromSession() {
1190 // Deprecated hook
1191 $result = null;
1192 Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' );
1193 if ( $result !== null ) {
1194 return $result;
1195 }
1196
1197 // MediaWiki\Session\Session already did the necessary authentication of the user
1198 // returned here, so just use it if applicable.
1199 $session = $this->getRequest()->getSession();
1200 $user = $session->getUser();
1201 if ( $user->isLoggedIn() ) {
1202 $this->loadFromUserObject( $user );
1203 // Other code expects these to be set in the session, so set them.
1204 $session->set( 'wsUserID', $this->getId() );
1205 $session->set( 'wsUserName', $this->getName() );
1206 $session->set( 'wsToken', $this->getToken() );
1207 return true;
1208 }
1209
1210 return false;
1211 }
1212
1220 public function loadFromDatabase( $flags = self::READ_LATEST ) {
1221 // Paranoia
1222 $this->mId = intval( $this->mId );
1223
1224 if ( !$this->mId ) {
1225 // Anonymous users are not in the database
1226 $this->loadDefaults();
1227 return false;
1228 }
1229
1231 $db = wfGetDB( $index );
1232
1233 $s = $db->selectRow(
1234 'user',
1235 self::selectFields(),
1236 [ 'user_id' => $this->mId ],
1237 __METHOD__,
1238 $options
1239 );
1240
1241 $this->queryFlagsUsed = $flags;
1242 Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] );
1243
1244 if ( $s !== false ) {
1245 // Initialise user table data
1246 $this->loadFromRow( $s );
1247 $this->mGroups = null; // deferred
1248 $this->getEditCount(); // revalidation for nulls
1249 return true;
1250 } else {
1251 // Invalid user_id
1252 $this->mId = 0;
1253 $this->loadDefaults();
1254 return false;
1255 }
1256 }
1257
1267 protected function loadFromRow( $row, $data = null ) {
1268 $all = true;
1269
1270 $this->mGroups = null; // deferred
1271
1272 if ( isset( $row->user_name ) ) {
1273 $this->mName = $row->user_name;
1274 $this->mFrom = 'name';
1275 $this->setItemLoaded( 'name' );
1276 } else {
1277 $all = false;
1278 }
1279
1280 if ( isset( $row->user_real_name ) ) {
1281 $this->mRealName = $row->user_real_name;
1282 $this->setItemLoaded( 'realname' );
1283 } else {
1284 $all = false;
1285 }
1286
1287 if ( isset( $row->user_id ) ) {
1288 $this->mId = intval( $row->user_id );
1289 $this->mFrom = 'id';
1290 $this->setItemLoaded( 'id' );
1291 } else {
1292 $all = false;
1293 }
1294
1295 if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
1296 self::$idCacheByName[$row->user_name] = $row->user_id;
1297 }
1298
1299 if ( isset( $row->user_editcount ) ) {
1300 $this->mEditCount = $row->user_editcount;
1301 } else {
1302 $all = false;
1303 }
1304
1305 if ( isset( $row->user_touched ) ) {
1306 $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
1307 } else {
1308 $all = false;
1309 }
1310
1311 if ( isset( $row->user_token ) ) {
1312 // The definition for the column is binary(32), so trim the NULs
1313 // that appends. The previous definition was char(32), so trim
1314 // spaces too.
1315 $this->mToken = rtrim( $row->user_token, " \0" );
1316 if ( $this->mToken === '' ) {
1317 $this->mToken = null;
1318 }
1319 } else {
1320 $all = false;
1321 }
1322
1323 if ( isset( $row->user_email ) ) {
1324 $this->mEmail = $row->user_email;
1325 $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
1326 $this->mEmailToken = $row->user_email_token;
1327 $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
1328 $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
1329 } else {
1330 $all = false;
1331 }
1332
1333 if ( $all ) {
1334 $this->mLoadedItems = true;
1335 }
1336
1337 if ( is_array( $data ) ) {
1338 if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
1339 $this->mGroups = $data['user_groups'];
1340 }
1341 if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
1342 $this->loadOptions( $data['user_properties'] );
1343 }
1344 }
1345 }
1346
1352 protected function loadFromUserObject( $user ) {
1353 $user->load();
1354 foreach ( self::$mCacheVars as $var ) {
1355 $this->$var = $user->$var;
1356 }
1357 }
1358
1362 private function loadGroups() {
1363 if ( is_null( $this->mGroups ) ) {
1364 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
1365 ? wfGetDB( DB_MASTER )
1366 : wfGetDB( DB_REPLICA );
1367 $res = $db->select( 'user_groups',
1368 [ 'ug_group' ],
1369 [ 'ug_user' => $this->mId ],
1370 __METHOD__ );
1371 $this->mGroups = [];
1372 foreach ( $res as $row ) {
1373 $this->mGroups[] = $row->ug_group;
1374 }
1375 }
1376 }
1377
1392 public function addAutopromoteOnceGroups( $event ) {
1394
1395 if ( wfReadOnly() || !$this->getId() ) {
1396 return [];
1397 }
1398
1399 $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
1400 if ( !count( $toPromote ) ) {
1401 return [];
1402 }
1403
1404 if ( !$this->checkAndSetTouched() ) {
1405 return []; // raced out (bug T48834)
1406 }
1407
1408 $oldGroups = $this->getGroups(); // previous groups
1409 foreach ( $toPromote as $group ) {
1410 $this->addGroup( $group );
1411 }
1412 // update groups in external authentication database
1413 Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false ] );
1414 AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
1415
1416 $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
1417
1418 $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
1419 $logEntry->setPerformer( $this );
1420 $logEntry->setTarget( $this->getUserPage() );
1421 $logEntry->setParameters( [
1422 '4::oldgroups' => $oldGroups,
1423 '5::newgroups' => $newGroups,
1424 ] );
1425 $logid = $logEntry->insert();
1427 $logEntry->publish( $logid );
1428 }
1429
1430 return $toPromote;
1431 }
1432
1442 protected function makeUpdateConditions( Database $db, array $conditions ) {
1443 if ( $this->mTouched ) {
1444 // CAS check: only update if the row wasn't changed sicne it was loaded.
1445 $conditions['user_touched'] = $db->timestamp( $this->mTouched );
1446 }
1447
1448 return $conditions;
1449 }
1450
1460 protected function checkAndSetTouched() {
1461 $this->load();
1462
1463 if ( !$this->mId ) {
1464 return false; // anon
1465 }
1466
1467 // Get a new user_touched that is higher than the old one
1468 $newTouched = $this->newTouchedTimestamp();
1469
1470 $dbw = wfGetDB( DB_MASTER );
1471 $dbw->update( 'user',
1472 [ 'user_touched' => $dbw->timestamp( $newTouched ) ],
1473 $this->makeUpdateConditions( $dbw, [
1474 'user_id' => $this->mId,
1475 ] ),
1476 __METHOD__
1477 );
1478 $success = ( $dbw->affectedRows() > 0 );
1479
1480 if ( $success ) {
1481 $this->mTouched = $newTouched;
1482 $this->clearSharedCache();
1483 } else {
1484 // Clears on failure too since that is desired if the cache is stale
1485 $this->clearSharedCache( 'refresh' );
1486 }
1487
1488 return $success;
1489 }
1490
1498 public function clearInstanceCache( $reloadFrom = false ) {
1499 $this->mNewtalk = -1;
1500 $this->mDatePreference = null;
1501 $this->mBlockedby = -1; # Unset
1502 $this->mHash = false;
1503 $this->mRights = null;
1504 $this->mEffectiveGroups = null;
1505 $this->mImplicitGroups = null;
1506 $this->mGroups = null;
1507 $this->mOptions = null;
1508 $this->mOptionsLoaded = false;
1509 $this->mEditCount = null;
1510
1511 if ( $reloadFrom ) {
1512 $this->mLoadedItems = [];
1513 $this->mFrom = $reloadFrom;
1514 }
1515 }
1516
1523 public static function getDefaultOptions() {
1525
1526 static $defOpt = null;
1527 static $defOptLang = null;
1528
1529 if ( $defOpt !== null && $defOptLang === $wgContLang->getCode() ) {
1530 // $wgContLang does not change (and should not change) mid-request,
1531 // but the unit tests change it anyway, and expect this method to
1532 // return values relevant to the current $wgContLang.
1533 return $defOpt;
1534 }
1535
1536 $defOpt = $wgDefaultUserOptions;
1537 // Default language setting
1538 $defOptLang = $wgContLang->getCode();
1539 $defOpt['language'] = $defOptLang;
1540 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
1541 $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
1542 }
1543
1544 // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
1545 // since extensions may change the set of searchable namespaces depending
1546 // on user groups/permissions.
1547 foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
1548 $defOpt['searchNs' . $nsnum] = (boolean)$val;
1549 }
1550 $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
1551
1552 Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] );
1553
1554 return $defOpt;
1555 }
1556
1563 public static function getDefaultOption( $opt ) {
1564 $defOpts = self::getDefaultOptions();
1565 if ( isset( $defOpts[$opt] ) ) {
1566 return $defOpts[$opt];
1567 } else {
1568 return null;
1569 }
1570 }
1571
1578 private function getBlockedStatus( $bFromSlave = true ) {
1580
1581 if ( -1 != $this->mBlockedby ) {
1582 return;
1583 }
1584
1585 wfDebug( __METHOD__ . ": checking...\n" );
1586
1587 // Initialize data...
1588 // Otherwise something ends up stomping on $this->mBlockedby when
1589 // things get lazy-loaded later, causing false positive block hits
1590 // due to -1 !== 0. Probably session-related... Nothing should be
1591 // overwriting mBlockedby, surely?
1592 $this->load();
1593
1594 # We only need to worry about passing the IP address to the Block generator if the
1595 # user is not immune to autoblocks/hardblocks, and they are the current user so we
1596 # know which IP address they're actually coming from
1597 $ip = null;
1598 if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
1599 // $wgUser->getName() only works after the end of Setup.php. Until
1600 // then, assume it's a logged-out user.
1601 $globalUserName = $wgUser->isSafeToLoad()
1602 ? $wgUser->getName()
1603 : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
1604 if ( $this->getName() === $globalUserName ) {
1605 $ip = $this->getRequest()->getIP();
1606 }
1607 }
1608
1609 // User/IP blocking
1610 $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
1611
1612 // Proxy blocking
1613 if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
1614 // Local list
1615 if ( self::isLocallyBlockedProxy( $ip ) ) {
1616 $block = new Block;
1617 $block->setBlocker( wfMessage( 'proxyblocker' )->text() );
1618 $block->mReason = wfMessage( 'proxyblockreason' )->text();
1619 $block->setTarget( $ip );
1620 } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
1621 $block = new Block;
1622 $block->setBlocker( wfMessage( 'sorbs' )->text() );
1623 $block->mReason = wfMessage( 'sorbsreason' )->text();
1624 $block->setTarget( $ip );
1625 }
1626 }
1627
1628 // (bug 23343) Apply IP blocks to the contents of XFF headers, if enabled
1629 if ( !$block instanceof Block
1631 && $ip !== null
1632 && !in_array( $ip, $wgProxyWhitelist )
1633 ) {
1634 $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
1635 $xff = array_map( 'trim', explode( ',', $xff ) );
1636 $xff = array_diff( $xff, [ $ip ] );
1637 $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
1638 $block = Block::chooseBlock( $xffblocks, $xff );
1639 if ( $block instanceof Block ) {
1640 # Mangle the reason to alert the user that the block
1641 # originated from matching the X-Forwarded-For header.
1642 $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
1643 }
1644 }
1645
1646 if ( $block instanceof Block ) {
1647 wfDebug( __METHOD__ . ": Found block.\n" );
1648 $this->mBlock = $block;
1649 $this->mBlockedby = $block->getByName();
1650 $this->mBlockreason = $block->mReason;
1651 $this->mHideName = $block->mHideName;
1652 $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
1653 } else {
1654 $this->mBlockedby = '';
1655 $this->mHideName = 0;
1656 $this->mAllowUsertalk = false;
1657 }
1658
1659 // Extensions
1660 Hooks::run( 'GetBlockedStatus', [ &$this ] );
1661
1662 }
1663
1671 public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
1673
1674 if ( !$wgEnableDnsBlacklist ) {
1675 return false;
1676 }
1677
1678 if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
1679 return false;
1680 }
1681
1682 return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
1683 }
1684
1692 public function inDnsBlacklist( $ip, $bases ) {
1693
1694 $found = false;
1695 // @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
1696 if ( IP::isIPv4( $ip ) ) {
1697 // Reverse IP, bug 21255
1698 $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
1699
1700 foreach ( (array)$bases as $base ) {
1701 // Make hostname
1702 // If we have an access key, use that too (ProjectHoneypot, etc.)
1703 $basename = $base;
1704 if ( is_array( $base ) ) {
1705 if ( count( $base ) >= 2 ) {
1706 // Access key is 1, base URL is 0
1707 $host = "{$base[1]}.$ipReversed.{$base[0]}";
1708 } else {
1709 $host = "$ipReversed.{$base[0]}";
1710 }
1711 $basename = $base[0];
1712 } else {
1713 $host = "$ipReversed.$base";
1714 }
1715
1716 // Send query
1717 $ipList = gethostbynamel( $host );
1718
1719 if ( $ipList ) {
1720 wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" );
1721 $found = true;
1722 break;
1723 } else {
1724 wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." );
1725 }
1726 }
1727 }
1728
1729 return $found;
1730 }
1731
1739 public static function isLocallyBlockedProxy( $ip ) {
1741
1742 if ( !$wgProxyList ) {
1743 return false;
1744 }
1745
1746 if ( !is_array( $wgProxyList ) ) {
1747 // Load from the specified file
1748 $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
1749 }
1750
1751 if ( !is_array( $wgProxyList ) ) {
1752 $ret = false;
1753 } elseif ( array_search( $ip, $wgProxyList ) !== false ) {
1754 $ret = true;
1755 } elseif ( array_key_exists( $ip, $wgProxyList ) ) {
1756 // Old-style flipped proxy list
1757 $ret = true;
1758 } else {
1759 $ret = false;
1760 }
1761 return $ret;
1762 }
1763
1769 public function isPingLimitable() {
1771 if ( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
1772 // No other good way currently to disable rate limits
1773 // for specific IPs. :P
1774 // But this is a crappy hack and should die.
1775 return false;
1776 }
1777 return !$this->isAllowed( 'noratelimit' );
1778 }
1779
1794 public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
1795 // Call the 'PingLimiter' hook
1796 $result = false;
1797 if ( !Hooks::run( 'PingLimiter', [ &$this, $action, &$result, $incrBy ] ) ) {
1798 return $result;
1799 }
1800
1802 if ( !isset( $wgRateLimits[$action] ) ) {
1803 return false;
1804 }
1805
1806 $limits = array_merge(
1807 [ '&can-bypass' => true ],
1808 $wgRateLimits[$action]
1809 );
1810
1811 // Some groups shouldn't trigger the ping limiter, ever
1812 if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
1813 return false;
1814 }
1815
1816 $keys = [];
1817 $id = $this->getId();
1818 $userLimit = false;
1819 $isNewbie = $this->isNewbie();
1820
1821 if ( $id == 0 ) {
1822 // limits for anons
1823 if ( isset( $limits['anon'] ) ) {
1824 $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
1825 }
1826 } else {
1827 // limits for logged-in users
1828 if ( isset( $limits['user'] ) ) {
1829 $userLimit = $limits['user'];
1830 }
1831 // limits for newbie logged-in users
1832 if ( $isNewbie && isset( $limits['newbie'] ) ) {
1833 $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
1834 }
1835 }
1836
1837 // limits for anons and for newbie logged-in users
1838 if ( $isNewbie ) {
1839 // ip-based limits
1840 if ( isset( $limits['ip'] ) ) {
1841 $ip = $this->getRequest()->getIP();
1842 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
1843 }
1844 // subnet-based limits
1845 if ( isset( $limits['subnet'] ) ) {
1846 $ip = $this->getRequest()->getIP();
1847 $subnet = IP::getSubnet( $ip );
1848 if ( $subnet !== false ) {
1849 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
1850 }
1851 }
1852 }
1853
1854 // Check for group-specific permissions
1855 // If more than one group applies, use the group with the highest limit ratio (max/period)
1856 foreach ( $this->getGroups() as $group ) {
1857 if ( isset( $limits[$group] ) ) {
1858 if ( $userLimit === false
1859 || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
1860 ) {
1861 $userLimit = $limits[$group];
1862 }
1863 }
1864 }
1865
1866 // Set the user limit key
1867 if ( $userLimit !== false ) {
1868 list( $max, $period ) = $userLimit;
1869 wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
1870 $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit;
1871 }
1872
1873 // ip-based limits for all ping-limitable users
1874 if ( isset( $limits['ip-all'] ) ) {
1875 $ip = $this->getRequest()->getIP();
1876 // ignore if user limit is more permissive
1877 if ( $isNewbie || $userLimit === false
1878 || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
1879 $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
1880 }
1881 }
1882
1883 // subnet-based limits for all ping-limitable users
1884 if ( isset( $limits['subnet-all'] ) ) {
1885 $ip = $this->getRequest()->getIP();
1886 $subnet = IP::getSubnet( $ip );
1887 if ( $subnet !== false ) {
1888 // ignore if user limit is more permissive
1889 if ( $isNewbie || $userLimit === false
1890 || $limits['ip-all'][0] / $limits['ip-all'][1]
1891 > $userLimit[0] / $userLimit[1] ) {
1892 $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
1893 }
1894 }
1895 }
1896
1897 $cache = ObjectCache::getLocalClusterInstance();
1898
1899 $triggered = false;
1900 foreach ( $keys as $key => $limit ) {
1901 list( $max, $period ) = $limit;
1902 $summary = "(limit $max in {$period}s)";
1903 $count = $cache->get( $key );
1904 // Already pinged?
1905 if ( $count ) {
1906 if ( $count >= $max ) {
1907 wfDebugLog( 'ratelimit', "User '{$this->getName()}' " .
1908 "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" );
1909 $triggered = true;
1910 } else {
1911 wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
1912 }
1913 } else {
1914 wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
1915 if ( $incrBy > 0 ) {
1916 $cache->add( $key, 0, intval( $period ) ); // first ping
1917 }
1918 }
1919 if ( $incrBy > 0 ) {
1920 $cache->incr( $key, $incrBy );
1921 }
1922 }
1923
1924 return $triggered;
1925 }
1926
1934 public function isBlocked( $bFromSlave = true ) {
1935 return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
1936 }
1937
1944 public function getBlock( $bFromSlave = true ) {
1945 $this->getBlockedStatus( $bFromSlave );
1946 return $this->mBlock instanceof Block ? $this->mBlock : null;
1947 }
1948
1956 public function isBlockedFrom( $title, $bFromSlave = false ) {
1958
1959 $blocked = $this->isBlocked( $bFromSlave );
1960 $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
1961 // If a user's name is suppressed, they cannot make edits anywhere
1962 if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
1963 && $title->getNamespace() == NS_USER_TALK ) {
1964 $blocked = false;
1965 wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
1966 }
1967
1968 Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
1969
1970 return $blocked;
1971 }
1972
1977 public function blockedBy() {
1978 $this->getBlockedStatus();
1979 return $this->mBlockedby;
1980 }
1981
1986 public function blockedFor() {
1987 $this->getBlockedStatus();
1988 return $this->mBlockreason;
1989 }
1990
1995 public function getBlockId() {
1996 $this->getBlockedStatus();
1997 return ( $this->mBlock ? $this->mBlock->getId() : false );
1998 }
1999
2008 public function isBlockedGlobally( $ip = '' ) {
2009 return $this->getGlobalBlock( $ip ) instanceof Block;
2010 }
2011
2022 public function getGlobalBlock( $ip = '' ) {
2023 if ( $this->mGlobalBlock !== null ) {
2024 return $this->mGlobalBlock ?: null;
2025 }
2026 // User is already an IP?
2027 if ( IP::isIPAddress( $this->getName() ) ) {
2028 $ip = $this->getName();
2029 } elseif ( !$ip ) {
2030 $ip = $this->getRequest()->getIP();
2031 }
2032 $blocked = false;
2033 $block = null;
2034 Hooks::run( 'UserIsBlockedGlobally', [ &$this, $ip, &$blocked, &$block ] );
2035
2036 if ( $blocked && $block === null ) {
2037 // back-compat: UserIsBlockedGlobally didn't have $block param first
2038 $block = new Block;
2039 $block->setTarget( $ip );
2040 }
2041
2042 $this->mGlobalBlock = $blocked ? $block : false;
2043 return $this->mGlobalBlock ?: null;
2044 }
2045
2051 public function isLocked() {
2052 if ( $this->mLocked !== null ) {
2053 return $this->mLocked;
2054 }
2055 $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2056 $this->mLocked = $authUser && $authUser->isLocked();
2057 Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
2058 return $this->mLocked;
2059 }
2060
2066 public function isHidden() {
2067 if ( $this->mHideName !== null ) {
2068 return $this->mHideName;
2069 }
2070 $this->getBlockedStatus();
2071 if ( !$this->mHideName ) {
2072 $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$this ], null );
2073 $this->mHideName = $authUser && $authUser->isHidden();
2074 Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
2075 }
2076 return $this->mHideName;
2077 }
2078
2083 public function getId() {
2084 if ( $this->mId === null && $this->mName !== null && User::isIP( $this->mName ) ) {
2085 // Special case, we know the user is anonymous
2086 return 0;
2087 } elseif ( !$this->isItemLoaded( 'id' ) ) {
2088 // Don't load if this was initialized from an ID
2089 $this->load();
2090 }
2091
2092 return (int)$this->mId;
2093 }
2094
2099 public function setId( $v ) {
2100 $this->mId = $v;
2101 $this->clearInstanceCache( 'id' );
2102 }
2103
2108 public function getName() {
2109 if ( $this->isItemLoaded( 'name', 'only' ) ) {
2110 // Special case optimisation
2111 return $this->mName;
2112 } else {
2113 $this->load();
2114 if ( $this->mName === false ) {
2115 // Clean up IPs
2116 $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
2117 }
2118 return $this->mName;
2119 }
2120 }
2121
2135 public function setName( $str ) {
2136 $this->load();
2137 $this->mName = $str;
2138 }
2139
2144 public function getTitleKey() {
2145 return str_replace( ' ', '_', $this->getName() );
2146 }
2147
2152 public function getNewtalk() {
2153 $this->load();
2154
2155 // Load the newtalk status if it is unloaded (mNewtalk=-1)
2156 if ( $this->mNewtalk === -1 ) {
2157 $this->mNewtalk = false; # reset talk page status
2158
2159 // Check memcached separately for anons, who have no
2160 // entire User object stored in there.
2161 if ( !$this->mId ) {
2163 if ( $wgDisableAnonTalk ) {
2164 // Anon newtalk disabled by configuration.
2165 $this->mNewtalk = false;
2166 } else {
2167 $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
2168 }
2169 } else {
2170 $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
2171 }
2172 }
2173
2174 return (bool)$this->mNewtalk;
2175 }
2176
2190 public function getNewMessageLinks() {
2191 $talks = [];
2192 if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$this, &$talks ] ) ) {
2193 return $talks;
2194 } elseif ( !$this->getNewtalk() ) {
2195 return [];
2196 }
2197 $utp = $this->getTalkPage();
2198 $dbr = wfGetDB( DB_REPLICA );
2199 // Get the "last viewed rev" timestamp from the oldest message notification
2200 $timestamp = $dbr->selectField( 'user_newtalk',
2201 'MIN(user_last_timestamp)',
2202 $this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
2203 __METHOD__ );
2205 return [ [ 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ] ];
2206 }
2207
2213 public function getNewMessageRevisionId() {
2214 $newMessageRevisionId = null;
2215 $newMessageLinks = $this->getNewMessageLinks();
2216 if ( $newMessageLinks ) {
2217 // Note: getNewMessageLinks() never returns more than a single link
2218 // and it is always for the same wiki, but we double-check here in
2219 // case that changes some time in the future.
2220 if ( count( $newMessageLinks ) === 1
2221 && $newMessageLinks[0]['wiki'] === wfWikiID()
2222 && $newMessageLinks[0]['rev']
2223 ) {
2225 $newMessageRevision = $newMessageLinks[0]['rev'];
2226 $newMessageRevisionId = $newMessageRevision->getId();
2227 }
2228 }
2229 return $newMessageRevisionId;
2230 }
2231
2240 protected function checkNewtalk( $field, $id ) {
2241 $dbr = wfGetDB( DB_REPLICA );
2242
2243 $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
2244
2245 return $ok !== false;
2246 }
2247
2255 protected function updateNewtalk( $field, $id, $curRev = null ) {
2256 // Get timestamp of the talk page revision prior to the current one
2257 $prevRev = $curRev ? $curRev->getPrevious() : false;
2258 $ts = $prevRev ? $prevRev->getTimestamp() : null;
2259 // Mark the user as having new messages since this revision
2260 $dbw = wfGetDB( DB_MASTER );
2261 $dbw->insert( 'user_newtalk',
2262 [ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
2263 __METHOD__,
2264 'IGNORE' );
2265 if ( $dbw->affectedRows() ) {
2266 wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
2267 return true;
2268 } else {
2269 wfDebug( __METHOD__ . " already set ($field, $id)\n" );
2270 return false;
2271 }
2272 }
2273
2280 protected function deleteNewtalk( $field, $id ) {
2281 $dbw = wfGetDB( DB_MASTER );
2282 $dbw->delete( 'user_newtalk',
2283 [ $field => $id ],
2284 __METHOD__ );
2285 if ( $dbw->affectedRows() ) {
2286 wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
2287 return true;
2288 } else {
2289 wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
2290 return false;
2291 }
2292 }
2293
2300 public function setNewtalk( $val, $curRev = null ) {
2301 if ( wfReadOnly() ) {
2302 return;
2303 }
2304
2305 $this->load();
2306 $this->mNewtalk = $val;
2307
2308 if ( $this->isAnon() ) {
2309 $field = 'user_ip';
2310 $id = $this->getName();
2311 } else {
2312 $field = 'user_id';
2313 $id = $this->getId();
2314 }
2315
2316 if ( $val ) {
2317 $changed = $this->updateNewtalk( $field, $id, $curRev );
2318 } else {
2319 $changed = $this->deleteNewtalk( $field, $id );
2320 }
2321
2322 if ( $changed ) {
2323 $this->invalidateCache();
2324 }
2325 }
2326
2332 private function newTouchedTimestamp() {
2334
2336 if ( $this->mTouched && $time <= $this->mTouched ) {
2337 $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
2338 }
2339
2340 return $time;
2341 }
2342
2353 public function clearSharedCache( $mode = 'changed' ) {
2354 if ( !$this->getId() ) {
2355 return;
2356 }
2357
2358 $cache = ObjectCache::getMainWANInstance();
2359 $key = $this->getCacheKey( $cache );
2360 if ( $mode === 'refresh' ) {
2361 $cache->delete( $key, 1 );
2362 } else {
2363 wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
2364 function() use ( $cache, $key ) {
2365 $cache->delete( $key );
2366 },
2367 __METHOD__
2368 );
2369 }
2370 }
2371
2377 public function invalidateCache() {
2378 $this->touch();
2379 $this->clearSharedCache();
2380 }
2381
2394 public function touch() {
2395 $id = $this->getId();
2396 if ( $id ) {
2397 $key = wfMemcKey( 'user-quicktouched', 'id', $id );
2398 ObjectCache::getMainWANInstance()->touchCheckKey( $key );
2399 $this->mQuickTouched = null;
2400 }
2401 }
2402
2408 public function validateCache( $timestamp ) {
2409 return ( $timestamp >= $this->getTouched() );
2410 }
2411
2420 public function getTouched() {
2421 $this->load();
2422
2423 if ( $this->mId ) {
2424 if ( $this->mQuickTouched === null ) {
2425 $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
2426 $cache = ObjectCache::getMainWANInstance();
2427
2428 $this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
2429 }
2430
2431 return max( $this->mTouched, $this->mQuickTouched );
2432 }
2433
2434 return $this->mTouched;
2435 }
2436
2442 public function getDBTouched() {
2443 $this->load();
2444
2445 return $this->mTouched;
2446 }
2447
2453 public function getPassword() {
2454 throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2455 }
2456
2462 public function getTemporaryPassword() {
2463 throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2464 }
2465
2482 public function setPassword( $str ) {
2483 return $this->setPasswordInternal( $str );
2484 }
2485
2494 public function setInternalPassword( $str ) {
2495 $this->setPasswordInternal( $str );
2496 }
2497
2506 private function setPasswordInternal( $str ) {
2507 $manager = AuthManager::singleton();
2508
2509 // If the user doesn't exist yet, fail
2510 if ( !$manager->userExists( $this->getName() ) ) {
2511 throw new LogicException( 'Cannot set a password for a user that is not in the database.' );
2512 }
2513
2515 'username' => $this->getName(),
2516 'password' => $str,
2517 'retype' => $str,
2518 ] );
2519 if ( !$status->isGood() ) {
2520 \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
2521 ->info( __METHOD__ . ': Password change rejected: '
2522 . $status->getWikiText( null, null, 'en' ) );
2523 return false;
2524 }
2525
2526 $this->setOption( 'watchlisttoken', false );
2527 SessionManager::singleton()->invalidateSessionsForUser( $this );
2528
2529 return true;
2530 }
2531
2544 public function changeAuthenticationData( array $data ) {
2545 $manager = AuthManager::singleton();
2546 $reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
2547 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
2548
2549 $status = Status::newGood( 'ignored' );
2550 foreach ( $reqs as $req ) {
2551 $status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
2552 }
2553 if ( $status->getValue() === 'ignored' ) {
2554 $status->warning( 'authenticationdatachange-ignored' );
2555 }
2556
2557 if ( $status->isGood() ) {
2558 foreach ( $reqs as $req ) {
2559 $manager->changeAuthenticationData( $req );
2560 }
2561 }
2562 return $status;
2563 }
2564
2571 public function getToken( $forceCreation = true ) {
2573
2574 $this->load();
2575 if ( !$this->mToken && $forceCreation ) {
2576 $this->setToken();
2577 }
2578
2579 if ( !$this->mToken ) {
2580 // The user doesn't have a token, return null to indicate that.
2581 return null;
2582 } elseif ( $this->mToken === self::INVALID_TOKEN ) {
2583 // We return a random value here so existing token checks are very
2584 // likely to fail.
2585 return MWCryptRand::generateHex( self::TOKEN_LENGTH );
2586 } elseif ( $wgAuthenticationTokenVersion === null ) {
2587 // $wgAuthenticationTokenVersion not in use, so return the raw secret
2588 return $this->mToken;
2589 } else {
2590 // $wgAuthenticationTokenVersion in use, so hmac it.
2591 $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
2592
2593 // The raw hash can be overly long. Shorten it up.
2594 $len = max( 32, self::TOKEN_LENGTH );
2595 if ( strlen( $ret ) < $len ) {
2596 // Should never happen, even md5 is 128 bits
2597 throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
2598 }
2599 return substr( $ret, -$len );
2600 }
2601 }
2602
2609 public function setToken( $token = false ) {
2610 $this->load();
2611 if ( $this->mToken === self::INVALID_TOKEN ) {
2612 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
2613 ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
2614 } elseif ( !$token ) {
2615 $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
2616 } else {
2617 $this->mToken = $token;
2618 }
2619 }
2620
2629 public function setNewpassword( $str, $throttle = true ) {
2630 throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2631 }
2632
2640 throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' );
2641 }
2642
2647 public function getEmail() {
2648 $this->load();
2649 Hooks::run( 'UserGetEmail', [ $this, &$this->mEmail ] );
2650 return $this->mEmail;
2651 }
2652
2658 $this->load();
2659 Hooks::run( 'UserGetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
2660 return $this->mEmailAuthenticated;
2661 }
2662
2667 public function setEmail( $str ) {
2668 $this->load();
2669 if ( $str == $this->mEmail ) {
2670 return;
2671 }
2672 $this->invalidateEmail();
2673 $this->mEmail = $str;
2674 Hooks::run( 'UserSetEmail', [ $this, &$this->mEmail ] );
2675 }
2676
2684 public function setEmailWithConfirmation( $str ) {
2686
2687 if ( !$wgEnableEmail ) {
2688 return Status::newFatal( 'emaildisabled' );
2689 }
2690
2691 $oldaddr = $this->getEmail();
2692 if ( $str === $oldaddr ) {
2693 return Status::newGood( true );
2694 }
2695
2696 $type = $oldaddr != '' ? 'changed' : 'set';
2697 $notificationResult = null;
2698
2699 if ( $wgEmailAuthentication ) {
2700 // Send the user an email notifying the user of the change in registered
2701 // email address on their previous email address
2702 if ( $type == 'changed' ) {
2703 $change = $str != '' ? 'changed' : 'removed';
2704 $notificationResult = $this->sendMail(
2705 wfMessage( 'notificationemail_subject_' . $change )->text(),
2706 wfMessage( 'notificationemail_body_' . $change,
2707 $this->getRequest()->getIP(),
2708 $this->getName(),
2709 $str )->text()
2710 );
2711 }
2712 }
2713
2714 $this->setEmail( $str );
2715
2716 if ( $str !== '' && $wgEmailAuthentication ) {
2717 // Send a confirmation request to the new address if needed
2719
2720 if ( $notificationResult !== null ) {
2721 $result->merge( $notificationResult );
2722 }
2723
2724 if ( $result->isGood() ) {
2725 // Say to the caller that a confirmation and notification mail has been sent
2726 $result->value = 'eauth';
2727 }
2728 } else {
2729 $result = Status::newGood( true );
2730 }
2731
2732 return $result;
2733 }
2734
2739 public function getRealName() {
2740 if ( !$this->isItemLoaded( 'realname' ) ) {
2741 $this->load();
2742 }
2743
2744 return $this->mRealName;
2745 }
2746
2751 public function setRealName( $str ) {
2752 $this->load();
2753 $this->mRealName = $str;
2754 }
2755
2766 public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
2768 $this->loadOptions();
2769
2770 # We want 'disabled' preferences to always behave as the default value for
2771 # users, even if they have set the option explicitly in their settings (ie they
2772 # set it, and then it was disabled removing their ability to change it). But
2773 # we don't want to erase the preferences in the database in case the preference
2774 # is re-enabled again. So don't touch $mOptions, just override the returned value
2775 if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
2776 return self::getDefaultOption( $oname );
2777 }
2778
2779 if ( array_key_exists( $oname, $this->mOptions ) ) {
2780 return $this->mOptions[$oname];
2781 } else {
2782 return $defaultOverride;
2783 }
2784 }
2785
2794 public function getOptions( $flags = 0 ) {
2796 $this->loadOptions();
2797 $options = $this->mOptions;
2798
2799 # We want 'disabled' preferences to always behave as the default value for
2800 # users, even if they have set the option explicitly in their settings (ie they
2801 # set it, and then it was disabled removing their ability to change it). But
2802 # we don't want to erase the preferences in the database in case the preference
2803 # is re-enabled again. So don't touch $mOptions, just override the returned value
2804 foreach ( $wgHiddenPrefs as $pref ) {
2805 $default = self::getDefaultOption( $pref );
2806 if ( $default !== null ) {
2807 $options[$pref] = $default;
2808 }
2809 }
2810
2811 if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
2812 $options = array_diff_assoc( $options, self::getDefaultOptions() );
2813 }
2814
2815 return $options;
2816 }
2817
2825 public function getBoolOption( $oname ) {
2826 return (bool)$this->getOption( $oname );
2827 }
2828
2837 public function getIntOption( $oname, $defaultOverride = 0 ) {
2838 $val = $this->getOption( $oname );
2839 if ( $val == '' ) {
2840 $val = $defaultOverride;
2841 }
2842 return intval( $val );
2843 }
2844
2853 public function setOption( $oname, $val ) {
2854 $this->loadOptions();
2855
2856 // Explicitly NULL values should refer to defaults
2857 if ( is_null( $val ) ) {
2858 $val = self::getDefaultOption( $oname );
2859 }
2860
2861 $this->mOptions[$oname] = $val;
2862 }
2863
2874 public function getTokenFromOption( $oname ) {
2876
2877 $id = $this->getId();
2878 if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) {
2879 return false;
2880 }
2881
2882 $token = $this->getOption( $oname );
2883 if ( !$token ) {
2884 // Default to a value based on the user token to avoid space
2885 // wasted on storing tokens for all users. When this option
2886 // is set manually by the user, only then is it stored.
2887 $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
2888 }
2889
2890 return $token;
2891 }
2892
2902 public function resetTokenFromOption( $oname ) {
2904 if ( in_array( $oname, $wgHiddenPrefs ) ) {
2905 return false;
2906 }
2907
2908 $token = MWCryptRand::generateHex( 40 );
2909 $this->setOption( $oname, $token );
2910 return $token;
2911 }
2912
2936 public static function listOptionKinds() {
2937 return [
2938 'registered',
2939 'registered-multiselect',
2940 'registered-checkmatrix',
2941 'userjs',
2942 'special',
2943 'unused'
2944 ];
2945 }
2946
2959 public function getOptionKinds( IContextSource $context, $options = null ) {
2960 $this->loadOptions();
2961 if ( $options === null ) {
2962 $options = $this->mOptions;
2963 }
2964
2965 $prefs = Preferences::getPreferences( $this, $context );
2966 $mapping = [];
2967
2968 // Pull out the "special" options, so they don't get converted as
2969 // multiselect or checkmatrix.
2970 $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
2971 foreach ( $specialOptions as $name => $value ) {
2972 unset( $prefs[$name] );
2973 }
2974
2975 // Multiselect and checkmatrix options are stored in the database with
2976 // one key per option, each having a boolean value. Extract those keys.
2977 $multiselectOptions = [];
2978 foreach ( $prefs as $name => $info ) {
2979 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
2980 ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
2981 $opts = HTMLFormField::flattenOptions( $info['options'] );
2982 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2983
2984 foreach ( $opts as $value ) {
2985 $multiselectOptions["$prefix$value"] = true;
2986 }
2987
2988 unset( $prefs[$name] );
2989 }
2990 }
2991 $checkmatrixOptions = [];
2992 foreach ( $prefs as $name => $info ) {
2993 if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
2994 ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) {
2995 $columns = HTMLFormField::flattenOptions( $info['columns'] );
2996 $rows = HTMLFormField::flattenOptions( $info['rows'] );
2997 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
2998
2999 foreach ( $columns as $column ) {
3000 foreach ( $rows as $row ) {
3001 $checkmatrixOptions["$prefix$column-$row"] = true;
3002 }
3003 }
3004
3005 unset( $prefs[$name] );
3006 }
3007 }
3008
3009 // $value is ignored
3010 foreach ( $options as $key => $value ) {
3011 if ( isset( $prefs[$key] ) ) {
3012 $mapping[$key] = 'registered';
3013 } elseif ( isset( $multiselectOptions[$key] ) ) {
3014 $mapping[$key] = 'registered-multiselect';
3015 } elseif ( isset( $checkmatrixOptions[$key] ) ) {
3016 $mapping[$key] = 'registered-checkmatrix';
3017 } elseif ( isset( $specialOptions[$key] ) ) {
3018 $mapping[$key] = 'special';
3019 } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
3020 $mapping[$key] = 'userjs';
3021 } else {
3022 $mapping[$key] = 'unused';
3023 }
3024 }
3025
3026 return $mapping;
3027 }
3028
3043 public function resetOptions(
3044 $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
3046 ) {
3047 $this->load();
3048 $defaultOptions = self::getDefaultOptions();
3049
3050 if ( !is_array( $resetKinds ) ) {
3051 $resetKinds = [ $resetKinds ];
3052 }
3053
3054 if ( in_array( 'all', $resetKinds ) ) {
3055 $newOptions = $defaultOptions;
3056 } else {
3057 if ( $context === null ) {
3059 }
3060
3061 $optionKinds = $this->getOptionKinds( $context );
3062 $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
3063 $newOptions = [];
3064
3065 // Use default values for the options that should be deleted, and
3066 // copy old values for the ones that shouldn't.
3067 foreach ( $this->mOptions as $key => $value ) {
3068 if ( in_array( $optionKinds[$key], $resetKinds ) ) {
3069 if ( array_key_exists( $key, $defaultOptions ) ) {
3070 $newOptions[$key] = $defaultOptions[$key];
3071 }
3072 } else {
3073 $newOptions[$key] = $value;
3074 }
3075 }
3076 }
3077
3078 Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
3079
3080 $this->mOptions = $newOptions;
3081 $this->mOptionsLoaded = true;
3082 }
3083
3088 public function getDatePreference() {
3089 // Important migration for old data rows
3090 if ( is_null( $this->mDatePreference ) ) {
3092 $value = $this->getOption( 'date' );
3093 $map = $wgLang->getDatePreferenceMigrationMap();
3094 if ( isset( $map[$value] ) ) {
3095 $value = $map[$value];
3096 }
3097 $this->mDatePreference = $value;
3098 }
3099 return $this->mDatePreference;
3100 }
3101
3108 public function requiresHTTPS() {
3110 if ( !$wgSecureLogin ) {
3111 return false;
3112 } else {
3113 $https = $this->getBoolOption( 'prefershttps' );
3114 Hooks::run( 'UserRequiresHTTPS', [ $this, &$https ] );
3115 if ( $https ) {
3116 $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
3117 }
3118 return $https;
3119 }
3120 }
3121
3127 public function getStubThreshold() {
3128 global $wgMaxArticleSize; # Maximum article size, in Kb
3129 $threshold = $this->getIntOption( 'stubthreshold' );
3130 if ( $threshold > $wgMaxArticleSize * 1024 ) {
3131 // If they have set an impossible value, disable the preference
3132 // so we can use the parser cache again.
3133 $threshold = 0;
3134 }
3135 return $threshold;
3136 }
3137
3142 public function getRights() {
3143 if ( is_null( $this->mRights ) ) {
3144 $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
3145 Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
3146
3147 // Deny any rights denied by the user's session, unless this
3148 // endpoint has no sessions.
3149 if ( !defined( 'MW_NO_SESSION' ) ) {
3150 $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
3151 if ( $allowedRights !== null ) {
3152 $this->mRights = array_intersect( $this->mRights, $allowedRights );
3153 }
3154 }
3155
3156 // Force reindexation of rights when a hook has unset one of them
3157 $this->mRights = array_values( array_unique( $this->mRights ) );
3158
3159 // If block disables login, we should also remove any
3160 // extra rights blocked users might have, in case the
3161 // blocked user has a pre-existing session (T129738).
3162 // This is checked here for cases where people only call
3163 // $user->isAllowed(). It is also checked in Title::checkUserBlock()
3164 // to give a better error message in the common case.
3165 $config = RequestContext::getMain()->getConfig();
3166 if (
3167 $this->isLoggedIn() &&
3168 $config->get( 'BlockDisablesLogin' ) &&
3169 $this->isBlocked()
3170 ) {
3171 $anon = new User;
3172 $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
3173 }
3174 }
3175 return $this->mRights;
3176 }
3177
3183 public function getGroups() {
3184 $this->load();
3185 $this->loadGroups();
3186 return $this->mGroups;
3187 }
3188
3196 public function getEffectiveGroups( $recache = false ) {
3197 if ( $recache || is_null( $this->mEffectiveGroups ) ) {
3198 $this->mEffectiveGroups = array_unique( array_merge(
3199 $this->getGroups(), // explicit groups
3200 $this->getAutomaticGroups( $recache ) // implicit groups
3201 ) );
3202 // Hook for additional groups
3203 Hooks::run( 'UserEffectiveGroups', [ &$this, &$this->mEffectiveGroups ] );
3204 // Force reindexation of groups when a hook has unset one of them
3205 $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
3206 }
3207 return $this->mEffectiveGroups;
3208 }
3209
3217 public function getAutomaticGroups( $recache = false ) {
3218 if ( $recache || is_null( $this->mImplicitGroups ) ) {
3219 $this->mImplicitGroups = [ '*' ];
3220 if ( $this->getId() ) {
3221 $this->mImplicitGroups[] = 'user';
3222
3223 $this->mImplicitGroups = array_unique( array_merge(
3224 $this->mImplicitGroups,
3226 ) );
3227 }
3228 if ( $recache ) {
3229 // Assure data consistency with rights/groups,
3230 // as getEffectiveGroups() depends on this function
3231 $this->mEffectiveGroups = null;
3232 }
3233 }
3234 return $this->mImplicitGroups;
3235 }
3236
3246 public function getFormerGroups() {
3247 $this->load();
3248
3249 if ( is_null( $this->mFormerGroups ) ) {
3250 $db = ( $this->queryFlagsUsed & self::READ_LATEST )
3251 ? wfGetDB( DB_MASTER )
3252 : wfGetDB( DB_REPLICA );
3253 $res = $db->select( 'user_former_groups',
3254 [ 'ufg_group' ],
3255 [ 'ufg_user' => $this->mId ],
3256 __METHOD__ );
3257 $this->mFormerGroups = [];
3258 foreach ( $res as $row ) {
3259 $this->mFormerGroups[] = $row->ufg_group;
3260 }
3261 }
3262
3263 return $this->mFormerGroups;
3264 }
3265
3270 public function getEditCount() {
3271 if ( !$this->getId() ) {
3272 return null;
3273 }
3274
3275 if ( $this->mEditCount === null ) {
3276 /* Populate the count, if it has not been populated yet */
3277 $dbr = wfGetDB( DB_REPLICA );
3278 // check if the user_editcount field has been initialized
3279 $count = $dbr->selectField(
3280 'user', 'user_editcount',
3281 [ 'user_id' => $this->mId ],
3282 __METHOD__
3283 );
3284
3285 if ( $count === null ) {
3286 // it has not been initialized. do so.
3287 $count = $this->initEditCount();
3288 }
3289 $this->mEditCount = $count;
3290 }
3291 return (int)$this->mEditCount;
3292 }
3293
3300 public function addGroup( $group ) {
3301 $this->load();
3302
3303 if ( !Hooks::run( 'UserAddGroup', [ $this, &$group ] ) ) {
3304 return false;
3305 }
3306
3307 $dbw = wfGetDB( DB_MASTER );
3308 if ( $this->getId() ) {
3309 $dbw->insert( 'user_groups',
3310 [
3311 'ug_user' => $this->getId(),
3312 'ug_group' => $group,
3313 ],
3314 __METHOD__,
3315 [ 'IGNORE' ] );
3316 }
3317
3318 $this->loadGroups();
3319 $this->mGroups[] = $group;
3320 // In case loadGroups was not called before, we now have the right twice.
3321 // Get rid of the duplicate.
3322 $this->mGroups = array_unique( $this->mGroups );
3323
3324 // Refresh the groups caches, and clear the rights cache so it will be
3325 // refreshed on the next call to $this->getRights().
3326 $this->getEffectiveGroups( true );
3327 $this->mRights = null;
3328
3329 $this->invalidateCache();
3330
3331 return true;
3332 }
3333
3340 public function removeGroup( $group ) {
3341 $this->load();
3342 if ( !Hooks::run( 'UserRemoveGroup', [ $this, &$group ] ) ) {
3343 return false;
3344 }
3345
3346 $dbw = wfGetDB( DB_MASTER );
3347 $dbw->delete( 'user_groups',
3348 [
3349 'ug_user' => $this->getId(),
3350 'ug_group' => $group,
3351 ], __METHOD__
3352 );
3353 // Remember that the user was in this group
3354 $dbw->insert( 'user_former_groups',
3355 [
3356 'ufg_user' => $this->getId(),
3357 'ufg_group' => $group,
3358 ],
3359 __METHOD__,
3360 [ 'IGNORE' ]
3361 );
3362
3363 $this->loadGroups();
3364 $this->mGroups = array_diff( $this->mGroups, [ $group ] );
3365
3366 // Refresh the groups caches, and clear the rights cache so it will be
3367 // refreshed on the next call to $this->getRights().
3368 $this->getEffectiveGroups( true );
3369 $this->mRights = null;
3370
3371 $this->invalidateCache();
3372
3373 return true;
3374 }
3375
3380 public function isLoggedIn() {
3381 return $this->getId() != 0;
3382 }
3383
3388 public function isAnon() {
3389 return !$this->isLoggedIn();
3390 }
3391
3396 public function isBot() {
3397 if ( in_array( 'bot', $this->getGroups() ) && $this->isAllowed( 'bot' ) ) {
3398 return true;
3399 }
3400
3401 $isBot = false;
3402 Hooks::run( "UserIsBot", [ $this, &$isBot ] );
3403
3404 return $isBot;
3405 }
3406
3413 public function isAllowedAny() {
3414 $permissions = func_get_args();
3415 foreach ( $permissions as $permission ) {
3416 if ( $this->isAllowed( $permission ) ) {
3417 return true;
3418 }
3419 }
3420 return false;
3421 }
3422
3428 public function isAllowedAll() {
3429 $permissions = func_get_args();
3430 foreach ( $permissions as $permission ) {
3431 if ( !$this->isAllowed( $permission ) ) {
3432 return false;
3433 }
3434 }
3435 return true;
3436 }
3437
3443 public function isAllowed( $action = '' ) {
3444 if ( $action === '' ) {
3445 return true; // In the spirit of DWIM
3446 }
3447 // Use strict parameter to avoid matching numeric 0 accidentally inserted
3448 // by misconfiguration: 0 == 'foo'
3449 return in_array( $action, $this->getRights(), true );
3450 }
3451
3456 public function useRCPatrol() {
3458 return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
3459 }
3460
3465 public function useNPPatrol() {
3467 return (
3469 && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3470 );
3471 }
3472
3477 public function useFilePatrol() {
3479 return (
3481 && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
3482 );
3483 }
3484
3490 public function getRequest() {
3491 if ( $this->mRequest ) {
3492 return $this->mRequest;
3493 } else {
3495 return $wgRequest;
3496 }
3497 }
3498
3507 public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3508 if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
3509 return MediaWikiServices::getInstance()->getWatchedItemStore()->isWatched( $this, $title );
3510 }
3511 return false;
3512 }
3513
3521 public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3522 if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3523 MediaWikiServices::getInstance()->getWatchedItemStore()->addWatchBatchForUser(
3524 $this,
3525 [ $title->getSubjectPage(), $title->getTalkPage() ]
3526 );
3527 }
3528 $this->invalidateCache();
3529 }
3530
3538 public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
3539 if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
3540 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
3541 $store->removeWatch( $this, $title->getSubjectPage() );
3542 $store->removeWatch( $this, $title->getTalkPage() );
3543 }
3544 $this->invalidateCache();
3545 }
3546
3555 public function clearNotification( &$title, $oldid = 0 ) {
3557
3558 // Do nothing if the database is locked to writes
3559 if ( wfReadOnly() ) {
3560 return;
3561 }
3562
3563 // Do nothing if not allowed to edit the watchlist
3564 if ( !$this->isAllowed( 'editmywatchlist' ) ) {
3565 return;
3566 }
3567
3568 // If we're working on user's talk page, we should update the talk page message indicator
3569 if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3570 if ( !Hooks::run( 'UserClearNewTalkNotification', [ &$this, $oldid ] ) ) {
3571 return;
3572 }
3573
3574 // Try to update the DB post-send and only if needed...
3575 DeferredUpdates::addCallableUpdate( function() use ( $title, $oldid ) {
3576 if ( !$this->getNewtalk() ) {
3577 return; // no notifications to clear
3578 }
3579
3580 // Delete the last notifications (they stack up)
3581 $this->setNewtalk( false );
3582
3583 // If there is a new, unseen, revision, use its timestamp
3584 $nextid = $oldid
3585 ? $title->getNextRevisionID( $oldid, Title::GAID_FOR_UPDATE )
3586 : null;
3587 if ( $nextid ) {
3588 $this->setNewtalk( true, Revision::newFromId( $nextid ) );
3589 }
3590 } );
3591 }
3592
3594 return;
3595 }
3596
3597 if ( $this->isAnon() ) {
3598 // Nothing else to do...
3599 return;
3600 }
3601
3602 // Only update the timestamp if the page is being watched.
3603 // The query to find out if it is watched is cached both in memcached and per-invocation,
3604 // and when it does have to be executed, it can be on a replica DB
3605 // If this is the user's newtalk page, we always update the timestamp
3606 $force = '';
3607 if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
3608 $force = 'force';
3609 }
3610
3611 MediaWikiServices::getInstance()->getWatchedItemStore()
3612 ->resetNotificationTimestamp( $this, $title, $force, $oldid );
3613 }
3614
3621 public function clearAllNotifications() {
3623 // Do nothing if not allowed to edit the watchlist
3624 if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
3625 return;
3626 }
3627
3629 $this->setNewtalk( false );
3630 return;
3631 }
3632
3633 $id = $this->getId();
3634 if ( !$id ) {
3635 return;
3636 }
3637
3638 $dbw = wfGetDB( DB_MASTER );
3639 $asOfTimes = array_unique( $dbw->selectFieldValues(
3640 'watchlist',
3641 'wl_notificationtimestamp',
3642 [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3643 __METHOD__,
3644 [ 'ORDER BY' => 'wl_notificationtimestamp DESC', 'LIMIT' => 500 ]
3645 ) );
3646 if ( !$asOfTimes ) {
3647 return;
3648 }
3649 // Immediately update the most recent touched rows, which hopefully covers what
3650 // the user sees on the watchlist page before pressing "mark all pages visited"....
3651 $dbw->update(
3652 'watchlist',
3653 [ 'wl_notificationtimestamp' => null ],
3654 [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimes ],
3655 __METHOD__
3656 );
3657 // ...and finish the older ones in a post-send update with lag checks...
3658 DeferredUpdates::addUpdate( new AutoCommitUpdate(
3659 $dbw,
3660 __METHOD__,
3661 function () use ( $dbw, $id ) {
3663
3664 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
3665 $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
3666 $asOfTimes = array_unique( $dbw->selectFieldValues(
3667 'watchlist',
3668 'wl_notificationtimestamp',
3669 [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
3670 __METHOD__
3671 ) );
3672 foreach ( array_chunk( $asOfTimes, $wgUpdateRowsPerQuery ) as $asOfTimeBatch ) {
3673 $dbw->update(
3674 'watchlist',
3675 [ 'wl_notificationtimestamp' => null ],
3676 [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimeBatch ],
3677 __METHOD__
3678 );
3679 $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
3680 }
3681 }
3682 ) );
3683 // We also need to clear here the "you have new message" notification for the own
3684 // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
3685 }
3686
3703 protected function setCookie(
3704 $name, $value, $exp = 0, $secure = null, $params = [], $request = null
3705 ) {
3706 wfDeprecated( __METHOD__, '1.27' );
3707 if ( $request === null ) {
3708 $request = $this->getRequest();
3709 }
3710 $params['secure'] = $secure;
3711 $request->response()->setCookie( $name, $value, $exp, $params );
3712 }
3713
3724 protected function clearCookie( $name, $secure = null, $params = [] ) {
3725 wfDeprecated( __METHOD__, '1.27' );
3726 $this->setCookie( $name, '', time() - 86400, $secure, $params );
3727 }
3728
3744 protected function setExtendedLoginCookie( $name, $value, $secure ) {
3746
3747 wfDeprecated( __METHOD__, '1.27' );
3748
3749 $exp = time();
3750 $exp += $wgExtendedLoginCookieExpiration !== null
3753
3754 $this->setCookie( $name, $value, $exp, $secure );
3755 }
3756
3765 public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
3766 $this->load();
3767 if ( 0 == $this->mId ) {
3768 return;
3769 }
3770
3771 $session = $this->getRequest()->getSession();
3772 if ( $request && $session->getRequest() !== $request ) {
3773 $session = $session->sessionWithRequest( $request );
3774 }
3775 $delay = $session->delaySave();
3776
3777 if ( !$session->getUser()->equals( $this ) ) {
3778 if ( !$session->canSetUser() ) {
3779 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3780 ->warning( __METHOD__ .
3781 ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
3782 );
3783 return;
3784 }
3785 $session->setUser( $this );
3786 }
3787
3788 $session->setRememberUser( $rememberMe );
3789 if ( $secure !== null ) {
3790 $session->setForceHTTPS( $secure );
3791 }
3792
3793 $session->persist();
3794
3795 ScopedCallback::consume( $delay );
3796 }
3797
3801 public function logout() {
3802 if ( Hooks::run( 'UserLogout', [ &$this ] ) ) {
3803 $this->doLogout();
3804 }
3805 }
3806
3811 public function doLogout() {
3812 $session = $this->getRequest()->getSession();
3813 if ( !$session->canSetUser() ) {
3814 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3815 ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
3816 $error = 'immutable';
3817 } elseif ( !$session->getUser()->equals( $this ) ) {
3818 \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
3819 ->warning( __METHOD__ .
3820 ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
3821 );
3822 // But we still may as well make this user object anon
3823 $this->clearInstanceCache( 'defaults' );
3824 $error = 'wronguser';
3825 } else {
3826 $this->clearInstanceCache( 'defaults' );
3827 $delay = $session->delaySave();
3828 $session->unpersist(); // Clear cookies (T127436)
3829 $session->setLoggedOutTimestamp( time() );
3830 $session->setUser( new User );
3831 $session->set( 'wsUserID', 0 ); // Other code expects this
3832 $session->resetAllTokens();
3833 ScopedCallback::consume( $delay );
3834 $error = false;
3835 }
3836 \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
3837 'event' => 'logout',
3838 'successful' => $error === false,
3839 'status' => $error ?: 'success',
3840 ] );
3841 }
3842
3847 public function saveSettings() {
3848 if ( wfReadOnly() ) {
3849 // @TODO: caller should deal with this instead!
3850 // This should really just be an exception.
3851 MWExceptionHandler::logException( new DBExpectedError(
3852 null,
3853 "Could not update user with ID '{$this->mId}'; DB is read-only."
3854 ) );
3855 return;
3856 }
3857
3858 $this->load();
3859 if ( 0 == $this->mId ) {
3860 return; // anon
3861 }
3862
3863 // Get a new user_touched that is higher than the old one.
3864 // This will be used for a CAS check as a last-resort safety
3865 // check against race conditions and replica DB lag.
3866 $newTouched = $this->newTouchedTimestamp();
3867
3868 $dbw = wfGetDB( DB_MASTER );
3869 $dbw->update( 'user',
3870 [ /* SET */
3871 'user_name' => $this->mName,
3872 'user_real_name' => $this->mRealName,
3873 'user_email' => $this->mEmail,
3874 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
3875 'user_touched' => $dbw->timestamp( $newTouched ),
3876 'user_token' => strval( $this->mToken ),
3877 'user_email_token' => $this->mEmailToken,
3878 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
3879 ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
3880 'user_id' => $this->mId,
3881 ] ), __METHOD__
3882 );
3883
3884 if ( !$dbw->affectedRows() ) {
3885 // Maybe the problem was a missed cache update; clear it to be safe
3886 $this->clearSharedCache( 'refresh' );
3887 // User was changed in the meantime or loaded with stale data
3888 $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
3889 throw new MWException(
3890 "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
3891 " the version of the user to be saved is older than the current version."
3892 );
3893 }
3894
3895 $this->mTouched = $newTouched;
3896 $this->saveOptions();
3897
3898 Hooks::run( 'UserSaveSettings', [ $this ] );
3899 $this->clearSharedCache();
3900 $this->getUserPage()->invalidateCache();
3901 }
3902
3909 public function idForName( $flags = 0 ) {
3910 $s = trim( $this->getName() );
3911 if ( $s === '' ) {
3912 return 0;
3913 }
3914
3915 $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
3916 ? wfGetDB( DB_MASTER )
3917 : wfGetDB( DB_REPLICA );
3918
3919 $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
3920 ? [ 'LOCK IN SHARE MODE' ]
3921 : [];
3922
3923 $id = $db->selectField( 'user',
3924 'user_id', [ 'user_name' => $s ], __METHOD__, $options );
3925
3926 return (int)$id;
3927 }
3928
3944 public static function createNew( $name, $params = [] ) {
3945 foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
3946 if ( isset( $params[$field] ) ) {
3947 wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
3948 unset( $params[$field] );
3949 }
3950 }
3951
3952 $user = new User;
3953 $user->load();
3954 $user->setToken(); // init token
3955 if ( isset( $params['options'] ) ) {
3956 $user->mOptions = $params['options'] + (array)$user->mOptions;
3957 unset( $params['options'] );
3958 }
3959 $dbw = wfGetDB( DB_MASTER );
3960 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
3961
3962 $noPass = PasswordFactory::newInvalidPassword()->toString();
3963
3964 $fields = [
3965 'user_id' => $seqVal,
3966 'user_name' => $name,
3967 'user_password' => $noPass,
3968 'user_newpassword' => $noPass,
3969 'user_email' => $user->mEmail,
3970 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
3971 'user_real_name' => $user->mRealName,
3972 'user_token' => strval( $user->mToken ),
3973 'user_registration' => $dbw->timestamp( $user->mRegistration ),
3974 'user_editcount' => 0,
3975 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
3976 ];
3977 foreach ( $params as $name => $value ) {
3978 $fields["user_$name"] = $value;
3979 }
3980 $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
3981 if ( $dbw->affectedRows() ) {
3982 $newUser = User::newFromId( $dbw->insertId() );
3983 } else {
3984 $newUser = null;
3985 }
3986 return $newUser;
3987 }
3988
4015 public function addToDatabase() {
4016 $this->load();
4017 if ( !$this->mToken ) {
4018 $this->setToken(); // init token
4019 }
4020
4021 $this->mTouched = $this->newTouchedTimestamp();
4022
4023 $noPass = PasswordFactory::newInvalidPassword()->toString();
4024
4025 $dbw = wfGetDB( DB_MASTER );
4026 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
4027 $dbw->insert( 'user',
4028 [
4029 'user_id' => $seqVal,
4030 'user_name' => $this->mName,
4031 'user_password' => $noPass,
4032 'user_newpassword' => $noPass,
4033 'user_email' => $this->mEmail,
4034 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
4035 'user_real_name' => $this->mRealName,
4036 'user_token' => strval( $this->mToken ),
4037 'user_registration' => $dbw->timestamp( $this->mRegistration ),
4038 'user_editcount' => 0,
4039 'user_touched' => $dbw->timestamp( $this->mTouched ),
4040 ], __METHOD__,
4041 [ 'IGNORE' ]
4042 );
4043 if ( !$dbw->affectedRows() ) {
4044 // Use locking reads to bypass any REPEATABLE-READ snapshot.
4045 $this->mId = $dbw->selectField(
4046 'user',
4047 'user_id',
4048 [ 'user_name' => $this->mName ],
4049 __METHOD__,
4050 [ 'LOCK IN SHARE MODE' ]
4051 );
4052 $loaded = false;
4053 if ( $this->mId ) {
4054 if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
4055 $loaded = true;
4056 }
4057 }
4058 if ( !$loaded ) {
4059 throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
4060 "to insert user '{$this->mName}' row, but it was not present in select!" );
4061 }
4062 return Status::newFatal( 'userexists' );
4063 }
4064 $this->mId = $dbw->insertId();
4065 self::$idCacheByName[$this->mName] = $this->mId;
4066
4067 // Clear instance cache other than user table data, which is already accurate
4068 $this->clearInstanceCache();
4069
4070 $this->saveOptions();
4071 return Status::newGood();
4072 }
4073
4079 public function spreadAnyEditBlock() {
4080 if ( $this->isLoggedIn() && $this->isBlocked() ) {
4081 return $this->spreadBlock();
4082 }
4083
4084 return false;
4085 }
4086
4092 protected function spreadBlock() {
4093 wfDebug( __METHOD__ . "()\n" );
4094 $this->load();
4095 if ( $this->mId == 0 ) {
4096 return false;
4097 }
4098
4099 $userblock = Block::newFromTarget( $this->getName() );
4100 if ( !$userblock ) {
4101 return false;
4102 }
4103
4104 return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
4105 }
4106
4111 public function isBlockedFromCreateAccount() {
4112 $this->getBlockedStatus();
4113 if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
4114 return $this->mBlock;
4115 }
4116
4117 # bug 13611: if the IP address the user is trying to create an account from is
4118 # blocked with createaccount disabled, prevent new account creation there even
4119 # when the user is logged in
4120 if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
4121 $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
4122 }
4123 return $this->mBlockedFromCreateAccount instanceof Block
4124 && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
4125 ? $this->mBlockedFromCreateAccount
4126 : false;
4127 }
4128
4133 public function isBlockedFromEmailuser() {
4134 $this->getBlockedStatus();
4135 return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
4136 }
4137
4142 public function isAllowedToCreateAccount() {
4143 return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
4144 }
4145
4151 public function getUserPage() {
4152 return Title::makeTitle( NS_USER, $this->getName() );
4153 }
4154
4160 public function getTalkPage() {
4161 $title = $this->getUserPage();
4162 return $title->getTalkPage();
4163 }
4164
4170 public function isNewbie() {
4171 return !$this->isAllowed( 'autoconfirmed' );
4172 }
4173
4180 public function checkPassword( $password ) {
4181 $manager = AuthManager::singleton();
4182 $reqs = AuthenticationRequest::loadRequestsFromSubmission(
4183 $manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
4184 [
4185 'username' => $this->getName(),
4186 'password' => $password,
4187 ]
4188 );
4189 $res = AuthManager::singleton()->beginAuthentication( $reqs, 'null:' );
4190 switch ( $res->status ) {
4191 case AuthenticationResponse::PASS:
4192 return true;
4193 case AuthenticationResponse::FAIL:
4194 // Hope it's not a PreAuthenticationProvider that failed...
4195 \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
4196 ->info( __METHOD__ . ': Authentication failed: ' . $res->message->plain() );
4197 return false;
4198 default:
4199 throw new BadMethodCallException(
4200 'AuthManager returned a response unsupported by ' . __METHOD__
4201 );
4202 }
4203 }
4204
4213 public function checkTemporaryPassword( $plaintext ) {
4214 // Can't check the temporary password individually.
4215 return $this->checkPassword( $plaintext );
4216 }
4217
4229 public function getEditTokenObject( $salt = '', $request = null ) {
4230 if ( $this->isAnon() ) {
4231 return new LoggedOutEditToken();
4232 }
4233
4234 if ( !$request ) {
4235 $request = $this->getRequest();
4236 }
4237 return $request->getSession()->getToken( $salt );
4238 }
4239
4253 public function getEditToken( $salt = '', $request = null ) {
4254 return $this->getEditTokenObject( $salt, $request )->toString();
4255 }
4256
4263 public static function getEditTokenTimestamp( $val ) {
4264 wfDeprecated( __METHOD__, '1.27' );
4265 return MediaWiki\Session\Token::getTimestamp( $val );
4266 }
4267
4280 public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
4281 return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
4282 }
4283
4294 public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
4295 $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . Token::SUFFIX;
4296 return $this->matchEditToken( $val, $salt, $request, $maxage );
4297 }
4298
4306 public function sendConfirmationMail( $type = 'created' ) {
4308 $expiration = null; // gets passed-by-ref and defined in next line.
4309 $token = $this->confirmationToken( $expiration );
4310 $url = $this->confirmationTokenUrl( $token );
4311 $invalidateURL = $this->invalidationTokenUrl( $token );
4312 $this->saveSettings();
4313
4314 if ( $type == 'created' || $type === false ) {
4315 $message = 'confirmemail_body';
4316 } elseif ( $type === true ) {
4317 $message = 'confirmemail_body_changed';
4318 } else {
4319 // Messages: confirmemail_body_changed, confirmemail_body_set
4320 $message = 'confirmemail_body_' . $type;
4321 }
4322
4323 return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(),
4324 wfMessage( $message,
4325 $this->getRequest()->getIP(),
4326 $this->getName(),
4327 $url,
4328 $wgLang->userTimeAndDate( $expiration, $this ),
4329 $invalidateURL,
4330 $wgLang->userDate( $expiration, $this ),
4331 $wgLang->userTime( $expiration, $this ) )->text() );
4332 }
4333
4345 public function sendMail( $subject, $body, $from = null, $replyto = null ) {
4347
4348 if ( $from instanceof User ) {
4349 $sender = MailAddress::newFromUser( $from );
4350 } else {
4351 $sender = new MailAddress( $wgPasswordSender,
4352 wfMessage( 'emailsender' )->inContentLanguage()->text() );
4353 }
4354 $to = MailAddress::newFromUser( $this );
4355
4356 return UserMailer::send( $to, $sender, $subject, $body, [
4357 'replyTo' => $replyto,
4358 ] );
4359 }
4360
4371 protected function confirmationToken( &$expiration ) {
4373 $now = time();
4374 $expires = $now + $wgUserEmailConfirmationTokenExpiry;
4375 $expiration = wfTimestamp( TS_MW, $expires );
4376 $this->load();
4377 $token = MWCryptRand::generateHex( 32 );
4378 $hash = md5( $token );
4379 $this->mEmailToken = $hash;
4380 $this->mEmailTokenExpires = $expiration;
4381 return $token;
4382 }
4383
4389 protected function confirmationTokenUrl( $token ) {
4390 return $this->getTokenUrl( 'ConfirmEmail', $token );
4391 }
4392
4398 protected function invalidationTokenUrl( $token ) {
4399 return $this->getTokenUrl( 'InvalidateEmail', $token );
4400 }
4401
4416 protected function getTokenUrl( $page, $token ) {
4417 // Hack to bypass localization of 'Special:'
4418 $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
4419 return $title->getCanonicalURL();
4420 }
4421
4429 public function confirmEmail() {
4430 // Check if it's already confirmed, so we don't touch the database
4431 // and fire the ConfirmEmailComplete hook on redundant confirmations.
4432 if ( !$this->isEmailConfirmed() ) {
4434 Hooks::run( 'ConfirmEmailComplete', [ $this ] );
4435 }
4436 return true;
4437 }
4438
4446 public function invalidateEmail() {
4447 $this->load();
4448 $this->mEmailToken = null;
4449 $this->mEmailTokenExpires = null;
4450 $this->setEmailAuthenticationTimestamp( null );
4451 $this->mEmail = '';
4452 Hooks::run( 'InvalidateEmailComplete', [ $this ] );
4453 return true;
4454 }
4455
4461 $this->load();
4462 $this->mEmailAuthenticated = $timestamp;
4463 Hooks::run( 'UserSetEmailAuthenticationTimestamp', [ $this, &$this->mEmailAuthenticated ] );
4464 }
4465
4471 public function canSendEmail() {
4473 if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
4474 return false;
4475 }
4476 $canSend = $this->isEmailConfirmed();
4477 Hooks::run( 'UserCanSendEmail', [ &$this, &$canSend ] );
4478 return $canSend;
4479 }
4480
4486 public function canReceiveEmail() {
4487 return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
4488 }
4489
4500 public function isEmailConfirmed() {
4502 $this->load();
4503 $confirmed = true;
4504 if ( Hooks::run( 'EmailConfirmed', [ &$this, &$confirmed ] ) ) {
4505 if ( $this->isAnon() ) {
4506 return false;
4507 }
4508 if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
4509 return false;
4510 }
4512 return false;
4513 }
4514 return true;
4515 } else {
4516 return $confirmed;
4517 }
4518 }
4519
4524 public function isEmailConfirmationPending() {
4526 return $wgEmailAuthentication &&
4527 !$this->isEmailConfirmed() &&
4528 $this->mEmailToken &&
4529 $this->mEmailTokenExpires > wfTimestamp();
4530 }
4531
4539 public function getRegistration() {
4540 if ( $this->isAnon() ) {
4541 return false;
4542 }
4543 $this->load();
4544 return $this->mRegistration;
4545 }
4546
4553 public function getFirstEditTimestamp() {
4554 if ( $this->getId() == 0 ) {
4555 return false; // anons
4556 }
4557 $dbr = wfGetDB( DB_REPLICA );
4558 $time = $dbr->selectField( 'revision', 'rev_timestamp',
4559 [ 'rev_user' => $this->getId() ],
4560 __METHOD__,
4561 [ 'ORDER BY' => 'rev_timestamp ASC' ]
4562 );
4563 if ( !$time ) {
4564 return false; // no edits
4565 }
4566 return wfTimestamp( TS_MW, $time );
4567 }
4568
4575 public static function getGroupPermissions( $groups ) {
4577 $rights = [];
4578 // grant every granted permission first
4579 foreach ( $groups as $group ) {
4580 if ( isset( $wgGroupPermissions[$group] ) ) {
4581 $rights = array_merge( $rights,
4582 // array_filter removes empty items
4583 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
4584 }
4585 }
4586 // now revoke the revoked permissions
4587 foreach ( $groups as $group ) {
4588 if ( isset( $wgRevokePermissions[$group] ) ) {
4589 $rights = array_diff( $rights,
4590 array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
4591 }
4592 }
4593 return array_unique( $rights );
4594 }
4595
4602 public static function getGroupsWithPermission( $role ) {
4604 $allowedGroups = [];
4605 foreach ( array_keys( $wgGroupPermissions ) as $group ) {
4606 if ( self::groupHasPermission( $group, $role ) ) {
4607 $allowedGroups[] = $group;
4608 }
4609 }
4610 return $allowedGroups;
4611 }
4612
4625 public static function groupHasPermission( $group, $role ) {
4627 return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
4628 && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
4629 }
4630
4645 public static function isEveryoneAllowed( $right ) {
4647 static $cache = [];
4648
4649 // Use the cached results, except in unit tests which rely on
4650 // being able change the permission mid-request
4651 if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
4652 return $cache[$right];
4653 }
4654
4655 if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
4656 $cache[$right] = false;
4657 return false;
4658 }
4659
4660 // If it's revoked anywhere, then everyone doesn't have it
4661 foreach ( $wgRevokePermissions as $rights ) {
4662 if ( isset( $rights[$right] ) && $rights[$right] ) {
4663 $cache[$right] = false;
4664 return false;
4665 }
4666 }
4667
4668 // Remove any rights that aren't allowed to the global-session user,
4669 // unless there are no sessions for this endpoint.
4670 if ( !defined( 'MW_NO_SESSION' ) ) {
4671 $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
4672 if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
4673 $cache[$right] = false;
4674 return false;
4675 }
4676 }
4677
4678 // Allow extensions to say false
4679 if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
4680 $cache[$right] = false;
4681 return false;
4682 }
4683
4684 $cache[$right] = true;
4685 return true;
4686 }
4687
4694 public static function getGroupName( $group ) {
4695 $msg = wfMessage( "group-$group" );
4696 return $msg->isBlank() ? $group : $msg->text();
4697 }
4698
4706 public static function getGroupMember( $group, $username = '#' ) {
4707 $msg = wfMessage( "group-$group-member", $username );
4708 return $msg->isBlank() ? $group : $msg->text();
4709 }
4710
4717 public static function getAllGroups() {
4719 return array_diff(
4720 array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
4721 self::getImplicitGroups()
4722 );
4723 }
4724
4729 public static function getAllRights() {
4730 if ( self::$mAllRights === false ) {
4732 if ( count( $wgAvailableRights ) ) {
4733 self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
4734 } else {
4735 self::$mAllRights = self::$mCoreRights;
4736 }
4737 Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
4738 }
4739 return self::$mAllRights;
4740 }
4741
4746 public static function getImplicitGroups() {
4748
4749 $groups = $wgImplicitGroups;
4750 # Deprecated, use $wgImplicitGroups instead
4751 Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
4752
4753 return $groups;
4754 }
4755
4762 public static function getGroupPage( $group ) {
4763 $msg = wfMessage( 'grouppage-' . $group )->inContentLanguage();
4764 if ( $msg->exists() ) {
4765 $title = Title::newFromText( $msg->text() );
4766 if ( is_object( $title ) ) {
4767 return $title;
4768 }
4769 }
4770 return false;
4771 }
4772
4781 public static function makeGroupLinkHTML( $group, $text = '' ) {
4782 if ( $text == '' ) {
4783 $text = self::getGroupName( $group );
4784 }
4785 $title = self::getGroupPage( $group );
4786 if ( $title ) {
4787 return Linker::link( $title, htmlspecialchars( $text ) );
4788 } else {
4789 return htmlspecialchars( $text );
4790 }
4791 }
4792
4801 public static function makeGroupLinkWiki( $group, $text = '' ) {
4802 if ( $text == '' ) {
4803 $text = self::getGroupName( $group );
4804 }
4805 $title = self::getGroupPage( $group );
4806 if ( $title ) {
4807 $page = $title->getFullText();
4808 return "[[$page|$text]]";
4809 } else {
4810 return $text;
4811 }
4812 }
4813
4823 public static function changeableByGroup( $group ) {
4825
4826 $groups = [
4827 'add' => [],
4828 'remove' => [],
4829 'add-self' => [],
4830 'remove-self' => []
4831 ];
4832
4833 if ( empty( $wgAddGroups[$group] ) ) {
4834 // Don't add anything to $groups
4835 } elseif ( $wgAddGroups[$group] === true ) {
4836 // You get everything
4837 $groups['add'] = self::getAllGroups();
4838 } elseif ( is_array( $wgAddGroups[$group] ) ) {
4839 $groups['add'] = $wgAddGroups[$group];
4840 }
4841
4842 // Same thing for remove
4843 if ( empty( $wgRemoveGroups[$group] ) ) {
4844 // Do nothing
4845 } elseif ( $wgRemoveGroups[$group] === true ) {
4846 $groups['remove'] = self::getAllGroups();
4847 } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
4848 $groups['remove'] = $wgRemoveGroups[$group];
4849 }
4850
4851 // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
4852 if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
4853 foreach ( $wgGroupsAddToSelf as $key => $value ) {
4854 if ( is_int( $key ) ) {
4855 $wgGroupsAddToSelf['user'][] = $value;
4856 }
4857 }
4858 }
4859
4860 if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
4861 foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
4862 if ( is_int( $key ) ) {
4863 $wgGroupsRemoveFromSelf['user'][] = $value;
4864 }
4865 }
4866 }
4867
4868 // Now figure out what groups the user can add to him/herself
4869 if ( empty( $wgGroupsAddToSelf[$group] ) ) {
4870 // Do nothing
4871 } elseif ( $wgGroupsAddToSelf[$group] === true ) {
4872 // No idea WHY this would be used, but it's there
4873 $groups['add-self'] = User::getAllGroups();
4874 } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
4875 $groups['add-self'] = $wgGroupsAddToSelf[$group];
4876 }
4877
4878 if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
4879 // Do nothing
4880 } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
4881 $groups['remove-self'] = User::getAllGroups();
4882 } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
4883 $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
4884 }
4885
4886 return $groups;
4887 }
4888
4896 public function changeableGroups() {
4897 if ( $this->isAllowed( 'userrights' ) ) {
4898 // This group gives the right to modify everything (reverse-
4899 // compatibility with old "userrights lets you change
4900 // everything")
4901 // Using array_merge to make the groups reindexed
4902 $all = array_merge( User::getAllGroups() );
4903 return [
4904 'add' => $all,
4905 'remove' => $all,
4906 'add-self' => [],
4907 'remove-self' => []
4908 ];
4909 }
4910
4911 // Okay, it's not so simple, we will have to go through the arrays
4912 $groups = [
4913 'add' => [],
4914 'remove' => [],
4915 'add-self' => [],
4916 'remove-self' => []
4917 ];
4918 $addergroups = $this->getEffectiveGroups();
4919
4920 foreach ( $addergroups as $addergroup ) {
4921 $groups = array_merge_recursive(
4922 $groups, $this->changeableByGroup( $addergroup )
4923 );
4924 $groups['add'] = array_unique( $groups['add'] );
4925 $groups['remove'] = array_unique( $groups['remove'] );
4926 $groups['add-self'] = array_unique( $groups['add-self'] );
4927 $groups['remove-self'] = array_unique( $groups['remove-self'] );
4928 }
4929 return $groups;
4930 }
4931
4935 public function incEditCount() {
4936 wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
4937 function () {
4938 $this->incEditCountImmediate();
4939 },
4940 __METHOD__
4941 );
4942 }
4943
4949 public function incEditCountImmediate() {
4950 if ( $this->isAnon() ) {
4951 return;
4952 }
4953
4954 $dbw = wfGetDB( DB_MASTER );
4955 // No rows will be "affected" if user_editcount is NULL
4956 $dbw->update(
4957 'user',
4958 [ 'user_editcount=user_editcount+1' ],
4959 [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
4960 __METHOD__
4961 );
4962 // Lazy initialization check...
4963 if ( $dbw->affectedRows() == 0 ) {
4964 // Now here's a goddamn hack...
4965 $dbr = wfGetDB( DB_REPLICA );
4966 if ( $dbr !== $dbw ) {
4967 // If we actually have a replica DB server, the count is
4968 // at least one behind because the current transaction
4969 // has not been committed and replicated.
4970 $this->mEditCount = $this->initEditCount( 1 );
4971 } else {
4972 // But if DB_REPLICA is selecting the master, then the
4973 // count we just read includes the revision that was
4974 // just added in the working transaction.
4975 $this->mEditCount = $this->initEditCount();
4976 }
4977 } else {
4978 if ( $this->mEditCount === null ) {
4979 $this->getEditCount();
4980 $dbr = wfGetDB( DB_REPLICA );
4981 $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
4982 } else {
4983 $this->mEditCount++;
4984 }
4985 }
4986 // Edit count in user cache too
4987 $this->invalidateCache();
4988 }
4989
4996 protected function initEditCount( $add = 0 ) {
4997 // Pull from a replica DB to be less cruel to servers
4998 // Accuracy isn't the point anyway here
4999 $dbr = wfGetDB( DB_REPLICA );
5000 $count = (int)$dbr->selectField(
5001 'revision',
5002 'COUNT(rev_user)',
5003 [ 'rev_user' => $this->getId() ],
5004 __METHOD__
5005 );
5006 $count = $count + $add;
5007
5008 $dbw = wfGetDB( DB_MASTER );
5009 $dbw->update(
5010 'user',
5011 [ 'user_editcount' => $count ],
5012 [ 'user_id' => $this->getId() ],
5013 __METHOD__
5014 );
5015
5016 return $count;
5017 }
5018
5025 public static function getRightDescription( $right ) {
5026 $key = "right-$right";
5027 $msg = wfMessage( $key );
5028 return $msg->isBlank() ? $right : $msg->text();
5029 }
5030
5040 public static function crypt( $password, $salt = false ) {
5041 wfDeprecated( __METHOD__, '1.24' );
5042 $passwordFactory = new PasswordFactory();
5043 $passwordFactory->init( RequestContext::getMain()->getConfig() );
5044 $hash = $passwordFactory->newFromPlaintext( $password );
5045 return $hash->toString();
5046 }
5047
5059 public static function comparePasswords( $hash, $password, $userId = false ) {
5060 wfDeprecated( __METHOD__, '1.24' );
5061
5062 // Check for *really* old password hashes that don't even have a type
5063 // The old hash format was just an md5 hex hash, with no type information
5064 if ( preg_match( '/^[0-9a-f]{32}$/', $hash ) ) {
5066 if ( $wgPasswordSalt ) {
5067 $password = ":B:{$userId}:{$hash}";
5068 } else {
5069 $password = ":A:{$hash}";
5070 }
5071 }
5072
5073 $passwordFactory = new PasswordFactory();
5074 $passwordFactory->init( RequestContext::getMain()->getConfig() );
5075 $hash = $passwordFactory->newFromCiphertext( $hash );
5076 return $hash->equals( $password );
5077 }
5078
5099 public function addNewUserLogEntry( $action = false, $reason = '' ) {
5100 return true; // disabled
5101 }
5102
5112 $this->addNewUserLogEntry( 'autocreate' );
5113
5114 return true;
5115 }
5116
5122 protected function loadOptions( $data = null ) {
5124
5125 $this->load();
5126
5127 if ( $this->mOptionsLoaded ) {
5128 return;
5129 }
5130
5131 $this->mOptions = self::getDefaultOptions();
5132
5133 if ( !$this->getId() ) {
5134 // For unlogged-in users, load language/variant options from request.
5135 // There's no need to do it for logged-in users: they can set preferences,
5136 // and handling of page content is done by $pageLang->getPreferredVariant() and such,
5137 // so don't override user's choice (especially when the user chooses site default).
5138 $variant = $wgContLang->getDefaultVariant();
5139 $this->mOptions['variant'] = $variant;
5140 $this->mOptions['language'] = $variant;
5141 $this->mOptionsLoaded = true;
5142 return;
5143 }
5144
5145 // Maybe load from the object
5146 if ( !is_null( $this->mOptionOverrides ) ) {
5147 wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
5148 foreach ( $this->mOptionOverrides as $key => $value ) {
5149 $this->mOptions[$key] = $value;
5150 }
5151 } else {
5152 if ( !is_array( $data ) ) {
5153 wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
5154 // Load from database
5155 $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
5156 ? wfGetDB( DB_MASTER )
5157 : wfGetDB( DB_REPLICA );
5158
5159 $res = $dbr->select(
5160 'user_properties',
5161 [ 'up_property', 'up_value' ],
5162 [ 'up_user' => $this->getId() ],
5163 __METHOD__
5164 );
5165
5166 $this->mOptionOverrides = [];
5167 $data = [];
5168 foreach ( $res as $row ) {
5169 $data[$row->up_property] = $row->up_value;
5170 }
5171 }
5172 foreach ( $data as $property => $value ) {
5173 $this->mOptionOverrides[$property] = $value;
5174 $this->mOptions[$property] = $value;
5175 }
5176 }
5177
5178 $this->mOptionsLoaded = true;
5179
5180 Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
5181 }
5182
5188 protected function saveOptions() {
5189 $this->loadOptions();
5190
5191 // Not using getOptions(), to keep hidden preferences in database
5192 $saveOptions = $this->mOptions;
5193
5194 // Allow hooks to abort, for instance to save to a global profile.
5195 // Reset options to default state before saving.
5196 if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
5197 return;
5198 }
5199
5200 $userId = $this->getId();
5201
5202 $insert_rows = []; // all the new preference rows
5203 foreach ( $saveOptions as $key => $value ) {
5204 // Don't bother storing default values
5205 $defaultOption = self::getDefaultOption( $key );
5206 if ( ( $defaultOption === null && $value !== false && $value !== null )
5207 || $value != $defaultOption
5208 ) {
5209 $insert_rows[] = [
5210 'up_user' => $userId,
5211 'up_property' => $key,
5212 'up_value' => $value,
5213 ];
5214 }
5215 }
5216
5217 $dbw = wfGetDB( DB_MASTER );
5218
5219 $res = $dbw->select( 'user_properties',
5220 [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
5221
5222 // Find prior rows that need to be removed or updated. These rows will
5223 // all be deleted (the latter so that INSERT IGNORE applies the new values).
5224 $keysDelete = [];
5225 foreach ( $res as $row ) {
5226 if ( !isset( $saveOptions[$row->up_property] )
5227 || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
5228 ) {
5229 $keysDelete[] = $row->up_property;
5230 }
5231 }
5232
5233 if ( count( $keysDelete ) ) {
5234 // Do the DELETE by PRIMARY KEY for prior rows.
5235 // In the past a very large portion of calls to this function are for setting
5236 // 'rememberpassword' for new accounts (a preference that has since been removed).
5237 // Doing a blanket per-user DELETE for new accounts with no rows in the table
5238 // caused gap locks on [max user ID,+infinity) which caused high contention since
5239 // updates would pile up on each other as they are for higher (newer) user IDs.
5240 // It might not be necessary these days, but it shouldn't hurt either.
5241 $dbw->delete( 'user_properties',
5242 [ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
5243 }
5244 // Insert the new preference rows
5245 $dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
5246 }
5247
5254 public static function getPasswordFactory() {
5255 wfDeprecated( __METHOD__, '1.27' );
5256 $ret = new PasswordFactory();
5257 $ret->init( RequestContext::getMain()->getConfig() );
5258 return $ret;
5259 }
5260
5285 public static function passwordChangeInputAttribs() {
5287
5288 if ( $wgMinimalPasswordLength == 0 ) {
5289 return [];
5290 }
5291
5292 # Note that the pattern requirement will always be satisfied if the
5293 # input is empty, so we need required in all cases.
5294
5295 # @todo FIXME: Bug 23769: This needs to not claim the password is required
5296 # if e-mail confirmation is being used. Since HTML5 input validation
5297 # is b0rked anyway in some browsers, just return nothing. When it's
5298 # re-enabled, fix this code to not output required for e-mail
5299 # registration.
5300 # $ret = array( 'required' );
5301 $ret = [];
5302
5303 # We can't actually do this right now, because Opera 9.6 will print out
5304 # the entered password visibly in its error message! When other
5305 # browsers add support for this attribute, or Opera fixes its support,
5306 # we can add support with a version check to avoid doing this on Opera
5307 # versions where it will be a problem. Reported to Opera as
5308 # DSK-262266, but they don't have a public bug tracker for us to follow.
5309 /*
5310 if ( $wgMinimalPasswordLength > 1 ) {
5311 $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}';
5312 $ret['title'] = wfMessage( 'passwordtooshort' )
5313 ->numParams( $wgMinimalPasswordLength )->text();
5314 }
5315 */
5316
5317 return $ret;
5318 }
5319
5325 public static function selectFields() {
5326 return [
5327 'user_id',
5328 'user_name',
5329 'user_real_name',
5330 'user_email',
5331 'user_touched',
5332 'user_token',
5333 'user_email_authenticated',
5334 'user_email_token',
5335 'user_email_token_expires',
5336 'user_registration',
5337 'user_editcount',
5338 ];
5339 }
5340
5348 static function newFatalPermissionDeniedStatus( $permission ) {
5350
5351 $groups = array_map(
5352 [ 'User', 'makeGroupLinkWiki' ],
5353 User::getGroupsWithPermission( $permission )
5354 );
5355
5356 if ( $groups ) {
5357 return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
5358 } else {
5359 return Status::newFatal( 'badaccess-group0' );
5360 }
5361 }
5362
5372 public function getInstanceForUpdate() {
5373 if ( !$this->getId() ) {
5374 return null; // anon
5375 }
5376
5377 $user = self::newFromId( $this->getId() );
5378 if ( !$user->loadFromId( self::READ_EXCLUSIVE ) ) {
5379 return null;
5380 }
5381
5382 return $user;
5383 }
5384
5392 public function equals( User $user ) {
5393 return $this->getName() === $user->getName();
5394 }
5395}
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$wgRateLimitsExcludedIPs
Array of IPs 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.
$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.
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
string null $wgAuthenticationTokenVersion
Versioning for authentication tokens.
$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...
$wgPasswordSalt
For compatibility with old installations set to false.
$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...
$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.
$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.
wfMemcKey()
Make a cache key for the local wiki.
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
$wgUser
Definition Setup.php:806
$wgUseEnotif
Definition Setup.php:343
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:664
$wgFullyInitialised
Definition Setup.php:880
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.
prevents( $action, $x=null)
Get/set whether the Block prevents a given action.
Definition Block.php:982
setBlocker( $user)
Set the user who implemented (or will implement) this block.
Definition Block.php:1416
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
Definition Block.php:1122
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest Block.
Definition Block.php:1203
setTarget( $target)
Set the target for this block, and update $this->type accordingly.
Definition Block.php:1400
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:1082
getRequest()
Get the WebRequest object.
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
static hasFlags( $bitfield, $flags)
Base class for the more common types of database errors.
Relational database abstraction object.
Definition Database.php:36
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition IP.php:101
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition IP.php:140
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition IP.php:79
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition IP.php:730
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition IP.php:90
static array $languagesWithVariants
languages supporting variants
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:203
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:394
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:301
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition Revision.php:110
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition Skin.php:93
static newFromIDs( $ids)
Definition UserArray.php:43
static singleton()
Definition UserCache.php:34
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
Check if a user's password complies with any password policies that apply to that user,...
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
loadFromSession()
Load user data from the session.
Definition User.php:1189
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition User.php:3521
string $mEmailToken
Definition User.php:222
string $mToken
Definition User.php:218
string $mTouched
TS_MW timestamp from the DB.
Definition User.php:214
logout()
Log this user out.
Definition User.php:3801
getOptions( $flags=0)
Get all user's options.
Definition User.php:2794
getRequest()
Get the WebRequest object to use with this object.
Definition User.php:3490
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition User.php:969
Block $mBlockedFromCreateAccount
Definition User.php:299
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2108
addToDatabase()
Add this existing user object to the database.
Definition User.php:4015
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:5285
requiresHTTPS()
Determine based on the wiki configuration and the user's options, whether this user must be over HTTP...
Definition User.php:3108
isBlocked( $bFromSlave=true)
Check if user is blocked.
Definition User.php:1934
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition User.php:4645
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:525
resetOptions( $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'], IContextSource $context=null)
Reset certain (or all) options to the site defaults.
Definition User.php:3043
static whoIsReal( $id)
Get the real name of a user given their user ID.
Definition User.php:718
invalidateCache()
Immediately touch the user data cache for this account.
Definition User.php:2377
static $mCacheVars
Array of Strings List of member variables which are saved to the shared cache (memcached).
Definition User.php:93
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition User.php:2657
array $mOptionOverrides
Definition User.php:232
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition User.php:4133
$mOptionsLoaded
Bool Whether the cache variables have been loaded.
Definition User.php:239
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition User.php:928
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition User.php:4553
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:2959
getBlockedStatus( $bFromSlave=true)
Get blocking information.
Definition User.php:1578
static changeableByGroup( $group)
Returns an array of the groups that a particular group can add/remove.
Definition User.php:4823
const VERSION
@const int Serialized record version.
Definition User.php:69
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:4229
static getAllGroups()
Return the set of defined explicit groups.
Definition User.php:4717
bool $mAllowUsertalk
Definition User.php:296
string $mEmailTokenExpires
Definition User.php:224
setCookies( $request=null, $secure=null, $rememberMe=false)
Persist this user's session (e.g.
Definition User.php:3765
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:4253
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition User.php:1671
static $mAllRights
String Cached results of getAllRights()
Definition User.php:200
getTokenUrl( $page, $token)
Internal function to format the e-mail validation/invalidation URLs.
Definition User.php:4416
array $mEffectiveGroups
Definition User.php:273
isBlockedGlobally( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2008
string $mQuickTouched
TS_MW timestamp from cache.
Definition User.php:216
const INVALID_TOKEN
@const string An invalid value for user_token
Definition User.php:57
isSafeToLoad()
Test if it's safe to load this User object.
Definition User.php:341
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition User.php:4625
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition User.php:4500
spreadAnyEditBlock()
If this user is logged-in and blocked, block any IP address they've successfully logged in from.
Definition User.php:4079
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition User.php:3477
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition User.php:421
static crypt( $password, $salt=false)
Make a new-style password hash.
Definition User.php:5040
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition User.php:3443
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition User.php:2442
array $mGroups
Definition User.php:230
setName( $str)
Set the user name.
Definition User.php:2135
changeAuthenticationData(array $data)
Changes credentials of the user.
Definition User.php:2544
Block $mGlobalBlock
Definition User.php:279
static $mCoreRights
Array of Strings Core rights.
Definition User.php:118
getId()
Get the user's ID.
Definition User.php:2083
getRealName()
Get the user's real name.
Definition User.php:2739
loadOptions( $data=null)
Load the user options either from cache, the database or an array.
Definition User.php:5122
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
Definition User.php:2825
clearCookie( $name, $secure=null, $params=[])
Clear a cookie on the user's client.
Definition User.php:3724
isPasswordReminderThrottled()
Has password reminder email been sent within the last $wgPasswordReminderResendTime hours?
Definition User.php:2639
getRegistration()
Get the timestamp of account creation.
Definition User.php:4539
getPassword()
Definition User.php:2453
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition User.php:1739
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
Definition User.php:2022
static edits( $uid)
Count the number of edits of a user.
Definition User.php:1105
string $mRealName
Definition User.php:209
isItemLoaded( $item, $all='all')
Return whether an item has been loaded.
Definition User.php:1168
getTokenFromOption( $oname)
Get a token stored in the preferences (like the watchlist one), resetting it if it's empty (and savin...
Definition User.php:2874
isNewbie()
Determine whether the user is a newbie.
Definition User.php:4170
clearNotification(&$title, $oldid=0)
Clear the user's notification timestamp for the given title.
Definition User.php:3555
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition User.php:768
deleteNewtalk( $field, $id)
Clear the new messages flag for the given user.
Definition User.php:2280
loadFromDatabase( $flags=self::READ_LATEST)
Load user and user_group data from the database.
Definition User.php:1220
invalidationTokenUrl( $token)
Return a URL the user can use to invalidate their email address.
Definition User.php:4398
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition User.php:3465
static randomPassword()
Return a random password.
Definition User.php:1117
static purge( $wikiId, $userId)
Definition User.php:451
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition User.php:2837
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition User.php:1498
canReceiveEmail()
Is this user allowed to receive e-mails within limits of current site configuration?
Definition User.php:4486
string $mEmail
Definition User.php:212
loadDefaults( $name=false)
Set cached properties to default.
Definition User.php:1130
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition User.php:2766
touch()
Update the "touched" timestamp for the user.
Definition User.php:2394
checkTemporaryPassword( $plaintext)
Check if the given clear-text password matches the temporary password sent by e-mail for password res...
Definition User.php:4213
isPingLimitable()
Is this user subject to rate limiting?
Definition User.php:1769
clearSharedCache( $mode='changed')
Clear user data from memcached.
Definition User.php:2353
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition User.php:1046
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition User.php:612
getToken( $forceCreation=true)
Get the user's current token.
Definition User.php:2571
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:548
setInternalPassword( $str)
Set the password and reset the random token unconditionally.
Definition User.php:2494
confirmEmail()
Mark the e-mail address confirmed.
Definition User.php:4429
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition User.php:4602
setId( $v)
Set the user and reload all fields according to a given ID.
Definition User.php:2099
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition User.php:4575
$mNewtalk
Lazy-initialized variables, invalidated with clearInstanceCache.
Definition User.php:261
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition User.php:1523
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition User.php:888
bool $mLocked
Definition User.php:281
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition User.php:3196
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition User.php:5325
isHidden()
Check if user account is hidden.
Definition User.php:2066
static newFromConfirmationCode( $code, $flags=0)
Factory method to fetch whichever user has a given email confirmation code.
Definition User.php:567
setEmailWithConfirmation( $str)
Set the user's e-mail address and a confirmation mail if needed.
Definition User.php:2684
checkPasswordValidity( $password, $purpose='login')
Check if this is a valid password for this user.
Definition User.php:1006
newTouchedTimestamp()
Generate a current or new-future timestamp to be stored in the user_touched field when we update thin...
Definition User.php:2332
inDnsBlacklist( $ip, $bases)
Whether the given IP is in a given DNS blacklist.
Definition User.php:1692
loadFromUserObject( $user)
Load the data for this user object from another user object.
Definition User.php:1352
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition User.php:853
static getDefaultOption( $opt)
Get a given default option value.
Definition User.php:1563
const IGNORE_USER_RIGHTS
Definition User.php:85
getDatePreference()
Get the user's preferred date format.
Definition User.php:3088
string $mHash
Definition User.php:267
static getGroupPage( $group)
Get the title of a page describing a particular group.
Definition User.php:4762
array $mRights
Definition User.php:269
idForName( $flags=0)
If only this user's username is known, and it exists, return the user ID.
Definition User.php:3909
matchEditTokenNoSuffix( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session, ignoring the suffix.
Definition User.php:4294
isWatched( $title, $checkRights=self::CHECK_USER_RIGHTS)
Check the watched status of an article.
Definition User.php:3507
loadFromRow( $row, $data=null)
Initialize this object from a row from the user table.
Definition User.php:1267
addGroup( $group)
Add the user to the given group.
Definition User.php:3300
setPassword( $str)
Set the password and reset the random token.
Definition User.php:2482
string $mBlockedby
Definition User.php:265
confirmationTokenUrl( $token)
Return a URL the user can use to confirm their email address.
Definition User.php:4389
isAllowedToCreateAccount()
Get whether the user is allowed to create an account.
Definition User.php:4142
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition User.php:591
incEditCountImmediate()
Increment the user's edit-count field.
Definition User.php:4949
setExtendedLoginCookie( $name, $value, $secure)
Set an extended login cookie on the user's client.
Definition User.php:3744
static getAllRights()
Get a list of all available permissions.
Definition User.php:4729
getNewtalk()
Check if the user has new messages.
Definition User.php:2152
getGroups()
Get the list of explicit group memberships this user has.
Definition User.php:3183
addNewUserLogEntry( $action=false, $reason='')
Add a newuser log entry for this user.
Definition User.php:5099
validateCache( $timestamp)
Validate the cache for this account.
Definition User.php:2408
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition User.php:3456
loadGroups()
Load the groups from the database if they aren't already loaded.
Definition User.php:1362
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition User.php:708
invalidateEmail()
Invalidate the user's e-mail confirmation, and unauthenticate the e-mail address if it was already co...
Definition User.php:4446
integer $queryFlagsUsed
User::READ_* constant bitfield used to load data.
Definition User.php:302
setEmailAuthenticationTimestamp( $timestamp)
Set the e-mail authentication timestamp.
Definition User.php:4460
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:653
setOption( $oname, $val)
Set the given option for a user.
Definition User.php:2853
saveOptions()
Saves the non-default options for this user, as previously set e.g.
Definition User.php:5188
static getRightDescription( $right)
Get the description of a given right.
Definition User.php:5025
getEditCount()
Get the user's edit count.
Definition User.php:3270
const CHECK_USER_RIGHTS
Definition User.php:80
addAutopromoteOnceGroups( $event)
Add the user to the group if he/she meets given criteria.
Definition User.php:1392
isValidPassword( $password)
Is the input a valid password for this user?
Definition User.php:958
getBlock( $bFromSlave=true)
Get the block affecting the user, or null if the user is not blocked.
Definition User.php:1944
array $mFormerGroups
Definition User.php:277
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition User.php:4111
spreadBlock()
If this (non-anonymous) user is blocked, block the IP address they've successfully logged in from.
Definition User.php:4092
getFormerGroups()
Returns the groups the user has belonged to.
Definition User.php:3246
setRealName( $str)
Set the user's real name.
Definition User.php:2751
static getPasswordFactory()
Lazily instantiate and return a factory object for making passwords.
Definition User.php:5254
int $mId
Cache variables.
Definition User.php:205
getTitleKey()
Get the user's name escaped by underscores.
Definition User.php:2144
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:788
static makeGroupLinkWiki( $group, $text='')
Create a link to the group in Wikitext, if available; else return the group name.
Definition User.php:4801
getTouched()
Get the user touched timestamp.
Definition User.php:2420
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition User.php:4345
Block $mBlock
Definition User.php:293
isLocked()
Check if user account is locked.
Definition User.php:2051
array $mOptions
Definition User.php:285
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition User.php:1460
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition User.php:3538
setPasswordInternal( $str)
Actually set the password and such.
Definition User.php:2506
isEmailConfirmationPending()
Check whether there is an outstanding request for e-mail confirmation.
Definition User.php:4524
checkNewtalk( $field, $id)
Internal uncached check for new messages.
Definition User.php:2240
__toString()
Definition User.php:323
isBlockedFrom( $title, $bFromSlave=false)
Check if user is blocked from editing a particular article.
Definition User.php:1956
getUserPage()
Get this user's personal page title.
Definition User.php:4151
$mFrom
String Initialization data source if mLoadedItems!==true.
Definition User.php:256
confirmationToken(&$expiration)
Generate, store, and return a new e-mail confirmation code.
Definition User.php:4371
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition User.php:4471
getBlockId()
If user is blocked, return the ID for the block.
Definition User.php:1995
isBot()
Definition User.php:3396
string $mRegistration
Definition User.php:226
getTemporaryPassword()
Definition User.php:2462
__construct()
Lightweight constructor for an anonymous user.
Definition User.php:316
setNewtalk( $val, $curRev=null)
Update the 'You have new messages!' status.
Definition User.php:2300
getStubThreshold()
Get the user preferred stub threshold.
Definition User.php:3127
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
Definition User.php:1794
string $mDatePreference
Definition User.php:263
static isValidUserName( $name)
Is the input a valid username?
Definition User.php:804
sendConfirmationMail( $type='created')
Generate a new e-mail confirmation token and send a confirmation/invalidation mail to the user's give...
Definition User.php:4306
getTalkPage()
Get this user's talk page title.
Definition User.php:4160
isLoggedIn()
Get whether the user is logged in.
Definition User.php:3380
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition User.php:728
loadFromCache()
Load user data from shared cache, given mId has already been set.
Definition User.php:472
getCacheKey(WANObjectCache $cache)
Definition User.php:462
initEditCount( $add=0)
Initialize user_editcount from data out of the revision table.
Definition User.php:4996
saveSettings()
Save this user's settings into the database.
Definition User.php:3847
static $idCacheByName
Definition User.php:304
getNewMessageRevisionId()
Get the revision ID for the last talk page revision viewed by the talk page owner.
Definition User.php:2213
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition User.php:3413
getEmail()
Get the user's e-mail address.
Definition User.php:2647
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition User.php:5348
setNewpassword( $str, $throttle=true)
Set the password for a password reminder or new account email.
Definition User.php:2629
getAutomaticGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition User.php:3217
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition User.php:358
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition User.php:1977
getRights()
Get the permissions this user has.
Definition User.php:3142
matchEditToken( $val, $salt='', $request=null, $maxage=null)
Check given value against the token value stored in the session.
Definition User.php:4280
string $mEmailAuthenticated
Definition User.php:220
const GETOPTIONS_EXCLUDE_DEFAULTS
Exclude user options that are set to their default value.
Definition User.php:75
const TOKEN_LENGTH
@const int Number of characters in user_token field.
Definition User.php:52
equals(User $user)
Checks if two user objects point to the same user.
Definition User.php:5392
isAllowedAll()
Definition User.php:3428
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition User.php:2609
updateNewtalk( $field, $id, $curRev=null)
Add or update the new messages flag.
Definition User.php:2255
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition User.php:3944
static getGroupMember( $group, $username='#')
Get the localized descriptive name for a member of a group, if it exists.
Definition User.php:4706
addNewUserLogEntryAutoCreate()
Add an autocreate newuser log entry for this user Used by things like CentralAuth and perhaps other a...
Definition User.php:5111
doLogout()
Clear the user's session, and reset the instance cache.
Definition User.php:3811
setCookie( $name, $value, $exp=0, $secure=null, $params=[], $request=null)
Set a cookie on the user's client.
Definition User.php:3703
setItemLoaded( $item)
Set that an item has been loaded.
Definition User.php:1178
incEditCount()
Deferred version of incEditCountImmediate()
Definition User.php:4935
$mLoadedItems
Array with already loaded items or true if all items have been loaded.
Definition User.php:244
blockedFor()
If user is blocked, return the specified reason for the block.
Definition User.php:1986
static getGroupName( $group)
Get the localized descriptive name for a group, if it exists.
Definition User.php:4694
static listOptionKinds()
Return a list of the types of user options currently returned by User::getOptionKinds().
Definition User.php:2936
static comparePasswords( $hash, $password, $userId=false)
Compare a password hash with a plain-text password.
Definition User.php:5059
int $mEditCount
Definition User.php:228
resetTokenFromOption( $oname)
Reset a token stored in the preferences (like the watchlist one).
Definition User.php:2902
static makeGroupLinkHTML( $group, $text='')
Create a link to the group in HTML, if available; else return the group name.
Definition User.php:4781
bool $mHideName
Definition User.php:283
static getImplicitGroups()
Get a list of implicit groups.
Definition User.php:4746
changeableGroups()
Returns an array of groups that this user can add and remove.
Definition User.php:4896
string $mName
Definition User.php:207
getNewMessageLinks()
Return the data needed to construct links for new talk page message alerts.
Definition User.php:2190
isAnon()
Get whether the user is anonymous.
Definition User.php:3388
setEmail( $str)
Set the user's e-mail address.
Definition User.php:2667
string $mBlockreason
Definition User.php:271
static getEditTokenTimestamp( $val)
Get the embedded timestamp from a token.
Definition User.php:4263
WebRequest $mRequest
Definition User.php:290
checkPassword( $password)
Check to see if the given clear-text password is one of the accepted passwords.
Definition User.php:4180
getInstanceForUpdate()
Get a new instance of this user that was loaded from the master via a locking read.
Definition User.php:5372
makeUpdateConditions(Database $db, array $conditions)
Builds update conditions.
Definition User.php:1442
const EDIT_TOKEN_SUFFIX
Global constant made accessible as class constants so that autoloader magic can be used.
Definition User.php:64
clearAllNotifications()
Resets all of the given user's page-change notification timestamps.
Definition User.php:3621
array $mImplicitGroups
Definition User.php:275
removeGroup( $group)
Remove the user from the given group.
Definition User.php:3340
Multi-datacenter aware caching interface.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
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
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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 document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
$lbFactory
const NS_USER
Definition Defines.php:58
const NS_MAIN
Definition Defines.php:56
const EDIT_TOKEN_SUFFIX
String Some punctuation to prevent editing from broken text-mangling proxies.
Definition User.php:36
const NS_USER_TALK
Definition Defines.php:59
this hook is for auditing only $req
Definition hooks.txt:1010
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1049
the array() calling protocol came about after MediaWiki 1.4rc1.
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:249
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2568
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1752
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition hooks.txt:1937
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:986
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled $incrBy
Definition hooks.txt:2565
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1096
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
null for the local wiki Added in
Definition hooks.txt:1558
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2710
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:1950
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2685
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired 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 inclusive $limit
Definition hooks.txt:1135
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:1949
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:807
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk page
Definition hooks.txt:2543
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:108
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition hooks.txt:2534
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:1734
processing should stop and the error should be shown to the user * false
Definition hooks.txt:189
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:887
$from
if( $limit) $timestamp
$summary
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for objects which can provide a MediaWiki context on request.
Interface for database access objects.
$context
Definition load.php:50
$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
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN boolean columns are always mapped to as the code does not always treat the column as a boolean(which is limited to accepting true, false, 0, 1, t, or f) *The default data type for all VARCHAR
const DB_REPLICA
Definition defines.php:22
const DB_MASTER
Definition defines.php:23
$property
$params
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
Definition defines.php:6
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition defines.php:11