MediaWiki
REL1_24
|
00001 <?php 00027 define( 'EDIT_TOKEN_SUFFIX', '+\\' ); 00028 00039 class User implements IDBAccessObject { 00043 const TOKEN_LENGTH = 32; 00044 00049 const EDIT_TOKEN_SUFFIX = EDIT_TOKEN_SUFFIX; 00050 00054 const VERSION = 10; 00055 00059 const MAX_WATCHED_ITEMS_CACHE = 100; 00060 00064 private static $mPasswordFactory = null; 00065 00072 protected static $mCacheVars = array( 00073 // user table 00074 'mId', 00075 'mName', 00076 'mRealName', 00077 'mEmail', 00078 'mTouched', 00079 'mToken', 00080 'mEmailAuthenticated', 00081 'mEmailToken', 00082 'mEmailTokenExpires', 00083 'mRegistration', 00084 'mEditCount', 00085 // user_groups table 00086 'mGroups', 00087 // user_properties table 00088 'mOptionOverrides', 00089 ); 00090 00097 protected static $mCoreRights = array( 00098 'apihighlimits', 00099 'autoconfirmed', 00100 'autopatrol', 00101 'bigdelete', 00102 'block', 00103 'blockemail', 00104 'bot', 00105 'browsearchive', 00106 'createaccount', 00107 'createpage', 00108 'createtalk', 00109 'delete', 00110 'deletedhistory', 00111 'deletedtext', 00112 'deletelogentry', 00113 'deleterevision', 00114 'edit', 00115 'editinterface', 00116 'editprotected', 00117 'editmyoptions', 00118 'editmyprivateinfo', 00119 'editmyusercss', 00120 'editmyuserjs', 00121 'editmywatchlist', 00122 'editsemiprotected', 00123 'editusercssjs', #deprecated 00124 'editusercss', 00125 'edituserjs', 00126 'hideuser', 00127 'import', 00128 'importupload', 00129 'ipblock-exempt', 00130 'markbotedits', 00131 'mergehistory', 00132 'minoredit', 00133 'move', 00134 'movefile', 00135 'move-categorypages', 00136 'move-rootuserpages', 00137 'move-subpages', 00138 'nominornewtalk', 00139 'noratelimit', 00140 'override-export-depth', 00141 'pagelang', 00142 'passwordreset', 00143 'patrol', 00144 'patrolmarks', 00145 'protect', 00146 'proxyunbannable', 00147 'purge', 00148 'read', 00149 'reupload', 00150 'reupload-own', 00151 'reupload-shared', 00152 'rollback', 00153 'sendemail', 00154 'siteadmin', 00155 'suppressionlog', 00156 'suppressredirect', 00157 'suppressrevision', 00158 'unblockself', 00159 'undelete', 00160 'unwatchedpages', 00161 'upload', 00162 'upload_by_url', 00163 'userrights', 00164 'userrights-interwiki', 00165 'viewmyprivateinfo', 00166 'viewmywatchlist', 00167 'viewsuppressed', 00168 'writeapi', 00169 ); 00170 00174 protected static $mAllRights = false; 00175 00178 public $mId; 00179 00180 public $mName; 00181 00182 public $mRealName; 00183 00188 public $mPassword; 00189 00194 public $mNewpassword; 00195 00196 public $mNewpassTime; 00197 00198 public $mEmail; 00199 00200 public $mTouched; 00201 00202 protected $mToken; 00203 00204 public $mEmailAuthenticated; 00205 00206 protected $mEmailToken; 00207 00208 protected $mEmailTokenExpires; 00209 00210 protected $mRegistration; 00211 00212 protected $mEditCount; 00213 00214 public $mGroups; 00215 00216 protected $mOptionOverrides; 00217 00218 protected $mPasswordExpires; 00220 00225 public $mOptionsLoaded; 00226 00230 protected $mLoadedItems = array(); 00232 00242 public $mFrom; 00243 00247 protected $mNewtalk; 00248 00249 protected $mDatePreference; 00250 00251 public $mBlockedby; 00252 00253 protected $mHash; 00254 00255 public $mRights; 00256 00257 protected $mBlockreason; 00258 00259 protected $mEffectiveGroups; 00260 00261 protected $mImplicitGroups; 00262 00263 protected $mFormerGroups; 00264 00265 protected $mBlockedGlobally; 00266 00267 protected $mLocked; 00268 00269 public $mHideName; 00270 00271 public $mOptions; 00272 00276 private $mRequest; 00277 00279 public $mBlock; 00280 00282 protected $mAllowUsertalk; 00283 00285 private $mBlockedFromCreateAccount = false; 00286 00288 private $mWatchedItems = array(); 00289 00290 public static $idCacheByName = array(); 00291 00302 public function __construct() { 00303 $this->clearInstanceCache( 'defaults' ); 00304 } 00305 00309 public function __toString() { 00310 return $this->getName(); 00311 } 00312 00316 public function load() { 00317 if ( $this->mLoadedItems === true ) { 00318 return; 00319 } 00320 wfProfileIn( __METHOD__ ); 00321 00322 // Set it now to avoid infinite recursion in accessors 00323 $this->mLoadedItems = true; 00324 00325 switch ( $this->mFrom ) { 00326 case 'defaults': 00327 $this->loadDefaults(); 00328 break; 00329 case 'name': 00330 $this->mId = self::idFromName( $this->mName ); 00331 if ( !$this->mId ) { 00332 // Nonexistent user placeholder object 00333 $this->loadDefaults( $this->mName ); 00334 } else { 00335 $this->loadFromId(); 00336 } 00337 break; 00338 case 'id': 00339 $this->loadFromId(); 00340 break; 00341 case 'session': 00342 if ( !$this->loadFromSession() ) { 00343 // Loading from session failed. Load defaults. 00344 $this->loadDefaults(); 00345 } 00346 wfRunHooks( 'UserLoadAfterLoadFromSession', array( $this ) ); 00347 break; 00348 default: 00349 wfProfileOut( __METHOD__ ); 00350 throw new MWException( "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" ); 00351 } 00352 wfProfileOut( __METHOD__ ); 00353 } 00354 00359 public function loadFromId() { 00360 global $wgMemc; 00361 if ( $this->mId == 0 ) { 00362 $this->loadDefaults(); 00363 return false; 00364 } 00365 00366 // Try cache 00367 $key = wfMemcKey( 'user', 'id', $this->mId ); 00368 $data = $wgMemc->get( $key ); 00369 if ( !is_array( $data ) || $data['mVersion'] != self::VERSION ) { 00370 // Object is expired, load from DB 00371 $data = false; 00372 } 00373 00374 if ( !$data ) { 00375 wfDebug( "User: cache miss for user {$this->mId}\n" ); 00376 // Load from DB 00377 if ( !$this->loadFromDatabase() ) { 00378 // Can't load from ID, user is anonymous 00379 return false; 00380 } 00381 $this->saveToCache(); 00382 } else { 00383 wfDebug( "User: got user {$this->mId} from cache\n" ); 00384 // Restore from cache 00385 foreach ( self::$mCacheVars as $name ) { 00386 $this->$name = $data[$name]; 00387 } 00388 } 00389 00390 $this->mLoadedItems = true; 00391 00392 return true; 00393 } 00394 00398 public function saveToCache() { 00399 $this->load(); 00400 $this->loadGroups(); 00401 $this->loadOptions(); 00402 if ( $this->isAnon() ) { 00403 // Anonymous users are uncached 00404 return; 00405 } 00406 $data = array(); 00407 foreach ( self::$mCacheVars as $name ) { 00408 $data[$name] = $this->$name; 00409 } 00410 $data['mVersion'] = self::VERSION; 00411 $key = wfMemcKey( 'user', 'id', $this->mId ); 00412 global $wgMemc; 00413 $wgMemc->set( $key, $data ); 00414 } 00415 00418 00435 public static function newFromName( $name, $validate = 'valid' ) { 00436 if ( $validate === true ) { 00437 $validate = 'valid'; 00438 } 00439 $name = self::getCanonicalName( $name, $validate ); 00440 if ( $name === false ) { 00441 return false; 00442 } else { 00443 // Create unloaded user object 00444 $u = new User; 00445 $u->mName = $name; 00446 $u->mFrom = 'name'; 00447 $u->setItemLoaded( 'name' ); 00448 return $u; 00449 } 00450 } 00451 00458 public static function newFromId( $id ) { 00459 $u = new User; 00460 $u->mId = $id; 00461 $u->mFrom = 'id'; 00462 $u->setItemLoaded( 'id' ); 00463 return $u; 00464 } 00465 00476 public static function newFromConfirmationCode( $code ) { 00477 $dbr = wfGetDB( DB_SLAVE ); 00478 $id = $dbr->selectField( 'user', 'user_id', array( 00479 'user_email_token' => md5( $code ), 00480 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ), 00481 ) ); 00482 if ( $id !== false ) { 00483 return User::newFromId( $id ); 00484 } else { 00485 return null; 00486 } 00487 } 00488 00496 public static function newFromSession( WebRequest $request = null ) { 00497 $user = new User; 00498 $user->mFrom = 'session'; 00499 $user->mRequest = $request; 00500 return $user; 00501 } 00502 00517 public static function newFromRow( $row, $data = null ) { 00518 $user = new User; 00519 $user->loadFromRow( $row, $data ); 00520 return $user; 00521 } 00522 00524 00530 public static function whoIs( $id ) { 00531 return UserCache::singleton()->getProp( $id, 'name' ); 00532 } 00533 00540 public static function whoIsReal( $id ) { 00541 return UserCache::singleton()->getProp( $id, 'real_name' ); 00542 } 00543 00549 public static function idFromName( $name ) { 00550 $nt = Title::makeTitleSafe( NS_USER, $name ); 00551 if ( is_null( $nt ) ) { 00552 // Illegal name 00553 return null; 00554 } 00555 00556 if ( isset( self::$idCacheByName[$name] ) ) { 00557 return self::$idCacheByName[$name]; 00558 } 00559 00560 $dbr = wfGetDB( DB_SLAVE ); 00561 $s = $dbr->selectRow( 00562 'user', 00563 array( 'user_id' ), 00564 array( 'user_name' => $nt->getText() ), 00565 __METHOD__ 00566 ); 00567 00568 if ( $s === false ) { 00569 $result = null; 00570 } else { 00571 $result = $s->user_id; 00572 } 00573 00574 self::$idCacheByName[$name] = $result; 00575 00576 if ( count( self::$idCacheByName ) > 1000 ) { 00577 self::$idCacheByName = array(); 00578 } 00579 00580 return $result; 00581 } 00582 00586 public static function resetIdByNameCache() { 00587 self::$idCacheByName = array(); 00588 } 00589 00606 public static function isIP( $name ) { 00607 return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name ) 00608 || IP::isIPv6( $name ); 00609 } 00610 00622 public static function isValidUserName( $name ) { 00623 global $wgContLang, $wgMaxNameChars; 00624 00625 if ( $name == '' 00626 || User::isIP( $name ) 00627 || strpos( $name, '/' ) !== false 00628 || strlen( $name ) > $wgMaxNameChars 00629 || $name != $wgContLang->ucfirst( $name ) ) { 00630 wfDebugLog( 'username', __METHOD__ . 00631 ": '$name' invalid due to empty, IP, slash, length, or lowercase" ); 00632 return false; 00633 } 00634 00635 // Ensure that the name can't be misresolved as a different title, 00636 // such as with extra namespace keys at the start. 00637 $parsed = Title::newFromText( $name ); 00638 if ( is_null( $parsed ) 00639 || $parsed->getNamespace() 00640 || strcmp( $name, $parsed->getPrefixedText() ) ) { 00641 wfDebugLog( 'username', __METHOD__ . 00642 ": '$name' invalid due to ambiguous prefixes" ); 00643 return false; 00644 } 00645 00646 // Check an additional blacklist of troublemaker characters. 00647 // Should these be merged into the title char list? 00648 $unicodeBlacklist = '/[' . 00649 '\x{0080}-\x{009f}' . # iso-8859-1 control chars 00650 '\x{00a0}' . # non-breaking space 00651 '\x{2000}-\x{200f}' . # various whitespace 00652 '\x{2028}-\x{202f}' . # breaks and control chars 00653 '\x{3000}' . # ideographic space 00654 '\x{e000}-\x{f8ff}' . # private use 00655 ']/u'; 00656 if ( preg_match( $unicodeBlacklist, $name ) ) { 00657 wfDebugLog( 'username', __METHOD__ . 00658 ": '$name' invalid due to blacklisted characters" ); 00659 return false; 00660 } 00661 00662 return true; 00663 } 00664 00676 public static function isUsableName( $name ) { 00677 global $wgReservedUsernames; 00678 // Must be a valid username, obviously ;) 00679 if ( !self::isValidUserName( $name ) ) { 00680 return false; 00681 } 00682 00683 static $reservedUsernames = false; 00684 if ( !$reservedUsernames ) { 00685 $reservedUsernames = $wgReservedUsernames; 00686 wfRunHooks( 'UserGetReservedNames', array( &$reservedUsernames ) ); 00687 } 00688 00689 // Certain names may be reserved for batch processes. 00690 foreach ( $reservedUsernames as $reserved ) { 00691 if ( substr( $reserved, 0, 4 ) == 'msg:' ) { 00692 $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text(); 00693 } 00694 if ( $reserved == $name ) { 00695 return false; 00696 } 00697 } 00698 return true; 00699 } 00700 00713 public static function isCreatableName( $name ) { 00714 global $wgInvalidUsernameCharacters; 00715 00716 // Ensure that the username isn't longer than 235 bytes, so that 00717 // (at least for the builtin skins) user javascript and css files 00718 // will work. (bug 23080) 00719 if ( strlen( $name ) > 235 ) { 00720 wfDebugLog( 'username', __METHOD__ . 00721 ": '$name' invalid due to length" ); 00722 return false; 00723 } 00724 00725 // Preg yells if you try to give it an empty string 00726 if ( $wgInvalidUsernameCharacters !== '' ) { 00727 if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) { 00728 wfDebugLog( 'username', __METHOD__ . 00729 ": '$name' invalid due to wgInvalidUsernameCharacters" ); 00730 return false; 00731 } 00732 } 00733 00734 return self::isUsableName( $name ); 00735 } 00736 00743 public function isValidPassword( $password ) { 00744 //simple boolean wrapper for getPasswordValidity 00745 return $this->getPasswordValidity( $password ) === true; 00746 } 00747 00748 00755 public function getPasswordValidity( $password ) { 00756 $result = $this->checkPasswordValidity( $password ); 00757 if ( $result->isGood() ) { 00758 return true; 00759 } else { 00760 $messages = array(); 00761 foreach ( $result->getErrorsByType( 'error' ) as $error ) { 00762 $messages[] = $error['message']; 00763 } 00764 foreach ( $result->getErrorsByType( 'warning' ) as $warning ) { 00765 $messages[] = $warning['message']; 00766 } 00767 if ( count( $messages ) === 1 ) { 00768 return $messages[0]; 00769 } 00770 return $messages; 00771 } 00772 } 00773 00782 public function checkPasswordValidity( $password ) { 00783 global $wgMinimalPasswordLength, $wgContLang; 00784 00785 static $blockedLogins = array( 00786 'Useruser' => 'Passpass', 'Useruser1' => 'Passpass1', # r75589 00787 'Apitestsysop' => 'testpass', 'Apitestuser' => 'testpass' # r75605 00788 ); 00789 00790 $status = Status::newGood(); 00791 00792 $result = false; //init $result to false for the internal checks 00793 00794 if ( !wfRunHooks( 'isValidPassword', array( $password, &$result, $this ) ) ) { 00795 $status->error( $result ); 00796 return $status; 00797 } 00798 00799 if ( $result === false ) { 00800 if ( strlen( $password ) < $wgMinimalPasswordLength ) { 00801 $status->error( 'passwordtooshort', $wgMinimalPasswordLength ); 00802 return $status; 00803 } elseif ( $wgContLang->lc( $password ) == $wgContLang->lc( $this->mName ) ) { 00804 $status->error( 'password-name-match' ); 00805 return $status; 00806 } elseif ( isset( $blockedLogins[$this->getName()] ) 00807 && $password == $blockedLogins[$this->getName()] 00808 ) { 00809 $status->error( 'password-login-forbidden' ); 00810 return $status; 00811 } else { 00812 //it seems weird returning a Good status here, but this is because of the 00813 //initialization of $result to false above. If the hook is never run or it 00814 //doesn't modify $result, then we will likely get down into this if with 00815 //a valid password. 00816 return $status; 00817 } 00818 } elseif ( $result === true ) { 00819 return $status; 00820 } else { 00821 $status->error( $result ); 00822 return $status; //the isValidPassword hook set a string $result and returned true 00823 } 00824 } 00825 00831 public function expirePassword( $ts = 0 ) { 00832 $this->loadPasswords(); 00833 $timestamp = wfTimestamp( TS_MW, $ts ); 00834 $this->mPasswordExpires = $timestamp; 00835 $this->saveSettings(); 00836 } 00837 00843 public function resetPasswordExpiration( $load = true ) { 00844 global $wgPasswordExpirationDays; 00845 if ( $load ) { 00846 $this->load(); 00847 } 00848 $newExpire = null; 00849 if ( $wgPasswordExpirationDays ) { 00850 $newExpire = wfTimestamp( 00851 TS_MW, 00852 time() + ( $wgPasswordExpirationDays * 24 * 3600 ) 00853 ); 00854 } 00855 // Give extensions a chance to force an expiration 00856 wfRunHooks( 'ResetPasswordExpiration', array( $this, &$newExpire ) ); 00857 $this->mPasswordExpires = $newExpire; 00858 } 00859 00869 public function getPasswordExpired() { 00870 global $wgPasswordExpireGrace; 00871 $expired = false; 00872 $now = wfTimestamp(); 00873 $expiration = $this->getPasswordExpireDate(); 00874 $expUnix = wfTimestamp( TS_UNIX, $expiration ); 00875 if ( $expiration !== null && $expUnix < $now ) { 00876 $expired = ( $expUnix + $wgPasswordExpireGrace < $now ) ? 'hard' : 'soft'; 00877 } 00878 return $expired; 00879 } 00880 00888 public function getPasswordExpireDate() { 00889 $this->load(); 00890 return $this->mPasswordExpires; 00891 } 00892 00906 public static function getCanonicalName( $name, $validate = 'valid' ) { 00907 // Force usernames to capital 00908 global $wgContLang; 00909 $name = $wgContLang->ucfirst( $name ); 00910 00911 # Reject names containing '#'; these will be cleaned up 00912 # with title normalisation, but then it's too late to 00913 # check elsewhere 00914 if ( strpos( $name, '#' ) !== false ) { 00915 return false; 00916 } 00917 00918 // Clean up name according to title rules, 00919 // but only when validation is requested (bug 12654) 00920 $t = ( $validate !== false ) ? 00921 Title::newFromText( $name ) : Title::makeTitle( NS_USER, $name ); 00922 // Check for invalid titles 00923 if ( is_null( $t ) ) { 00924 return false; 00925 } 00926 00927 // Reject various classes of invalid names 00928 global $wgAuth; 00929 $name = $wgAuth->getCanonicalName( $t->getText() ); 00930 00931 switch ( $validate ) { 00932 case false: 00933 break; 00934 case 'valid': 00935 if ( !User::isValidUserName( $name ) ) { 00936 $name = false; 00937 } 00938 break; 00939 case 'usable': 00940 if ( !User::isUsableName( $name ) ) { 00941 $name = false; 00942 } 00943 break; 00944 case 'creatable': 00945 if ( !User::isCreatableName( $name ) ) { 00946 $name = false; 00947 } 00948 break; 00949 default: 00950 throw new MWException( 'Invalid parameter value for $validate in ' . __METHOD__ ); 00951 } 00952 return $name; 00953 } 00954 00963 public static function edits( $uid ) { 00964 wfDeprecated( __METHOD__, '1.21' ); 00965 $user = self::newFromId( $uid ); 00966 return $user->getEditCount(); 00967 } 00968 00974 public static function randomPassword() { 00975 global $wgMinimalPasswordLength; 00976 // Decide the final password length based on our min password length, 00977 // stopping at a minimum of 10 chars. 00978 $length = max( 10, $wgMinimalPasswordLength ); 00979 // Multiply by 1.25 to get the number of hex characters we need 00980 $length = $length * 1.25; 00981 // Generate random hex chars 00982 $hex = MWCryptRand::generateHex( $length ); 00983 // Convert from base 16 to base 32 to get a proper password like string 00984 return wfBaseConvert( $hex, 16, 32 ); 00985 } 00986 00995 public function loadDefaults( $name = false ) { 00996 wfProfileIn( __METHOD__ ); 00997 00998 $passwordFactory = self::getPasswordFactory(); 00999 01000 $this->mId = 0; 01001 $this->mName = $name; 01002 $this->mRealName = ''; 01003 $this->mPassword = $passwordFactory->newFromCiphertext( null ); 01004 $this->mNewpassword = $passwordFactory->newFromCiphertext( null ); 01005 $this->mNewpassTime = null; 01006 $this->mEmail = ''; 01007 $this->mOptionOverrides = null; 01008 $this->mOptionsLoaded = false; 01009 01010 $loggedOut = $this->getRequest()->getCookie( 'LoggedOut' ); 01011 if ( $loggedOut !== null ) { 01012 $this->mTouched = wfTimestamp( TS_MW, $loggedOut ); 01013 } else { 01014 $this->mTouched = '1'; # Allow any pages to be cached 01015 } 01016 01017 $this->mToken = null; // Don't run cryptographic functions till we need a token 01018 $this->mEmailAuthenticated = null; 01019 $this->mEmailToken = ''; 01020 $this->mEmailTokenExpires = null; 01021 $this->mPasswordExpires = null; 01022 $this->resetPasswordExpiration( false ); 01023 $this->mRegistration = wfTimestamp( TS_MW ); 01024 $this->mGroups = array(); 01025 01026 wfRunHooks( 'UserLoadDefaults', array( $this, $name ) ); 01027 01028 wfProfileOut( __METHOD__ ); 01029 } 01030 01043 public function isItemLoaded( $item, $all = 'all' ) { 01044 return ( $this->mLoadedItems === true && $all === 'all' ) || 01045 ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true ); 01046 } 01047 01053 protected function setItemLoaded( $item ) { 01054 if ( is_array( $this->mLoadedItems ) ) { 01055 $this->mLoadedItems[$item] = true; 01056 } 01057 } 01058 01063 private function loadFromSession() { 01064 $result = null; 01065 wfRunHooks( 'UserLoadFromSession', array( $this, &$result ) ); 01066 if ( $result !== null ) { 01067 return $result; 01068 } 01069 01070 $request = $this->getRequest(); 01071 01072 $cookieId = $request->getCookie( 'UserID' ); 01073 $sessId = $request->getSessionData( 'wsUserID' ); 01074 01075 if ( $cookieId !== null ) { 01076 $sId = intval( $cookieId ); 01077 if ( $sessId !== null && $cookieId != $sessId ) { 01078 wfDebugLog( 'loginSessions', "Session user ID ($sessId) and 01079 cookie user ID ($sId) don't match!" ); 01080 return false; 01081 } 01082 $request->setSessionData( 'wsUserID', $sId ); 01083 } elseif ( $sessId !== null && $sessId != 0 ) { 01084 $sId = $sessId; 01085 } else { 01086 return false; 01087 } 01088 01089 if ( $request->getSessionData( 'wsUserName' ) !== null ) { 01090 $sName = $request->getSessionData( 'wsUserName' ); 01091 } elseif ( $request->getCookie( 'UserName' ) !== null ) { 01092 $sName = $request->getCookie( 'UserName' ); 01093 $request->setSessionData( 'wsUserName', $sName ); 01094 } else { 01095 return false; 01096 } 01097 01098 $proposedUser = User::newFromId( $sId ); 01099 if ( !$proposedUser->isLoggedIn() ) { 01100 // Not a valid ID 01101 return false; 01102 } 01103 01104 global $wgBlockDisablesLogin; 01105 if ( $wgBlockDisablesLogin && $proposedUser->isBlocked() ) { 01106 // User blocked and we've disabled blocked user logins 01107 return false; 01108 } 01109 01110 if ( $request->getSessionData( 'wsToken' ) ) { 01111 $passwordCorrect = 01112 ( $proposedUser->getToken( false ) === $request->getSessionData( 'wsToken' ) ); 01113 $from = 'session'; 01114 } elseif ( $request->getCookie( 'Token' ) ) { 01115 # Get the token from DB/cache and clean it up to remove garbage padding. 01116 # This deals with historical problems with bugs and the default column value. 01117 $token = rtrim( $proposedUser->getToken( false ) ); // correct token 01118 // Make comparison in constant time (bug 61346) 01119 $passwordCorrect = strlen( $token ) 01120 && hash_equals( $token, $request->getCookie( 'Token' ) ); 01121 $from = 'cookie'; 01122 } else { 01123 // No session or persistent login cookie 01124 return false; 01125 } 01126 01127 if ( ( $sName === $proposedUser->getName() ) && $passwordCorrect ) { 01128 $this->loadFromUserObject( $proposedUser ); 01129 $request->setSessionData( 'wsToken', $this->mToken ); 01130 wfDebug( "User: logged in from $from\n" ); 01131 return true; 01132 } else { 01133 // Invalid credentials 01134 wfDebug( "User: can't log in from $from, invalid credentials\n" ); 01135 return false; 01136 } 01137 } 01138 01146 public function loadFromDatabase( $flags = 0 ) { 01147 // Paranoia 01148 $this->mId = intval( $this->mId ); 01149 01150 // Anonymous user 01151 if ( !$this->mId ) { 01152 $this->loadDefaults(); 01153 return false; 01154 } 01155 01156 $dbr = wfGetDB( DB_MASTER ); 01157 $s = $dbr->selectRow( 01158 'user', 01159 self::selectFields(), 01160 array( 'user_id' => $this->mId ), 01161 __METHOD__, 01162 ( $flags & self::READ_LOCKING == self::READ_LOCKING ) 01163 ? array( 'LOCK IN SHARE MODE' ) 01164 : array() 01165 ); 01166 01167 wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) ); 01168 01169 if ( $s !== false ) { 01170 // Initialise user table data 01171 $this->loadFromRow( $s ); 01172 $this->mGroups = null; // deferred 01173 $this->getEditCount(); // revalidation for nulls 01174 return true; 01175 } else { 01176 // Invalid user_id 01177 $this->mId = 0; 01178 $this->loadDefaults(); 01179 return false; 01180 } 01181 } 01182 01192 public function loadFromRow( $row, $data = null ) { 01193 $all = true; 01194 $passwordFactory = self::getPasswordFactory(); 01195 01196 $this->mGroups = null; // deferred 01197 01198 if ( isset( $row->user_name ) ) { 01199 $this->mName = $row->user_name; 01200 $this->mFrom = 'name'; 01201 $this->setItemLoaded( 'name' ); 01202 } else { 01203 $all = false; 01204 } 01205 01206 if ( isset( $row->user_real_name ) ) { 01207 $this->mRealName = $row->user_real_name; 01208 $this->setItemLoaded( 'realname' ); 01209 } else { 01210 $all = false; 01211 } 01212 01213 if ( isset( $row->user_id ) ) { 01214 $this->mId = intval( $row->user_id ); 01215 $this->mFrom = 'id'; 01216 $this->setItemLoaded( 'id' ); 01217 } else { 01218 $all = false; 01219 } 01220 01221 if ( isset( $row->user_editcount ) ) { 01222 $this->mEditCount = $row->user_editcount; 01223 } else { 01224 $all = false; 01225 } 01226 01227 if ( isset( $row->user_password ) ) { 01228 // Check for *really* old password hashes that don't even have a type 01229 // The old hash format was just an md5 hex hash, with no type information 01230 if ( preg_match( '/^[0-9a-f]{32}$/', $row->user_password ) ) { 01231 $row->user_password = ":A:{$this->mId}:{$row->user_password}"; 01232 } 01233 01234 try { 01235 $this->mPassword = $passwordFactory->newFromCiphertext( $row->user_password ); 01236 } catch ( PasswordError $e ) { 01237 wfDebug( 'Invalid password hash found in database.' ); 01238 $this->mPassword = $passwordFactory->newFromCiphertext( null ); 01239 } 01240 01241 try { 01242 $this->mNewpassword = $passwordFactory->newFromCiphertext( $row->user_newpassword ); 01243 } catch ( PasswordError $e ) { 01244 wfDebug( 'Invalid password hash found in database.' ); 01245 $this->mNewpassword = $passwordFactory->newFromCiphertext( null ); 01246 } 01247 01248 $this->mNewpassTime = wfTimestampOrNull( TS_MW, $row->user_newpass_time ); 01249 $this->mPasswordExpires = wfTimestampOrNull( TS_MW, $row->user_password_expires ); 01250 } 01251 01252 if ( isset( $row->user_email ) ) { 01253 $this->mEmail = $row->user_email; 01254 $this->mTouched = wfTimestamp( TS_MW, $row->user_touched ); 01255 $this->mToken = $row->user_token; 01256 if ( $this->mToken == '' ) { 01257 $this->mToken = null; 01258 } 01259 $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated ); 01260 $this->mEmailToken = $row->user_email_token; 01261 $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires ); 01262 $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration ); 01263 } else { 01264 $all = false; 01265 } 01266 01267 if ( $all ) { 01268 $this->mLoadedItems = true; 01269 } 01270 01271 if ( is_array( $data ) ) { 01272 if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) { 01273 $this->mGroups = $data['user_groups']; 01274 } 01275 if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) { 01276 $this->loadOptions( $data['user_properties'] ); 01277 } 01278 } 01279 } 01280 01286 protected function loadFromUserObject( $user ) { 01287 $user->load(); 01288 $user->loadGroups(); 01289 $user->loadOptions(); 01290 foreach ( self::$mCacheVars as $var ) { 01291 $this->$var = $user->$var; 01292 } 01293 } 01294 01298 private function loadGroups() { 01299 if ( is_null( $this->mGroups ) ) { 01300 $dbr = wfGetDB( DB_MASTER ); 01301 $res = $dbr->select( 'user_groups', 01302 array( 'ug_group' ), 01303 array( 'ug_user' => $this->mId ), 01304 __METHOD__ ); 01305 $this->mGroups = array(); 01306 foreach ( $res as $row ) { 01307 $this->mGroups[] = $row->ug_group; 01308 } 01309 } 01310 } 01311 01321 private function loadPasswords() { 01322 if ( $this->getId() !== 0 && ( $this->mPassword === null || $this->mNewpassword === null ) ) { 01323 $this->loadFromRow( wfGetDB( DB_MASTER )->selectRow( 01324 'user', 01325 array( 'user_password', 'user_newpassword', 'user_newpass_time', 'user_password_expires' ), 01326 array( 'user_id' => $this->getId() ), 01327 __METHOD__ 01328 ) ); 01329 } 01330 } 01331 01346 public function addAutopromoteOnceGroups( $event ) { 01347 global $wgAutopromoteOnceLogInRC, $wgAuth; 01348 01349 $toPromote = array(); 01350 if ( $this->getId() ) { 01351 $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event ); 01352 if ( count( $toPromote ) ) { 01353 $oldGroups = $this->getGroups(); // previous groups 01354 01355 foreach ( $toPromote as $group ) { 01356 $this->addGroup( $group ); 01357 } 01358 // update groups in external authentication database 01359 $wgAuth->updateExternalDBGroups( $this, $toPromote ); 01360 01361 $newGroups = array_merge( $oldGroups, $toPromote ); // all groups 01362 01363 $logEntry = new ManualLogEntry( 'rights', 'autopromote' ); 01364 $logEntry->setPerformer( $this ); 01365 $logEntry->setTarget( $this->getUserPage() ); 01366 $logEntry->setParameters( array( 01367 '4::oldgroups' => $oldGroups, 01368 '5::newgroups' => $newGroups, 01369 ) ); 01370 $logid = $logEntry->insert(); 01371 if ( $wgAutopromoteOnceLogInRC ) { 01372 $logEntry->publish( $logid ); 01373 } 01374 } 01375 } 01376 return $toPromote; 01377 } 01378 01386 public function clearInstanceCache( $reloadFrom = false ) { 01387 $this->mNewtalk = -1; 01388 $this->mDatePreference = null; 01389 $this->mBlockedby = -1; # Unset 01390 $this->mHash = false; 01391 $this->mRights = null; 01392 $this->mEffectiveGroups = null; 01393 $this->mImplicitGroups = null; 01394 $this->mGroups = null; 01395 $this->mOptions = null; 01396 $this->mOptionsLoaded = false; 01397 $this->mEditCount = null; 01398 01399 if ( $reloadFrom ) { 01400 $this->mLoadedItems = array(); 01401 $this->mFrom = $reloadFrom; 01402 } 01403 } 01404 01411 public static function getDefaultOptions() { 01412 global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin; 01413 01414 static $defOpt = null; 01415 if ( !defined( 'MW_PHPUNIT_TEST' ) && $defOpt !== null ) { 01416 // Disabling this for the unit tests, as they rely on being able to change $wgContLang 01417 // mid-request and see that change reflected in the return value of this function. 01418 // Which is insane and would never happen during normal MW operation 01419 return $defOpt; 01420 } 01421 01422 $defOpt = $wgDefaultUserOptions; 01423 // Default language setting 01424 $defOpt['language'] = $wgContLang->getCode(); 01425 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) { 01426 $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode; 01427 } 01428 foreach ( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) { 01429 $defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] ); 01430 } 01431 $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin ); 01432 01433 wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) ); 01434 01435 return $defOpt; 01436 } 01437 01444 public static function getDefaultOption( $opt ) { 01445 $defOpts = self::getDefaultOptions(); 01446 if ( isset( $defOpts[$opt] ) ) { 01447 return $defOpts[$opt]; 01448 } else { 01449 return null; 01450 } 01451 } 01452 01459 private function getBlockedStatus( $bFromSlave = true ) { 01460 global $wgProxyWhitelist, $wgUser, $wgApplyIpBlocksToXff; 01461 01462 if ( -1 != $this->mBlockedby ) { 01463 return; 01464 } 01465 01466 wfProfileIn( __METHOD__ ); 01467 wfDebug( __METHOD__ . ": checking...\n" ); 01468 01469 // Initialize data... 01470 // Otherwise something ends up stomping on $this->mBlockedby when 01471 // things get lazy-loaded later, causing false positive block hits 01472 // due to -1 !== 0. Probably session-related... Nothing should be 01473 // overwriting mBlockedby, surely? 01474 $this->load(); 01475 01476 # We only need to worry about passing the IP address to the Block generator if the 01477 # user is not immune to autoblocks/hardblocks, and they are the current user so we 01478 # know which IP address they're actually coming from 01479 if ( !$this->isAllowed( 'ipblock-exempt' ) && $this->getID() == $wgUser->getID() ) { 01480 $ip = $this->getRequest()->getIP(); 01481 } else { 01482 $ip = null; 01483 } 01484 01485 // User/IP blocking 01486 $block = Block::newFromTarget( $this, $ip, !$bFromSlave ); 01487 01488 // Proxy blocking 01489 if ( !$block instanceof Block && $ip !== null && !$this->isAllowed( 'proxyunbannable' ) 01490 && !in_array( $ip, $wgProxyWhitelist ) 01491 ) { 01492 // Local list 01493 if ( self::isLocallyBlockedProxy( $ip ) ) { 01494 $block = new Block; 01495 $block->setBlocker( wfMessage( 'proxyblocker' )->text() ); 01496 $block->mReason = wfMessage( 'proxyblockreason' )->text(); 01497 $block->setTarget( $ip ); 01498 } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) { 01499 $block = new Block; 01500 $block->setBlocker( wfMessage( 'sorbs' )->text() ); 01501 $block->mReason = wfMessage( 'sorbsreason' )->text(); 01502 $block->setTarget( $ip ); 01503 } 01504 } 01505 01506 // (bug 23343) Apply IP blocks to the contents of XFF headers, if enabled 01507 if ( !$block instanceof Block 01508 && $wgApplyIpBlocksToXff 01509 && $ip !== null 01510 && !$this->isAllowed( 'proxyunbannable' ) 01511 && !in_array( $ip, $wgProxyWhitelist ) 01512 ) { 01513 $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' ); 01514 $xff = array_map( 'trim', explode( ',', $xff ) ); 01515 $xff = array_diff( $xff, array( $ip ) ); 01516 $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave ); 01517 $block = Block::chooseBlock( $xffblocks, $xff ); 01518 if ( $block instanceof Block ) { 01519 # Mangle the reason to alert the user that the block 01520 # originated from matching the X-Forwarded-For header. 01521 $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text(); 01522 } 01523 } 01524 01525 if ( $block instanceof Block ) { 01526 wfDebug( __METHOD__ . ": Found block.\n" ); 01527 $this->mBlock = $block; 01528 $this->mBlockedby = $block->getByName(); 01529 $this->mBlockreason = $block->mReason; 01530 $this->mHideName = $block->mHideName; 01531 $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' ); 01532 } else { 01533 $this->mBlockedby = ''; 01534 $this->mHideName = 0; 01535 $this->mAllowUsertalk = false; 01536 } 01537 01538 // Extensions 01539 wfRunHooks( 'GetBlockedStatus', array( &$this ) ); 01540 01541 wfProfileOut( __METHOD__ ); 01542 } 01543 01551 public function isDnsBlacklisted( $ip, $checkWhitelist = false ) { 01552 global $wgEnableDnsBlacklist, $wgDnsBlacklistUrls, $wgProxyWhitelist; 01553 01554 if ( !$wgEnableDnsBlacklist ) { 01555 return false; 01556 } 01557 01558 if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) { 01559 return false; 01560 } 01561 01562 return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls ); 01563 } 01564 01572 public function inDnsBlacklist( $ip, $bases ) { 01573 wfProfileIn( __METHOD__ ); 01574 01575 $found = false; 01576 // @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170) 01577 if ( IP::isIPv4( $ip ) ) { 01578 // Reverse IP, bug 21255 01579 $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) ); 01580 01581 foreach ( (array)$bases as $base ) { 01582 // Make hostname 01583 // If we have an access key, use that too (ProjectHoneypot, etc.) 01584 if ( is_array( $base ) ) { 01585 if ( count( $base ) >= 2 ) { 01586 // Access key is 1, base URL is 0 01587 $host = "{$base[1]}.$ipReversed.{$base[0]}"; 01588 } else { 01589 $host = "$ipReversed.{$base[0]}"; 01590 } 01591 } else { 01592 $host = "$ipReversed.$base"; 01593 } 01594 01595 // Send query 01596 $ipList = gethostbynamel( $host ); 01597 01598 if ( $ipList ) { 01599 wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $base!" ); 01600 $found = true; 01601 break; 01602 } else { 01603 wfDebugLog( 'dnsblacklist', "Requested $host, not found in $base." ); 01604 } 01605 } 01606 } 01607 01608 wfProfileOut( __METHOD__ ); 01609 return $found; 01610 } 01611 01619 public static function isLocallyBlockedProxy( $ip ) { 01620 global $wgProxyList; 01621 01622 if ( !$wgProxyList ) { 01623 return false; 01624 } 01625 wfProfileIn( __METHOD__ ); 01626 01627 if ( !is_array( $wgProxyList ) ) { 01628 // Load from the specified file 01629 $wgProxyList = array_map( 'trim', file( $wgProxyList ) ); 01630 } 01631 01632 if ( !is_array( $wgProxyList ) ) { 01633 $ret = false; 01634 } elseif ( array_search( $ip, $wgProxyList ) !== false ) { 01635 $ret = true; 01636 } elseif ( array_key_exists( $ip, $wgProxyList ) ) { 01637 // Old-style flipped proxy list 01638 $ret = true; 01639 } else { 01640 $ret = false; 01641 } 01642 wfProfileOut( __METHOD__ ); 01643 return $ret; 01644 } 01645 01651 public function isPingLimitable() { 01652 global $wgRateLimitsExcludedIPs; 01653 if ( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) { 01654 // No other good way currently to disable rate limits 01655 // for specific IPs. :P 01656 // But this is a crappy hack and should die. 01657 return false; 01658 } 01659 return !$this->isAllowed( 'noratelimit' ); 01660 } 01661 01676 public function pingLimiter( $action = 'edit', $incrBy = 1 ) { 01677 // Call the 'PingLimiter' hook 01678 $result = false; 01679 if ( !wfRunHooks( 'PingLimiter', array( &$this, $action, &$result, $incrBy ) ) ) { 01680 return $result; 01681 } 01682 01683 global $wgRateLimits; 01684 if ( !isset( $wgRateLimits[$action] ) ) { 01685 return false; 01686 } 01687 01688 // Some groups shouldn't trigger the ping limiter, ever 01689 if ( !$this->isPingLimitable() ) { 01690 return false; 01691 } 01692 01693 global $wgMemc; 01694 wfProfileIn( __METHOD__ ); 01695 wfProfileIn( __METHOD__ . '-' . $action ); 01696 01697 $limits = $wgRateLimits[$action]; 01698 $keys = array(); 01699 $id = $this->getId(); 01700 $userLimit = false; 01701 01702 if ( isset( $limits['anon'] ) && $id == 0 ) { 01703 $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon']; 01704 } 01705 01706 if ( isset( $limits['user'] ) && $id != 0 ) { 01707 $userLimit = $limits['user']; 01708 } 01709 if ( $this->isNewbie() ) { 01710 if ( isset( $limits['newbie'] ) && $id != 0 ) { 01711 $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie']; 01712 } 01713 if ( isset( $limits['ip'] ) ) { 01714 $ip = $this->getRequest()->getIP(); 01715 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip']; 01716 } 01717 if ( isset( $limits['subnet'] ) ) { 01718 $ip = $this->getRequest()->getIP(); 01719 $matches = array(); 01720 $subnet = false; 01721 if ( IP::isIPv6( $ip ) ) { 01722 $parts = IP::parseRange( "$ip/64" ); 01723 $subnet = $parts[0]; 01724 } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) { 01725 // IPv4 01726 $subnet = $matches[1]; 01727 } 01728 if ( $subnet !== false ) { 01729 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet']; 01730 } 01731 } 01732 } 01733 // Check for group-specific permissions 01734 // If more than one group applies, use the group with the highest limit 01735 foreach ( $this->getGroups() as $group ) { 01736 if ( isset( $limits[$group] ) ) { 01737 if ( $userLimit === false || $limits[$group] > $userLimit ) { 01738 $userLimit = $limits[$group]; 01739 } 01740 } 01741 } 01742 // Set the user limit key 01743 if ( $userLimit !== false ) { 01744 list( $max, $period ) = $userLimit; 01745 wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" ); 01746 $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit; 01747 } 01748 01749 $triggered = false; 01750 foreach ( $keys as $key => $limit ) { 01751 list( $max, $period ) = $limit; 01752 $summary = "(limit $max in {$period}s)"; 01753 $count = $wgMemc->get( $key ); 01754 // Already pinged? 01755 if ( $count ) { 01756 if ( $count >= $max ) { 01757 wfDebugLog( 'ratelimit', "User '{$this->getName()}' " . 01758 "(IP {$this->getRequest()->getIP()}) tripped $key at $count $summary" ); 01759 $triggered = true; 01760 } else { 01761 wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" ); 01762 } 01763 } else { 01764 wfDebug( __METHOD__ . ": adding record for $key $summary\n" ); 01765 if ( $incrBy > 0 ) { 01766 $wgMemc->add( $key, 0, intval( $period ) ); // first ping 01767 } 01768 } 01769 if ( $incrBy > 0 ) { 01770 $wgMemc->incr( $key, $incrBy ); 01771 } 01772 } 01773 01774 wfProfileOut( __METHOD__ . '-' . $action ); 01775 wfProfileOut( __METHOD__ ); 01776 return $triggered; 01777 } 01778 01786 public function isBlocked( $bFromSlave = true ) { 01787 return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' ); 01788 } 01789 01796 public function getBlock( $bFromSlave = true ) { 01797 $this->getBlockedStatus( $bFromSlave ); 01798 return $this->mBlock instanceof Block ? $this->mBlock : null; 01799 } 01800 01808 public function isBlockedFrom( $title, $bFromSlave = false ) { 01809 global $wgBlockAllowsUTEdit; 01810 wfProfileIn( __METHOD__ ); 01811 01812 $blocked = $this->isBlocked( $bFromSlave ); 01813 $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false ); 01814 // If a user's name is suppressed, they cannot make edits anywhere 01815 if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName() 01816 && $title->getNamespace() == NS_USER_TALK ) { 01817 $blocked = false; 01818 wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" ); 01819 } 01820 01821 wfRunHooks( 'UserIsBlockedFrom', array( $this, $title, &$blocked, &$allowUsertalk ) ); 01822 01823 wfProfileOut( __METHOD__ ); 01824 return $blocked; 01825 } 01826 01831 public function blockedBy() { 01832 $this->getBlockedStatus(); 01833 return $this->mBlockedby; 01834 } 01835 01840 public function blockedFor() { 01841 $this->getBlockedStatus(); 01842 return $this->mBlockreason; 01843 } 01844 01849 public function getBlockId() { 01850 $this->getBlockedStatus(); 01851 return ( $this->mBlock ? $this->mBlock->getId() : false ); 01852 } 01853 01862 public function isBlockedGlobally( $ip = '' ) { 01863 if ( $this->mBlockedGlobally !== null ) { 01864 return $this->mBlockedGlobally; 01865 } 01866 // User is already an IP? 01867 if ( IP::isIPAddress( $this->getName() ) ) { 01868 $ip = $this->getName(); 01869 } elseif ( !$ip ) { 01870 $ip = $this->getRequest()->getIP(); 01871 } 01872 $blocked = false; 01873 wfRunHooks( 'UserIsBlockedGlobally', array( &$this, $ip, &$blocked ) ); 01874 $this->mBlockedGlobally = (bool)$blocked; 01875 return $this->mBlockedGlobally; 01876 } 01877 01883 public function isLocked() { 01884 if ( $this->mLocked !== null ) { 01885 return $this->mLocked; 01886 } 01887 global $wgAuth; 01888 $authUser = $wgAuth->getUserInstance( $this ); 01889 $this->mLocked = (bool)$authUser->isLocked(); 01890 return $this->mLocked; 01891 } 01892 01898 public function isHidden() { 01899 if ( $this->mHideName !== null ) { 01900 return $this->mHideName; 01901 } 01902 $this->getBlockedStatus(); 01903 if ( !$this->mHideName ) { 01904 global $wgAuth; 01905 $authUser = $wgAuth->getUserInstance( $this ); 01906 $this->mHideName = (bool)$authUser->isHidden(); 01907 } 01908 return $this->mHideName; 01909 } 01910 01915 public function getId() { 01916 if ( $this->mId === null && $this->mName !== null && User::isIP( $this->mName ) ) { 01917 // Special case, we know the user is anonymous 01918 return 0; 01919 } elseif ( !$this->isItemLoaded( 'id' ) ) { 01920 // Don't load if this was initialized from an ID 01921 $this->load(); 01922 } 01923 return $this->mId; 01924 } 01925 01930 public function setId( $v ) { 01931 $this->mId = $v; 01932 $this->clearInstanceCache( 'id' ); 01933 } 01934 01939 public function getName() { 01940 if ( $this->isItemLoaded( 'name', 'only' ) ) { 01941 // Special case optimisation 01942 return $this->mName; 01943 } else { 01944 $this->load(); 01945 if ( $this->mName === false ) { 01946 // Clean up IPs 01947 $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() ); 01948 } 01949 return $this->mName; 01950 } 01951 } 01952 01966 public function setName( $str ) { 01967 $this->load(); 01968 $this->mName = $str; 01969 } 01970 01975 public function getTitleKey() { 01976 return str_replace( ' ', '_', $this->getName() ); 01977 } 01978 01983 public function getNewtalk() { 01984 $this->load(); 01985 01986 // Load the newtalk status if it is unloaded (mNewtalk=-1) 01987 if ( $this->mNewtalk === -1 ) { 01988 $this->mNewtalk = false; # reset talk page status 01989 01990 // Check memcached separately for anons, who have no 01991 // entire User object stored in there. 01992 if ( !$this->mId ) { 01993 global $wgDisableAnonTalk; 01994 if ( $wgDisableAnonTalk ) { 01995 // Anon newtalk disabled by configuration. 01996 $this->mNewtalk = false; 01997 } else { 01998 global $wgMemc; 01999 $key = wfMemcKey( 'newtalk', 'ip', $this->getName() ); 02000 $newtalk = $wgMemc->get( $key ); 02001 if ( strval( $newtalk ) !== '' ) { 02002 $this->mNewtalk = (bool)$newtalk; 02003 } else { 02004 // Since we are caching this, make sure it is up to date by getting it 02005 // from the master 02006 $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName(), true ); 02007 $wgMemc->set( $key, (int)$this->mNewtalk, 1800 ); 02008 } 02009 } 02010 } else { 02011 $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId ); 02012 } 02013 } 02014 02015 return (bool)$this->mNewtalk; 02016 } 02017 02031 public function getNewMessageLinks() { 02032 $talks = array(); 02033 if ( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) { 02034 return $talks; 02035 } elseif ( !$this->getNewtalk() ) { 02036 return array(); 02037 } 02038 $utp = $this->getTalkPage(); 02039 $dbr = wfGetDB( DB_SLAVE ); 02040 // Get the "last viewed rev" timestamp from the oldest message notification 02041 $timestamp = $dbr->selectField( 'user_newtalk', 02042 'MIN(user_last_timestamp)', 02043 $this->isAnon() ? array( 'user_ip' => $this->getName() ) : array( 'user_id' => $this->getID() ), 02044 __METHOD__ ); 02045 $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null; 02046 return array( array( 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ) ); 02047 } 02048 02054 public function getNewMessageRevisionId() { 02055 $newMessageRevisionId = null; 02056 $newMessageLinks = $this->getNewMessageLinks(); 02057 if ( $newMessageLinks ) { 02058 // Note: getNewMessageLinks() never returns more than a single link 02059 // and it is always for the same wiki, but we double-check here in 02060 // case that changes some time in the future. 02061 if ( count( $newMessageLinks ) === 1 02062 && $newMessageLinks[0]['wiki'] === wfWikiID() 02063 && $newMessageLinks[0]['rev'] 02064 ) { 02065 $newMessageRevision = $newMessageLinks[0]['rev']; 02066 $newMessageRevisionId = $newMessageRevision->getId(); 02067 } 02068 } 02069 return $newMessageRevisionId; 02070 } 02071 02081 protected function checkNewtalk( $field, $id, $fromMaster = false ) { 02082 if ( $fromMaster ) { 02083 $db = wfGetDB( DB_MASTER ); 02084 } else { 02085 $db = wfGetDB( DB_SLAVE ); 02086 } 02087 $ok = $db->selectField( 'user_newtalk', $field, 02088 array( $field => $id ), __METHOD__ ); 02089 return $ok !== false; 02090 } 02091 02099 protected function updateNewtalk( $field, $id, $curRev = null ) { 02100 // Get timestamp of the talk page revision prior to the current one 02101 $prevRev = $curRev ? $curRev->getPrevious() : false; 02102 $ts = $prevRev ? $prevRev->getTimestamp() : null; 02103 // Mark the user as having new messages since this revision 02104 $dbw = wfGetDB( DB_MASTER ); 02105 $dbw->insert( 'user_newtalk', 02106 array( $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ), 02107 __METHOD__, 02108 'IGNORE' ); 02109 if ( $dbw->affectedRows() ) { 02110 wfDebug( __METHOD__ . ": set on ($field, $id)\n" ); 02111 return true; 02112 } else { 02113 wfDebug( __METHOD__ . " already set ($field, $id)\n" ); 02114 return false; 02115 } 02116 } 02117 02124 protected function deleteNewtalk( $field, $id ) { 02125 $dbw = wfGetDB( DB_MASTER ); 02126 $dbw->delete( 'user_newtalk', 02127 array( $field => $id ), 02128 __METHOD__ ); 02129 if ( $dbw->affectedRows() ) { 02130 wfDebug( __METHOD__ . ": killed on ($field, $id)\n" ); 02131 return true; 02132 } else { 02133 wfDebug( __METHOD__ . ": already gone ($field, $id)\n" ); 02134 return false; 02135 } 02136 } 02137 02144 public function setNewtalk( $val, $curRev = null ) { 02145 if ( wfReadOnly() ) { 02146 return; 02147 } 02148 02149 $this->load(); 02150 $this->mNewtalk = $val; 02151 02152 if ( $this->isAnon() ) { 02153 $field = 'user_ip'; 02154 $id = $this->getName(); 02155 } else { 02156 $field = 'user_id'; 02157 $id = $this->getId(); 02158 } 02159 global $wgMemc; 02160 02161 if ( $val ) { 02162 $changed = $this->updateNewtalk( $field, $id, $curRev ); 02163 } else { 02164 $changed = $this->deleteNewtalk( $field, $id ); 02165 } 02166 02167 if ( $this->isAnon() ) { 02168 // Anons have a separate memcached space, since 02169 // user records aren't kept for them. 02170 $key = wfMemcKey( 'newtalk', 'ip', $id ); 02171 $wgMemc->set( $key, $val ? 1 : 0, 1800 ); 02172 } 02173 if ( $changed ) { 02174 $this->invalidateCache(); 02175 } 02176 } 02177 02183 private static function newTouchedTimestamp() { 02184 global $wgClockSkewFudge; 02185 return wfTimestamp( TS_MW, time() + $wgClockSkewFudge ); 02186 } 02187 02195 public function clearSharedCache() { 02196 $this->load(); 02197 if ( $this->mId ) { 02198 global $wgMemc; 02199 $wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) ); 02200 } 02201 } 02202 02208 public function invalidateCache() { 02209 if ( wfReadOnly() ) { 02210 return; 02211 } 02212 $this->load(); 02213 if ( $this->mId ) { 02214 $this->mTouched = self::newTouchedTimestamp(); 02215 02216 $dbw = wfGetDB( DB_MASTER ); 02217 $userid = $this->mId; 02218 $touched = $this->mTouched; 02219 $method = __METHOD__; 02220 $dbw->onTransactionIdle( function () use ( $dbw, $userid, $touched, $method ) { 02221 // Prevent contention slams by checking user_touched first 02222 $encTouched = $dbw->addQuotes( $dbw->timestamp( $touched ) ); 02223 $needsPurge = $dbw->selectField( 'user', '1', 02224 array( 'user_id' => $userid, 'user_touched < ' . $encTouched ) ); 02225 if ( $needsPurge ) { 02226 $dbw->update( 'user', 02227 array( 'user_touched' => $dbw->timestamp( $touched ) ), 02228 array( 'user_id' => $userid, 'user_touched < ' . $encTouched ), 02229 $method 02230 ); 02231 } 02232 } ); 02233 $this->clearSharedCache(); 02234 } 02235 } 02236 02242 public function validateCache( $timestamp ) { 02243 $this->load(); 02244 return ( $timestamp >= $this->mTouched ); 02245 } 02246 02251 public function getTouched() { 02252 $this->load(); 02253 return $this->mTouched; 02254 } 02255 02260 public function getPassword() { 02261 $this->loadPasswords(); 02262 02263 return $this->mPassword; 02264 } 02265 02270 public function getTemporaryPassword() { 02271 $this->loadPasswords(); 02272 02273 return $this->mNewpassword; 02274 } 02275 02292 public function setPassword( $str ) { 02293 global $wgAuth; 02294 02295 $this->loadPasswords(); 02296 02297 if ( $str !== null ) { 02298 if ( !$wgAuth->allowPasswordChange() ) { 02299 throw new PasswordError( wfMessage( 'password-change-forbidden' )->text() ); 02300 } 02301 02302 if ( !$this->isValidPassword( $str ) ) { 02303 global $wgMinimalPasswordLength; 02304 $valid = $this->getPasswordValidity( $str ); 02305 if ( is_array( $valid ) ) { 02306 $message = array_shift( $valid ); 02307 $params = $valid; 02308 } else { 02309 $message = $valid; 02310 $params = array( $wgMinimalPasswordLength ); 02311 } 02312 throw new PasswordError( wfMessage( $message, $params )->text() ); 02313 } 02314 } 02315 02316 if ( !$wgAuth->setPassword( $this, $str ) ) { 02317 throw new PasswordError( wfMessage( 'externaldberror' )->text() ); 02318 } 02319 02320 $this->setInternalPassword( $str ); 02321 02322 return true; 02323 } 02324 02332 public function setInternalPassword( $str ) { 02333 $this->setToken(); 02334 02335 $passwordFactory = self::getPasswordFactory(); 02336 if ( $str === null ) { 02337 $this->mPassword = $passwordFactory->newFromCiphertext( null ); 02338 } else { 02339 $this->mPassword = $passwordFactory->newFromPlaintext( $str ); 02340 } 02341 02342 $this->mNewpassword = $passwordFactory->newFromCiphertext( null ); 02343 $this->mNewpassTime = null; 02344 } 02345 02352 public function getToken( $forceCreation = true ) { 02353 $this->load(); 02354 if ( !$this->mToken && $forceCreation ) { 02355 $this->setToken(); 02356 } 02357 return $this->mToken; 02358 } 02359 02366 public function setToken( $token = false ) { 02367 $this->load(); 02368 if ( !$token ) { 02369 $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH ); 02370 } else { 02371 $this->mToken = $token; 02372 } 02373 } 02374 02382 public function setNewpassword( $str, $throttle = true ) { 02383 $this->loadPasswords(); 02384 02385 if ( $str === null ) { 02386 $this->mNewpassword = ''; 02387 $this->mNewpassTime = null; 02388 } else { 02389 $this->mNewpassword = self::getPasswordFactory()->newFromPlaintext( $str ); 02390 if ( $throttle ) { 02391 $this->mNewpassTime = wfTimestampNow(); 02392 } 02393 } 02394 } 02395 02401 public function isPasswordReminderThrottled() { 02402 global $wgPasswordReminderResendTime; 02403 $this->load(); 02404 if ( !$this->mNewpassTime || !$wgPasswordReminderResendTime ) { 02405 return false; 02406 } 02407 $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgPasswordReminderResendTime * 3600; 02408 return time() < $expiry; 02409 } 02410 02415 public function getEmail() { 02416 $this->load(); 02417 wfRunHooks( 'UserGetEmail', array( $this, &$this->mEmail ) ); 02418 return $this->mEmail; 02419 } 02420 02425 public function getEmailAuthenticationTimestamp() { 02426 $this->load(); 02427 wfRunHooks( 'UserGetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) ); 02428 return $this->mEmailAuthenticated; 02429 } 02430 02435 public function setEmail( $str ) { 02436 $this->load(); 02437 if ( $str == $this->mEmail ) { 02438 return; 02439 } 02440 $this->invalidateEmail(); 02441 $this->mEmail = $str; 02442 wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) ); 02443 } 02444 02452 public function setEmailWithConfirmation( $str ) { 02453 global $wgEnableEmail, $wgEmailAuthentication; 02454 02455 if ( !$wgEnableEmail ) { 02456 return Status::newFatal( 'emaildisabled' ); 02457 } 02458 02459 $oldaddr = $this->getEmail(); 02460 if ( $str === $oldaddr ) { 02461 return Status::newGood( true ); 02462 } 02463 02464 $this->setEmail( $str ); 02465 02466 if ( $str !== '' && $wgEmailAuthentication ) { 02467 // Send a confirmation request to the new address if needed 02468 $type = $oldaddr != '' ? 'changed' : 'set'; 02469 $result = $this->sendConfirmationMail( $type ); 02470 if ( $result->isGood() ) { 02471 // Say the the caller that a confirmation mail has been sent 02472 $result->value = 'eauth'; 02473 } 02474 } else { 02475 $result = Status::newGood( true ); 02476 } 02477 02478 return $result; 02479 } 02480 02485 public function getRealName() { 02486 if ( !$this->isItemLoaded( 'realname' ) ) { 02487 $this->load(); 02488 } 02489 02490 return $this->mRealName; 02491 } 02492 02497 public function setRealName( $str ) { 02498 $this->load(); 02499 $this->mRealName = $str; 02500 } 02501 02512 public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) { 02513 global $wgHiddenPrefs; 02514 $this->loadOptions(); 02515 02516 # We want 'disabled' preferences to always behave as the default value for 02517 # users, even if they have set the option explicitly in their settings (ie they 02518 # set it, and then it was disabled removing their ability to change it). But 02519 # we don't want to erase the preferences in the database in case the preference 02520 # is re-enabled again. So don't touch $mOptions, just override the returned value 02521 if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) { 02522 return self::getDefaultOption( $oname ); 02523 } 02524 02525 if ( array_key_exists( $oname, $this->mOptions ) ) { 02526 return $this->mOptions[$oname]; 02527 } else { 02528 return $defaultOverride; 02529 } 02530 } 02531 02537 public function getOptions() { 02538 global $wgHiddenPrefs; 02539 $this->loadOptions(); 02540 $options = $this->mOptions; 02541 02542 # We want 'disabled' preferences to always behave as the default value for 02543 # users, even if they have set the option explicitly in their settings (ie they 02544 # set it, and then it was disabled removing their ability to change it). But 02545 # we don't want to erase the preferences in the database in case the preference 02546 # is re-enabled again. So don't touch $mOptions, just override the returned value 02547 foreach ( $wgHiddenPrefs as $pref ) { 02548 $default = self::getDefaultOption( $pref ); 02549 if ( $default !== null ) { 02550 $options[$pref] = $default; 02551 } 02552 } 02553 02554 return $options; 02555 } 02556 02564 public function getBoolOption( $oname ) { 02565 return (bool)$this->getOption( $oname ); 02566 } 02567 02576 public function getIntOption( $oname, $defaultOverride = 0 ) { 02577 $val = $this->getOption( $oname ); 02578 if ( $val == '' ) { 02579 $val = $defaultOverride; 02580 } 02581 return intval( $val ); 02582 } 02583 02592 public function setOption( $oname, $val ) { 02593 $this->loadOptions(); 02594 02595 // Explicitly NULL values should refer to defaults 02596 if ( is_null( $val ) ) { 02597 $val = self::getDefaultOption( $oname ); 02598 } 02599 02600 $this->mOptions[$oname] = $val; 02601 } 02602 02612 public function getTokenFromOption( $oname ) { 02613 global $wgHiddenPrefs; 02614 if ( in_array( $oname, $wgHiddenPrefs ) ) { 02615 return false; 02616 } 02617 02618 $token = $this->getOption( $oname ); 02619 if ( !$token ) { 02620 $token = $this->resetTokenFromOption( $oname ); 02621 $this->saveSettings(); 02622 } 02623 return $token; 02624 } 02625 02635 public function resetTokenFromOption( $oname ) { 02636 global $wgHiddenPrefs; 02637 if ( in_array( $oname, $wgHiddenPrefs ) ) { 02638 return false; 02639 } 02640 02641 $token = MWCryptRand::generateHex( 40 ); 02642 $this->setOption( $oname, $token ); 02643 return $token; 02644 } 02645 02669 public static function listOptionKinds() { 02670 return array( 02671 'registered', 02672 'registered-multiselect', 02673 'registered-checkmatrix', 02674 'userjs', 02675 'special', 02676 'unused' 02677 ); 02678 } 02679 02692 public function getOptionKinds( IContextSource $context, $options = null ) { 02693 $this->loadOptions(); 02694 if ( $options === null ) { 02695 $options = $this->mOptions; 02696 } 02697 02698 $prefs = Preferences::getPreferences( $this, $context ); 02699 $mapping = array(); 02700 02701 // Pull out the "special" options, so they don't get converted as 02702 // multiselect or checkmatrix. 02703 $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true ); 02704 foreach ( $specialOptions as $name => $value ) { 02705 unset( $prefs[$name] ); 02706 } 02707 02708 // Multiselect and checkmatrix options are stored in the database with 02709 // one key per option, each having a boolean value. Extract those keys. 02710 $multiselectOptions = array(); 02711 foreach ( $prefs as $name => $info ) { 02712 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) || 02713 ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) { 02714 $opts = HTMLFormField::flattenOptions( $info['options'] ); 02715 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name; 02716 02717 foreach ( $opts as $value ) { 02718 $multiselectOptions["$prefix$value"] = true; 02719 } 02720 02721 unset( $prefs[$name] ); 02722 } 02723 } 02724 $checkmatrixOptions = array(); 02725 foreach ( $prefs as $name => $info ) { 02726 if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) || 02727 ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) { 02728 $columns = HTMLFormField::flattenOptions( $info['columns'] ); 02729 $rows = HTMLFormField::flattenOptions( $info['rows'] ); 02730 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name; 02731 02732 foreach ( $columns as $column ) { 02733 foreach ( $rows as $row ) { 02734 $checkmatrixOptions["$prefix$column-$row"] = true; 02735 } 02736 } 02737 02738 unset( $prefs[$name] ); 02739 } 02740 } 02741 02742 // $value is ignored 02743 foreach ( $options as $key => $value ) { 02744 if ( isset( $prefs[$key] ) ) { 02745 $mapping[$key] = 'registered'; 02746 } elseif ( isset( $multiselectOptions[$key] ) ) { 02747 $mapping[$key] = 'registered-multiselect'; 02748 } elseif ( isset( $checkmatrixOptions[$key] ) ) { 02749 $mapping[$key] = 'registered-checkmatrix'; 02750 } elseif ( isset( $specialOptions[$key] ) ) { 02751 $mapping[$key] = 'special'; 02752 } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) { 02753 $mapping[$key] = 'userjs'; 02754 } else { 02755 $mapping[$key] = 'unused'; 02756 } 02757 } 02758 02759 return $mapping; 02760 } 02761 02776 public function resetOptions( 02777 $resetKinds = array( 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ), 02778 IContextSource $context = null 02779 ) { 02780 $this->load(); 02781 $defaultOptions = self::getDefaultOptions(); 02782 02783 if ( !is_array( $resetKinds ) ) { 02784 $resetKinds = array( $resetKinds ); 02785 } 02786 02787 if ( in_array( 'all', $resetKinds ) ) { 02788 $newOptions = $defaultOptions; 02789 } else { 02790 if ( $context === null ) { 02791 $context = RequestContext::getMain(); 02792 } 02793 02794 $optionKinds = $this->getOptionKinds( $context ); 02795 $resetKinds = array_intersect( $resetKinds, self::listOptionKinds() ); 02796 $newOptions = array(); 02797 02798 // Use default values for the options that should be deleted, and 02799 // copy old values for the ones that shouldn't. 02800 foreach ( $this->mOptions as $key => $value ) { 02801 if ( in_array( $optionKinds[$key], $resetKinds ) ) { 02802 if ( array_key_exists( $key, $defaultOptions ) ) { 02803 $newOptions[$key] = $defaultOptions[$key]; 02804 } 02805 } else { 02806 $newOptions[$key] = $value; 02807 } 02808 } 02809 } 02810 02811 wfRunHooks( 'UserResetAllOptions', array( $this, &$newOptions, $this->mOptions, $resetKinds ) ); 02812 02813 $this->mOptions = $newOptions; 02814 $this->mOptionsLoaded = true; 02815 } 02816 02821 public function getDatePreference() { 02822 // Important migration for old data rows 02823 if ( is_null( $this->mDatePreference ) ) { 02824 global $wgLang; 02825 $value = $this->getOption( 'date' ); 02826 $map = $wgLang->getDatePreferenceMigrationMap(); 02827 if ( isset( $map[$value] ) ) { 02828 $value = $map[$value]; 02829 } 02830 $this->mDatePreference = $value; 02831 } 02832 return $this->mDatePreference; 02833 } 02834 02841 public function requiresHTTPS() { 02842 global $wgSecureLogin; 02843 if ( !$wgSecureLogin ) { 02844 return false; 02845 } else { 02846 $https = $this->getBoolOption( 'prefershttps' ); 02847 wfRunHooks( 'UserRequiresHTTPS', array( $this, &$https ) ); 02848 if ( $https ) { 02849 $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() ); 02850 } 02851 return $https; 02852 } 02853 } 02854 02860 public function getStubThreshold() { 02861 global $wgMaxArticleSize; # Maximum article size, in Kb 02862 $threshold = $this->getIntOption( 'stubthreshold' ); 02863 if ( $threshold > $wgMaxArticleSize * 1024 ) { 02864 // If they have set an impossible value, disable the preference 02865 // so we can use the parser cache again. 02866 $threshold = 0; 02867 } 02868 return $threshold; 02869 } 02870 02875 public function getRights() { 02876 if ( is_null( $this->mRights ) ) { 02877 $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() ); 02878 wfRunHooks( 'UserGetRights', array( $this, &$this->mRights ) ); 02879 // Force reindexation of rights when a hook has unset one of them 02880 $this->mRights = array_values( array_unique( $this->mRights ) ); 02881 } 02882 return $this->mRights; 02883 } 02884 02890 public function getGroups() { 02891 $this->load(); 02892 $this->loadGroups(); 02893 return $this->mGroups; 02894 } 02895 02903 public function getEffectiveGroups( $recache = false ) { 02904 if ( $recache || is_null( $this->mEffectiveGroups ) ) { 02905 wfProfileIn( __METHOD__ ); 02906 $this->mEffectiveGroups = array_unique( array_merge( 02907 $this->getGroups(), // explicit groups 02908 $this->getAutomaticGroups( $recache ) // implicit groups 02909 ) ); 02910 // Hook for additional groups 02911 wfRunHooks( 'UserEffectiveGroups', array( &$this, &$this->mEffectiveGroups ) ); 02912 // Force reindexation of groups when a hook has unset one of them 02913 $this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) ); 02914 wfProfileOut( __METHOD__ ); 02915 } 02916 return $this->mEffectiveGroups; 02917 } 02918 02926 public function getAutomaticGroups( $recache = false ) { 02927 if ( $recache || is_null( $this->mImplicitGroups ) ) { 02928 wfProfileIn( __METHOD__ ); 02929 $this->mImplicitGroups = array( '*' ); 02930 if ( $this->getId() ) { 02931 $this->mImplicitGroups[] = 'user'; 02932 02933 $this->mImplicitGroups = array_unique( array_merge( 02934 $this->mImplicitGroups, 02935 Autopromote::getAutopromoteGroups( $this ) 02936 ) ); 02937 } 02938 if ( $recache ) { 02939 // Assure data consistency with rights/groups, 02940 // as getEffectiveGroups() depends on this function 02941 $this->mEffectiveGroups = null; 02942 } 02943 wfProfileOut( __METHOD__ ); 02944 } 02945 return $this->mImplicitGroups; 02946 } 02947 02957 public function getFormerGroups() { 02958 if ( is_null( $this->mFormerGroups ) ) { 02959 $dbr = wfGetDB( DB_MASTER ); 02960 $res = $dbr->select( 'user_former_groups', 02961 array( 'ufg_group' ), 02962 array( 'ufg_user' => $this->mId ), 02963 __METHOD__ ); 02964 $this->mFormerGroups = array(); 02965 foreach ( $res as $row ) { 02966 $this->mFormerGroups[] = $row->ufg_group; 02967 } 02968 } 02969 return $this->mFormerGroups; 02970 } 02971 02976 public function getEditCount() { 02977 if ( !$this->getId() ) { 02978 return null; 02979 } 02980 02981 if ( $this->mEditCount === null ) { 02982 /* Populate the count, if it has not been populated yet */ 02983 wfProfileIn( __METHOD__ ); 02984 $dbr = wfGetDB( DB_SLAVE ); 02985 // check if the user_editcount field has been initialized 02986 $count = $dbr->selectField( 02987 'user', 'user_editcount', 02988 array( 'user_id' => $this->mId ), 02989 __METHOD__ 02990 ); 02991 02992 if ( $count === null ) { 02993 // it has not been initialized. do so. 02994 $count = $this->initEditCount(); 02995 } 02996 $this->mEditCount = $count; 02997 wfProfileOut( __METHOD__ ); 02998 } 02999 return (int)$this->mEditCount; 03000 } 03001 03007 public function addGroup( $group ) { 03008 if ( wfRunHooks( 'UserAddGroup', array( $this, &$group ) ) ) { 03009 $dbw = wfGetDB( DB_MASTER ); 03010 if ( $this->getId() ) { 03011 $dbw->insert( 'user_groups', 03012 array( 03013 'ug_user' => $this->getID(), 03014 'ug_group' => $group, 03015 ), 03016 __METHOD__, 03017 array( 'IGNORE' ) ); 03018 } 03019 } 03020 $this->loadGroups(); 03021 $this->mGroups[] = $group; 03022 // In case loadGroups was not called before, we now have the right twice. 03023 // Get rid of the duplicate. 03024 $this->mGroups = array_unique( $this->mGroups ); 03025 03026 // Refresh the groups caches, and clear the rights cache so it will be 03027 // refreshed on the next call to $this->getRights(). 03028 $this->getEffectiveGroups( true ); 03029 $this->mRights = null; 03030 03031 $this->invalidateCache(); 03032 } 03033 03039 public function removeGroup( $group ) { 03040 $this->load(); 03041 if ( wfRunHooks( 'UserRemoveGroup', array( $this, &$group ) ) ) { 03042 $dbw = wfGetDB( DB_MASTER ); 03043 $dbw->delete( 'user_groups', 03044 array( 03045 'ug_user' => $this->getID(), 03046 'ug_group' => $group, 03047 ), __METHOD__ ); 03048 // Remember that the user was in this group 03049 $dbw->insert( 'user_former_groups', 03050 array( 03051 'ufg_user' => $this->getID(), 03052 'ufg_group' => $group, 03053 ), 03054 __METHOD__, 03055 array( 'IGNORE' ) ); 03056 } 03057 $this->loadGroups(); 03058 $this->mGroups = array_diff( $this->mGroups, array( $group ) ); 03059 03060 // Refresh the groups caches, and clear the rights cache so it will be 03061 // refreshed on the next call to $this->getRights(). 03062 $this->getEffectiveGroups( true ); 03063 $this->mRights = null; 03064 03065 $this->invalidateCache(); 03066 } 03067 03072 public function isLoggedIn() { 03073 return $this->getID() != 0; 03074 } 03075 03080 public function isAnon() { 03081 return !$this->isLoggedIn(); 03082 } 03083 03090 public function isAllowedAny( /*...*/ ) { 03091 $permissions = func_get_args(); 03092 foreach ( $permissions as $permission ) { 03093 if ( $this->isAllowed( $permission ) ) { 03094 return true; 03095 } 03096 } 03097 return false; 03098 } 03099 03105 public function isAllowedAll( /*...*/ ) { 03106 $permissions = func_get_args(); 03107 foreach ( $permissions as $permission ) { 03108 if ( !$this->isAllowed( $permission ) ) { 03109 return false; 03110 } 03111 } 03112 return true; 03113 } 03114 03120 public function isAllowed( $action = '' ) { 03121 if ( $action === '' ) { 03122 return true; // In the spirit of DWIM 03123 } 03124 // Patrolling may not be enabled 03125 if ( $action === 'patrol' || $action === 'autopatrol' ) { 03126 global $wgUseRCPatrol, $wgUseNPPatrol; 03127 if ( !$wgUseRCPatrol && !$wgUseNPPatrol ) { 03128 return false; 03129 } 03130 } 03131 // Use strict parameter to avoid matching numeric 0 accidentally inserted 03132 // by misconfiguration: 0 == 'foo' 03133 return in_array( $action, $this->getRights(), true ); 03134 } 03135 03140 public function useRCPatrol() { 03141 global $wgUseRCPatrol; 03142 return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' ); 03143 } 03144 03149 public function useNPPatrol() { 03150 global $wgUseRCPatrol, $wgUseNPPatrol; 03151 return ( 03152 ( $wgUseRCPatrol || $wgUseNPPatrol ) 03153 && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) ) 03154 ); 03155 } 03156 03162 public function getRequest() { 03163 if ( $this->mRequest ) { 03164 return $this->mRequest; 03165 } else { 03166 global $wgRequest; 03167 return $wgRequest; 03168 } 03169 } 03170 03177 public function getSkin() { 03178 wfDeprecated( __METHOD__, '1.18' ); 03179 return RequestContext::getMain()->getSkin(); 03180 } 03181 03191 public function getWatchedItem( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) { 03192 $key = $checkRights . ':' . $title->getNamespace() . ':' . $title->getDBkey(); 03193 03194 if ( isset( $this->mWatchedItems[$key] ) ) { 03195 return $this->mWatchedItems[$key]; 03196 } 03197 03198 if ( count( $this->mWatchedItems ) >= self::MAX_WATCHED_ITEMS_CACHE ) { 03199 $this->mWatchedItems = array(); 03200 } 03201 03202 $this->mWatchedItems[$key] = WatchedItem::fromUserTitle( $this, $title, $checkRights ); 03203 return $this->mWatchedItems[$key]; 03204 } 03205 03214 public function isWatched( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) { 03215 return $this->getWatchedItem( $title, $checkRights )->isWatched(); 03216 } 03217 03225 public function addWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) { 03226 $this->getWatchedItem( $title, $checkRights )->addWatch(); 03227 $this->invalidateCache(); 03228 } 03229 03237 public function removeWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) { 03238 $this->getWatchedItem( $title, $checkRights )->removeWatch(); 03239 $this->invalidateCache(); 03240 } 03241 03250 public function clearNotification( &$title, $oldid = 0 ) { 03251 global $wgUseEnotif, $wgShowUpdatedMarker; 03252 03253 // Do nothing if the database is locked to writes 03254 if ( wfReadOnly() ) { 03255 return; 03256 } 03257 03258 // Do nothing if not allowed to edit the watchlist 03259 if ( !$this->isAllowed( 'editmywatchlist' ) ) { 03260 return; 03261 } 03262 03263 // If we're working on user's talk page, we should update the talk page message indicator 03264 if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) { 03265 if ( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this, $oldid ) ) ) { 03266 return; 03267 } 03268 03269 $nextid = $oldid ? $title->getNextRevisionID( $oldid ) : null; 03270 03271 if ( !$oldid || !$nextid ) { 03272 // If we're looking at the latest revision, we should definitely clear it 03273 $this->setNewtalk( false ); 03274 } else { 03275 // Otherwise we should update its revision, if it's present 03276 if ( $this->getNewtalk() ) { 03277 // Naturally the other one won't clear by itself 03278 $this->setNewtalk( false ); 03279 $this->setNewtalk( true, Revision::newFromId( $nextid ) ); 03280 } 03281 } 03282 } 03283 03284 if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) { 03285 return; 03286 } 03287 03288 if ( $this->isAnon() ) { 03289 // Nothing else to do... 03290 return; 03291 } 03292 03293 // Only update the timestamp if the page is being watched. 03294 // The query to find out if it is watched is cached both in memcached and per-invocation, 03295 // and when it does have to be executed, it can be on a slave 03296 // If this is the user's newtalk page, we always update the timestamp 03297 $force = ''; 03298 if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) { 03299 $force = 'force'; 03300 } 03301 03302 $this->getWatchedItem( $title )->resetNotificationTimestamp( $force, $oldid ); 03303 } 03304 03311 public function clearAllNotifications() { 03312 if ( wfReadOnly() ) { 03313 return; 03314 } 03315 03316 // Do nothing if not allowed to edit the watchlist 03317 if ( !$this->isAllowed( 'editmywatchlist' ) ) { 03318 return; 03319 } 03320 03321 global $wgUseEnotif, $wgShowUpdatedMarker; 03322 if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) { 03323 $this->setNewtalk( false ); 03324 return; 03325 } 03326 $id = $this->getId(); 03327 if ( $id != 0 ) { 03328 $dbw = wfGetDB( DB_MASTER ); 03329 $dbw->update( 'watchlist', 03330 array( /* SET */ 'wl_notificationtimestamp' => null ), 03331 array( /* WHERE */ 'wl_user' => $id ), 03332 __METHOD__ 03333 ); 03334 // We also need to clear here the "you have new message" notification for the own user_talk page; 03335 // it's cleared one page view later in WikiPage::doViewUpdates(). 03336 } 03337 } 03338 03352 protected function setCookie( $name, $value, $exp = 0, $secure = null, $params = array() ) { 03353 $params['secure'] = $secure; 03354 $this->getRequest()->response()->setcookie( $name, $value, $exp, $params ); 03355 } 03356 03366 protected function clearCookie( $name, $secure = null, $params = array() ) { 03367 $this->setCookie( $name, '', time() - 86400, $secure, $params ); 03368 } 03369 03378 public function setCookies( $request = null, $secure = null, $rememberMe = false ) { 03379 if ( $request === null ) { 03380 $request = $this->getRequest(); 03381 } 03382 03383 $this->load(); 03384 if ( 0 == $this->mId ) { 03385 return; 03386 } 03387 if ( !$this->mToken ) { 03388 // When token is empty or NULL generate a new one and then save it to the database 03389 // This allows a wiki to re-secure itself after a leak of it's user table or $wgSecretKey 03390 // Simply by setting every cell in the user_token column to NULL and letting them be 03391 // regenerated as users log back into the wiki. 03392 $this->setToken(); 03393 $this->saveSettings(); 03394 } 03395 $session = array( 03396 'wsUserID' => $this->mId, 03397 'wsToken' => $this->mToken, 03398 'wsUserName' => $this->getName() 03399 ); 03400 $cookies = array( 03401 'UserID' => $this->mId, 03402 'UserName' => $this->getName(), 03403 ); 03404 if ( $rememberMe ) { 03405 $cookies['Token'] = $this->mToken; 03406 } else { 03407 $cookies['Token'] = false; 03408 } 03409 03410 wfRunHooks( 'UserSetCookies', array( $this, &$session, &$cookies ) ); 03411 03412 foreach ( $session as $name => $value ) { 03413 $request->setSessionData( $name, $value ); 03414 } 03415 foreach ( $cookies as $name => $value ) { 03416 if ( $value === false ) { 03417 $this->clearCookie( $name ); 03418 } else { 03419 $this->setCookie( $name, $value, 0, $secure ); 03420 } 03421 } 03422 03430 if ( $request->getCheck( 'wpStickHTTPS' ) || $this->requiresHTTPS() ) { 03431 $this->setCookie( 03432 'forceHTTPS', 03433 'true', 03434 $rememberMe ? 0 : null, 03435 false, 03436 array( 'prefix' => '' ) // no prefix 03437 ); 03438 } 03439 } 03440 03444 public function logout() { 03445 if ( wfRunHooks( 'UserLogout', array( &$this ) ) ) { 03446 $this->doLogout(); 03447 } 03448 } 03449 03454 public function doLogout() { 03455 $this->clearInstanceCache( 'defaults' ); 03456 03457 $this->getRequest()->setSessionData( 'wsUserID', 0 ); 03458 03459 $this->clearCookie( 'UserID' ); 03460 $this->clearCookie( 'Token' ); 03461 $this->clearCookie( 'forceHTTPS', false, array( 'prefix' => '' ) ); 03462 03463 // Remember when user logged out, to prevent seeing cached pages 03464 $this->setCookie( 'LoggedOut', time(), time() + 86400 ); 03465 } 03466 03471 public function saveSettings() { 03472 global $wgAuth; 03473 03474 $this->load(); 03475 $this->loadPasswords(); 03476 if ( wfReadOnly() ) { 03477 return; 03478 } 03479 if ( 0 == $this->mId ) { 03480 return; 03481 } 03482 03483 $this->mTouched = self::newTouchedTimestamp(); 03484 if ( !$wgAuth->allowSetLocalPassword() ) { 03485 $this->mPassword = self::getPasswordFactory()->newFromCiphertext( null ); 03486 } 03487 03488 $dbw = wfGetDB( DB_MASTER ); 03489 $dbw->update( 'user', 03490 array( /* SET */ 03491 'user_name' => $this->mName, 03492 'user_password' => $this->mPassword->toString(), 03493 'user_newpassword' => $this->mNewpassword->toString(), 03494 'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ), 03495 'user_real_name' => $this->mRealName, 03496 'user_email' => $this->mEmail, 03497 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), 03498 'user_touched' => $dbw->timestamp( $this->mTouched ), 03499 'user_token' => strval( $this->mToken ), 03500 'user_email_token' => $this->mEmailToken, 03501 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ), 03502 'user_password_expires' => $dbw->timestampOrNull( $this->mPasswordExpires ), 03503 ), array( /* WHERE */ 03504 'user_id' => $this->mId 03505 ), __METHOD__ 03506 ); 03507 03508 $this->saveOptions(); 03509 03510 wfRunHooks( 'UserSaveSettings', array( $this ) ); 03511 $this->clearSharedCache(); 03512 $this->getUserPage()->invalidateCache(); 03513 } 03514 03519 public function idForName() { 03520 $s = trim( $this->getName() ); 03521 if ( $s === '' ) { 03522 return 0; 03523 } 03524 03525 $dbr = wfGetDB( DB_SLAVE ); 03526 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), __METHOD__ ); 03527 if ( $id === false ) { 03528 $id = 0; 03529 } 03530 return $id; 03531 } 03532 03552 public static function createNew( $name, $params = array() ) { 03553 $user = new User; 03554 $user->load(); 03555 $user->loadPasswords(); 03556 $user->setToken(); // init token 03557 if ( isset( $params['options'] ) ) { 03558 $user->mOptions = $params['options'] + (array)$user->mOptions; 03559 unset( $params['options'] ); 03560 } 03561 $dbw = wfGetDB( DB_MASTER ); 03562 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' ); 03563 03564 $fields = array( 03565 'user_id' => $seqVal, 03566 'user_name' => $name, 03567 'user_password' => $user->mPassword->toString(), 03568 'user_newpassword' => $user->mNewpassword->toString(), 03569 'user_newpass_time' => $dbw->timestampOrNull( $user->mNewpassTime ), 03570 'user_email' => $user->mEmail, 03571 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ), 03572 'user_real_name' => $user->mRealName, 03573 'user_token' => strval( $user->mToken ), 03574 'user_registration' => $dbw->timestamp( $user->mRegistration ), 03575 'user_editcount' => 0, 03576 'user_touched' => $dbw->timestamp( self::newTouchedTimestamp() ), 03577 ); 03578 foreach ( $params as $name => $value ) { 03579 $fields["user_$name"] = $value; 03580 } 03581 $dbw->insert( 'user', $fields, __METHOD__, array( 'IGNORE' ) ); 03582 if ( $dbw->affectedRows() ) { 03583 $newUser = User::newFromId( $dbw->insertId() ); 03584 } else { 03585 $newUser = null; 03586 } 03587 return $newUser; 03588 } 03589 03616 public function addToDatabase() { 03617 $this->load(); 03618 $this->loadPasswords(); 03619 if ( !$this->mToken ) { 03620 $this->setToken(); // init token 03621 } 03622 03623 $this->mTouched = self::newTouchedTimestamp(); 03624 03625 $dbw = wfGetDB( DB_MASTER ); 03626 $inWrite = $dbw->writesOrCallbacksPending(); 03627 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' ); 03628 $dbw->insert( 'user', 03629 array( 03630 'user_id' => $seqVal, 03631 'user_name' => $this->mName, 03632 'user_password' => $this->mPassword->toString(), 03633 'user_newpassword' => $this->mNewpassword->toString(), 03634 'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ), 03635 'user_email' => $this->mEmail, 03636 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), 03637 'user_real_name' => $this->mRealName, 03638 'user_token' => strval( $this->mToken ), 03639 'user_registration' => $dbw->timestamp( $this->mRegistration ), 03640 'user_editcount' => 0, 03641 'user_touched' => $dbw->timestamp( $this->mTouched ), 03642 ), __METHOD__, 03643 array( 'IGNORE' ) 03644 ); 03645 if ( !$dbw->affectedRows() ) { 03646 // The queries below cannot happen in the same REPEATABLE-READ snapshot. 03647 // Handle this by COMMIT, if possible, or by LOCK IN SHARE MODE otherwise. 03648 if ( $inWrite ) { 03649 // Can't commit due to pending writes that may need atomicity. 03650 // This may cause some lock contention unlike the case below. 03651 $options = array( 'LOCK IN SHARE MODE' ); 03652 $flags = self::READ_LOCKING; 03653 } else { 03654 // Often, this case happens early in views before any writes when 03655 // using CentralAuth. It's should be OK to commit and break the snapshot. 03656 $dbw->commit( __METHOD__, 'flush' ); 03657 $options = array(); 03658 $flags = 0; 03659 } 03660 $this->mId = $dbw->selectField( 'user', 'user_id', 03661 array( 'user_name' => $this->mName ), __METHOD__, $options ); 03662 $loaded = false; 03663 if ( $this->mId ) { 03664 if ( $this->loadFromDatabase( $flags ) ) { 03665 $loaded = true; 03666 } 03667 } 03668 if ( !$loaded ) { 03669 throw new MWException( __METHOD__ . ": hit a key conflict attempting " . 03670 "to insert user '{$this->mName}' row, but it was not present in select!" ); 03671 } 03672 return Status::newFatal( 'userexists' ); 03673 } 03674 $this->mId = $dbw->insertId(); 03675 03676 // Clear instance cache other than user table data, which is already accurate 03677 $this->clearInstanceCache(); 03678 03679 $this->saveOptions(); 03680 return Status::newGood(); 03681 } 03682 03688 public function spreadAnyEditBlock() { 03689 if ( $this->isLoggedIn() && $this->isBlocked() ) { 03690 return $this->spreadBlock(); 03691 } 03692 return false; 03693 } 03694 03700 protected function spreadBlock() { 03701 wfDebug( __METHOD__ . "()\n" ); 03702 $this->load(); 03703 if ( $this->mId == 0 ) { 03704 return false; 03705 } 03706 03707 $userblock = Block::newFromTarget( $this->getName() ); 03708 if ( !$userblock ) { 03709 return false; 03710 } 03711 03712 return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() ); 03713 } 03714 03719 public function isBlockedFromCreateAccount() { 03720 $this->getBlockedStatus(); 03721 if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) { 03722 return $this->mBlock; 03723 } 03724 03725 # bug 13611: if the IP address the user is trying to create an account from is 03726 # blocked with createaccount disabled, prevent new account creation there even 03727 # when the user is logged in 03728 if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) { 03729 $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() ); 03730 } 03731 return $this->mBlockedFromCreateAccount instanceof Block 03732 && $this->mBlockedFromCreateAccount->prevents( 'createaccount' ) 03733 ? $this->mBlockedFromCreateAccount 03734 : false; 03735 } 03736 03741 public function isBlockedFromEmailuser() { 03742 $this->getBlockedStatus(); 03743 return $this->mBlock && $this->mBlock->prevents( 'sendemail' ); 03744 } 03745 03750 public function isAllowedToCreateAccount() { 03751 return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount(); 03752 } 03753 03759 public function getUserPage() { 03760 return Title::makeTitle( NS_USER, $this->getName() ); 03761 } 03762 03768 public function getTalkPage() { 03769 $title = $this->getUserPage(); 03770 return $title->getTalkPage(); 03771 } 03772 03778 public function isNewbie() { 03779 return !$this->isAllowed( 'autoconfirmed' ); 03780 } 03781 03787 public function checkPassword( $password ) { 03788 global $wgAuth, $wgLegacyEncoding; 03789 03790 $section = new ProfileSection( __METHOD__ ); 03791 03792 $this->loadPasswords(); 03793 03794 // Certain authentication plugins do NOT want to save 03795 // domain passwords in a mysql database, so we should 03796 // check this (in case $wgAuth->strict() is false). 03797 if ( $wgAuth->authenticate( $this->getName(), $password ) ) { 03798 return true; 03799 } elseif ( $wgAuth->strict() ) { 03800 // Auth plugin doesn't allow local authentication 03801 return false; 03802 } elseif ( $wgAuth->strictUserAuth( $this->getName() ) ) { 03803 // Auth plugin doesn't allow local authentication for this user name 03804 return false; 03805 } 03806 03807 $passwordFactory = self::getPasswordFactory(); 03808 if ( !$this->mPassword->equals( $password ) ) { 03809 if ( $wgLegacyEncoding ) { 03810 // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted 03811 // Check for this with iconv 03812 $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password ); 03813 if ( $cp1252Password === $password || !$this->mPassword->equals( $cp1252Password ) ) { 03814 return false; 03815 } 03816 } else { 03817 return false; 03818 } 03819 } 03820 03821 if ( $passwordFactory->needsUpdate( $this->mPassword ) ) { 03822 $this->mPassword = $passwordFactory->newFromPlaintext( $password ); 03823 $this->saveSettings(); 03824 } 03825 03826 return true; 03827 } 03828 03837 public function checkTemporaryPassword( $plaintext ) { 03838 global $wgNewPasswordExpiry; 03839 03840 $this->load(); 03841 $this->loadPasswords(); 03842 if ( $this->mNewpassword->equals( $plaintext ) ) { 03843 if ( is_null( $this->mNewpassTime ) ) { 03844 return true; 03845 } 03846 $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgNewPasswordExpiry; 03847 return ( time() < $expiry ); 03848 } else { 03849 return false; 03850 } 03851 } 03852 03861 public function editToken( $salt = '', $request = null ) { 03862 wfDeprecated( __METHOD__, '1.19' ); 03863 return $this->getEditToken( $salt, $request ); 03864 } 03865 03878 public function getEditToken( $salt = '', $request = null ) { 03879 if ( $request == null ) { 03880 $request = $this->getRequest(); 03881 } 03882 03883 if ( $this->isAnon() ) { 03884 return self::EDIT_TOKEN_SUFFIX; 03885 } else { 03886 $token = $request->getSessionData( 'wsEditToken' ); 03887 if ( $token === null ) { 03888 $token = MWCryptRand::generateHex( 32 ); 03889 $request->setSessionData( 'wsEditToken', $token ); 03890 } 03891 if ( is_array( $salt ) ) { 03892 $salt = implode( '|', $salt ); 03893 } 03894 return md5( $token . $salt ) . self::EDIT_TOKEN_SUFFIX; 03895 } 03896 } 03897 03905 public static function generateToken() { 03906 return MWCryptRand::generateHex( 32 ); 03907 } 03908 03920 public function matchEditToken( $val, $salt = '', $request = null ) { 03921 $sessionToken = $this->getEditToken( $salt, $request ); 03922 if ( $val != $sessionToken ) { 03923 wfDebug( "User::matchEditToken: broken session data\n" ); 03924 } 03925 03926 return $val == $sessionToken; 03927 } 03928 03938 public function matchEditTokenNoSuffix( $val, $salt = '', $request = null ) { 03939 $sessionToken = $this->getEditToken( $salt, $request ); 03940 return substr( $sessionToken, 0, 32 ) == substr( $val, 0, 32 ); 03941 } 03942 03950 public function sendConfirmationMail( $type = 'created' ) { 03951 global $wgLang; 03952 $expiration = null; // gets passed-by-ref and defined in next line. 03953 $token = $this->confirmationToken( $expiration ); 03954 $url = $this->confirmationTokenUrl( $token ); 03955 $invalidateURL = $this->invalidationTokenUrl( $token ); 03956 $this->saveSettings(); 03957 03958 if ( $type == 'created' || $type === false ) { 03959 $message = 'confirmemail_body'; 03960 } elseif ( $type === true ) { 03961 $message = 'confirmemail_body_changed'; 03962 } else { 03963 // Messages: confirmemail_body_changed, confirmemail_body_set 03964 $message = 'confirmemail_body_' . $type; 03965 } 03966 03967 return $this->sendMail( wfMessage( 'confirmemail_subject' )->text(), 03968 wfMessage( $message, 03969 $this->getRequest()->getIP(), 03970 $this->getName(), 03971 $url, 03972 $wgLang->timeanddate( $expiration, false ), 03973 $invalidateURL, 03974 $wgLang->date( $expiration, false ), 03975 $wgLang->time( $expiration, false ) )->text() ); 03976 } 03977 03989 public function sendMail( $subject, $body, $from = null, $replyto = null ) { 03990 if ( is_null( $from ) ) { 03991 global $wgPasswordSender; 03992 $sender = new MailAddress( $wgPasswordSender, 03993 wfMessage( 'emailsender' )->inContentLanguage()->text() ); 03994 } else { 03995 $sender = MailAddress::newFromUser( $from ); 03996 } 03997 03998 $to = MailAddress::newFromUser( $this ); 03999 return UserMailer::send( $to, $sender, $subject, $body, $replyto ); 04000 } 04001 04012 protected function confirmationToken( &$expiration ) { 04013 global $wgUserEmailConfirmationTokenExpiry; 04014 $now = time(); 04015 $expires = $now + $wgUserEmailConfirmationTokenExpiry; 04016 $expiration = wfTimestamp( TS_MW, $expires ); 04017 $this->load(); 04018 $token = MWCryptRand::generateHex( 32 ); 04019 $hash = md5( $token ); 04020 $this->mEmailToken = $hash; 04021 $this->mEmailTokenExpires = $expiration; 04022 return $token; 04023 } 04024 04030 protected function confirmationTokenUrl( $token ) { 04031 return $this->getTokenUrl( 'ConfirmEmail', $token ); 04032 } 04033 04039 protected function invalidationTokenUrl( $token ) { 04040 return $this->getTokenUrl( 'InvalidateEmail', $token ); 04041 } 04042 04057 protected function getTokenUrl( $page, $token ) { 04058 // Hack to bypass localization of 'Special:' 04059 $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" ); 04060 return $title->getCanonicalURL(); 04061 } 04062 04070 public function confirmEmail() { 04071 // Check if it's already confirmed, so we don't touch the database 04072 // and fire the ConfirmEmailComplete hook on redundant confirmations. 04073 if ( !$this->isEmailConfirmed() ) { 04074 $this->setEmailAuthenticationTimestamp( wfTimestampNow() ); 04075 wfRunHooks( 'ConfirmEmailComplete', array( $this ) ); 04076 } 04077 return true; 04078 } 04079 04087 public function invalidateEmail() { 04088 $this->load(); 04089 $this->mEmailToken = null; 04090 $this->mEmailTokenExpires = null; 04091 $this->setEmailAuthenticationTimestamp( null ); 04092 $this->mEmail = ''; 04093 wfRunHooks( 'InvalidateEmailComplete', array( $this ) ); 04094 return true; 04095 } 04096 04101 public function setEmailAuthenticationTimestamp( $timestamp ) { 04102 $this->load(); 04103 $this->mEmailAuthenticated = $timestamp; 04104 wfRunHooks( 'UserSetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) ); 04105 } 04106 04112 public function canSendEmail() { 04113 global $wgEnableEmail, $wgEnableUserEmail; 04114 if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) { 04115 return false; 04116 } 04117 $canSend = $this->isEmailConfirmed(); 04118 wfRunHooks( 'UserCanSendEmail', array( &$this, &$canSend ) ); 04119 return $canSend; 04120 } 04121 04127 public function canReceiveEmail() { 04128 return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' ); 04129 } 04130 04141 public function isEmailConfirmed() { 04142 global $wgEmailAuthentication; 04143 $this->load(); 04144 $confirmed = true; 04145 if ( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) { 04146 if ( $this->isAnon() ) { 04147 return false; 04148 } 04149 if ( !Sanitizer::validateEmail( $this->mEmail ) ) { 04150 return false; 04151 } 04152 if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) { 04153 return false; 04154 } 04155 return true; 04156 } else { 04157 return $confirmed; 04158 } 04159 } 04160 04165 public function isEmailConfirmationPending() { 04166 global $wgEmailAuthentication; 04167 return $wgEmailAuthentication && 04168 !$this->isEmailConfirmed() && 04169 $this->mEmailToken && 04170 $this->mEmailTokenExpires > wfTimestamp(); 04171 } 04172 04180 public function getRegistration() { 04181 if ( $this->isAnon() ) { 04182 return false; 04183 } 04184 $this->load(); 04185 return $this->mRegistration; 04186 } 04187 04194 public function getFirstEditTimestamp() { 04195 if ( $this->getId() == 0 ) { 04196 return false; // anons 04197 } 04198 $dbr = wfGetDB( DB_SLAVE ); 04199 $time = $dbr->selectField( 'revision', 'rev_timestamp', 04200 array( 'rev_user' => $this->getId() ), 04201 __METHOD__, 04202 array( 'ORDER BY' => 'rev_timestamp ASC' ) 04203 ); 04204 if ( !$time ) { 04205 return false; // no edits 04206 } 04207 return wfTimestamp( TS_MW, $time ); 04208 } 04209 04216 public static function getGroupPermissions( $groups ) { 04217 global $wgGroupPermissions, $wgRevokePermissions; 04218 $rights = array(); 04219 // grant every granted permission first 04220 foreach ( $groups as $group ) { 04221 if ( isset( $wgGroupPermissions[$group] ) ) { 04222 $rights = array_merge( $rights, 04223 // array_filter removes empty items 04224 array_keys( array_filter( $wgGroupPermissions[$group] ) ) ); 04225 } 04226 } 04227 // now revoke the revoked permissions 04228 foreach ( $groups as $group ) { 04229 if ( isset( $wgRevokePermissions[$group] ) ) { 04230 $rights = array_diff( $rights, 04231 array_keys( array_filter( $wgRevokePermissions[$group] ) ) ); 04232 } 04233 } 04234 return array_unique( $rights ); 04235 } 04236 04243 public static function getGroupsWithPermission( $role ) { 04244 global $wgGroupPermissions; 04245 $allowedGroups = array(); 04246 foreach ( array_keys( $wgGroupPermissions ) as $group ) { 04247 if ( self::groupHasPermission( $group, $role ) ) { 04248 $allowedGroups[] = $group; 04249 } 04250 } 04251 return $allowedGroups; 04252 } 04253 04266 public static function groupHasPermission( $group, $role ) { 04267 global $wgGroupPermissions, $wgRevokePermissions; 04268 return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role] 04269 && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] ); 04270 } 04271 04279 public static function isEveryoneAllowed( $right ) { 04280 global $wgGroupPermissions, $wgRevokePermissions; 04281 static $cache = array(); 04282 04283 // Use the cached results, except in unit tests which rely on 04284 // being able change the permission mid-request 04285 if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) { 04286 return $cache[$right]; 04287 } 04288 04289 if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) { 04290 $cache[$right] = false; 04291 return false; 04292 } 04293 04294 // If it's revoked anywhere, then everyone doesn't have it 04295 foreach ( $wgRevokePermissions as $rights ) { 04296 if ( isset( $rights[$right] ) && $rights[$right] ) { 04297 $cache[$right] = false; 04298 return false; 04299 } 04300 } 04301 04302 // Allow extensions (e.g. OAuth) to say false 04303 if ( !wfRunHooks( 'UserIsEveryoneAllowed', array( $right ) ) ) { 04304 $cache[$right] = false; 04305 return false; 04306 } 04307 04308 $cache[$right] = true; 04309 return true; 04310 } 04311 04318 public static function getGroupName( $group ) { 04319 $msg = wfMessage( "group-$group" ); 04320 return $msg->isBlank() ? $group : $msg->text(); 04321 } 04322 04330 public static function getGroupMember( $group, $username = '#' ) { 04331 $msg = wfMessage( "group-$group-member", $username ); 04332 return $msg->isBlank() ? $group : $msg->text(); 04333 } 04334 04341 public static function getAllGroups() { 04342 global $wgGroupPermissions, $wgRevokePermissions; 04343 return array_diff( 04344 array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ), 04345 self::getImplicitGroups() 04346 ); 04347 } 04348 04353 public static function getAllRights() { 04354 if ( self::$mAllRights === false ) { 04355 global $wgAvailableRights; 04356 if ( count( $wgAvailableRights ) ) { 04357 self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) ); 04358 } else { 04359 self::$mAllRights = self::$mCoreRights; 04360 } 04361 wfRunHooks( 'UserGetAllRights', array( &self::$mAllRights ) ); 04362 } 04363 return self::$mAllRights; 04364 } 04365 04370 public static function getImplicitGroups() { 04371 global $wgImplicitGroups; 04372 04373 $groups = $wgImplicitGroups; 04374 # Deprecated, use $wgImplictGroups instead 04375 wfRunHooks( 'UserGetImplicitGroups', array( &$groups ) ); 04376 04377 return $groups; 04378 } 04379 04386 public static function getGroupPage( $group ) { 04387 $msg = wfMessage( 'grouppage-' . $group )->inContentLanguage(); 04388 if ( $msg->exists() ) { 04389 $title = Title::newFromText( $msg->text() ); 04390 if ( is_object( $title ) ) { 04391 return $title; 04392 } 04393 } 04394 return false; 04395 } 04396 04405 public static function makeGroupLinkHTML( $group, $text = '' ) { 04406 if ( $text == '' ) { 04407 $text = self::getGroupName( $group ); 04408 } 04409 $title = self::getGroupPage( $group ); 04410 if ( $title ) { 04411 return Linker::link( $title, htmlspecialchars( $text ) ); 04412 } else { 04413 return $text; 04414 } 04415 } 04416 04425 public static function makeGroupLinkWiki( $group, $text = '' ) { 04426 if ( $text == '' ) { 04427 $text = self::getGroupName( $group ); 04428 } 04429 $title = self::getGroupPage( $group ); 04430 if ( $title ) { 04431 $page = $title->getPrefixedText(); 04432 return "[[$page|$text]]"; 04433 } else { 04434 return $text; 04435 } 04436 } 04437 04447 public static function changeableByGroup( $group ) { 04448 global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; 04449 04450 $groups = array( 04451 'add' => array(), 04452 'remove' => array(), 04453 'add-self' => array(), 04454 'remove-self' => array() 04455 ); 04456 04457 if ( empty( $wgAddGroups[$group] ) ) { 04458 // Don't add anything to $groups 04459 } elseif ( $wgAddGroups[$group] === true ) { 04460 // You get everything 04461 $groups['add'] = self::getAllGroups(); 04462 } elseif ( is_array( $wgAddGroups[$group] ) ) { 04463 $groups['add'] = $wgAddGroups[$group]; 04464 } 04465 04466 // Same thing for remove 04467 if ( empty( $wgRemoveGroups[$group] ) ) { 04468 } elseif ( $wgRemoveGroups[$group] === true ) { 04469 $groups['remove'] = self::getAllGroups(); 04470 } elseif ( is_array( $wgRemoveGroups[$group] ) ) { 04471 $groups['remove'] = $wgRemoveGroups[$group]; 04472 } 04473 04474 // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility 04475 if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) { 04476 foreach ( $wgGroupsAddToSelf as $key => $value ) { 04477 if ( is_int( $key ) ) { 04478 $wgGroupsAddToSelf['user'][] = $value; 04479 } 04480 } 04481 } 04482 04483 if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) { 04484 foreach ( $wgGroupsRemoveFromSelf as $key => $value ) { 04485 if ( is_int( $key ) ) { 04486 $wgGroupsRemoveFromSelf['user'][] = $value; 04487 } 04488 } 04489 } 04490 04491 // Now figure out what groups the user can add to him/herself 04492 if ( empty( $wgGroupsAddToSelf[$group] ) ) { 04493 } elseif ( $wgGroupsAddToSelf[$group] === true ) { 04494 // No idea WHY this would be used, but it's there 04495 $groups['add-self'] = User::getAllGroups(); 04496 } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) { 04497 $groups['add-self'] = $wgGroupsAddToSelf[$group]; 04498 } 04499 04500 if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) { 04501 } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) { 04502 $groups['remove-self'] = User::getAllGroups(); 04503 } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) { 04504 $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group]; 04505 } 04506 04507 return $groups; 04508 } 04509 04517 public function changeableGroups() { 04518 if ( $this->isAllowed( 'userrights' ) ) { 04519 // This group gives the right to modify everything (reverse- 04520 // compatibility with old "userrights lets you change 04521 // everything") 04522 // Using array_merge to make the groups reindexed 04523 $all = array_merge( User::getAllGroups() ); 04524 return array( 04525 'add' => $all, 04526 'remove' => $all, 04527 'add-self' => array(), 04528 'remove-self' => array() 04529 ); 04530 } 04531 04532 // Okay, it's not so simple, we will have to go through the arrays 04533 $groups = array( 04534 'add' => array(), 04535 'remove' => array(), 04536 'add-self' => array(), 04537 'remove-self' => array() 04538 ); 04539 $addergroups = $this->getEffectiveGroups(); 04540 04541 foreach ( $addergroups as $addergroup ) { 04542 $groups = array_merge_recursive( 04543 $groups, $this->changeableByGroup( $addergroup ) 04544 ); 04545 $groups['add'] = array_unique( $groups['add'] ); 04546 $groups['remove'] = array_unique( $groups['remove'] ); 04547 $groups['add-self'] = array_unique( $groups['add-self'] ); 04548 $groups['remove-self'] = array_unique( $groups['remove-self'] ); 04549 } 04550 return $groups; 04551 } 04552 04557 public function incEditCount() { 04558 if ( !$this->isAnon() ) { 04559 $dbw = wfGetDB( DB_MASTER ); 04560 $dbw->update( 04561 'user', 04562 array( 'user_editcount=user_editcount+1' ), 04563 array( 'user_id' => $this->getId() ), 04564 __METHOD__ 04565 ); 04566 04567 // Lazy initialization check... 04568 if ( $dbw->affectedRows() == 0 ) { 04569 // Now here's a goddamn hack... 04570 $dbr = wfGetDB( DB_SLAVE ); 04571 if ( $dbr !== $dbw ) { 04572 // If we actually have a slave server, the count is 04573 // at least one behind because the current transaction 04574 // has not been committed and replicated. 04575 $this->initEditCount( 1 ); 04576 } else { 04577 // But if DB_SLAVE is selecting the master, then the 04578 // count we just read includes the revision that was 04579 // just added in the working transaction. 04580 $this->initEditCount(); 04581 } 04582 } 04583 } 04584 // edit count in user cache too 04585 $this->invalidateCache(); 04586 } 04587 04594 protected function initEditCount( $add = 0 ) { 04595 // Pull from a slave to be less cruel to servers 04596 // Accuracy isn't the point anyway here 04597 $dbr = wfGetDB( DB_SLAVE ); 04598 $count = (int)$dbr->selectField( 04599 'revision', 04600 'COUNT(rev_user)', 04601 array( 'rev_user' => $this->getId() ), 04602 __METHOD__ 04603 ); 04604 $count = $count + $add; 04605 04606 $dbw = wfGetDB( DB_MASTER ); 04607 $dbw->update( 04608 'user', 04609 array( 'user_editcount' => $count ), 04610 array( 'user_id' => $this->getId() ), 04611 __METHOD__ 04612 ); 04613 04614 return $count; 04615 } 04616 04623 public static function getRightDescription( $right ) { 04624 $key = "right-$right"; 04625 $msg = wfMessage( $key ); 04626 return $msg->isBlank() ? $right : $msg->text(); 04627 } 04628 04638 public static function crypt( $password, $salt = false ) { 04639 wfDeprecated( __METHOD__, '1.24' ); 04640 $hash = self::getPasswordFactory()->newFromPlaintext( $password ); 04641 return $hash->toString(); 04642 } 04643 04655 public static function comparePasswords( $hash, $password, $userId = false ) { 04656 wfDeprecated( __METHOD__, '1.24' ); 04657 04658 // Check for *really* old password hashes that don't even have a type 04659 // The old hash format was just an md5 hex hash, with no type information 04660 if ( preg_match( '/^[0-9a-f]{32}$/', $hash ) ) { 04661 global $wgPasswordSalt; 04662 if ( $wgPasswordSalt ) { 04663 $password = ":B:{$userId}:{$hash}"; 04664 } else { 04665 $password = ":A:{$hash}"; 04666 } 04667 } 04668 04669 $hash = self::getPasswordFactory()->newFromCiphertext( $hash ); 04670 return $hash->equals( $password ); 04671 } 04672 04694 public function addNewUserLogEntry( $action = false, $reason = '' ) { 04695 global $wgUser, $wgNewUserLog; 04696 if ( empty( $wgNewUserLog ) ) { 04697 return true; // disabled 04698 } 04699 04700 if ( $action === true ) { 04701 $action = 'byemail'; 04702 } elseif ( $action === false ) { 04703 if ( $this->getName() == $wgUser->getName() ) { 04704 $action = 'create'; 04705 } else { 04706 $action = 'create2'; 04707 } 04708 } 04709 04710 if ( $action === 'create' || $action === 'autocreate' ) { 04711 $performer = $this; 04712 } else { 04713 $performer = $wgUser; 04714 } 04715 04716 $logEntry = new ManualLogEntry( 'newusers', $action ); 04717 $logEntry->setPerformer( $performer ); 04718 $logEntry->setTarget( $this->getUserPage() ); 04719 $logEntry->setComment( $reason ); 04720 $logEntry->setParameters( array( 04721 '4::userid' => $this->getId(), 04722 ) ); 04723 $logid = $logEntry->insert(); 04724 04725 if ( $action !== 'autocreate' ) { 04726 $logEntry->publish( $logid ); 04727 } 04728 04729 return (int)$logid; 04730 } 04731 04739 public function addNewUserLogEntryAutoCreate() { 04740 $this->addNewUserLogEntry( 'autocreate' ); 04741 04742 return true; 04743 } 04744 04750 protected function loadOptions( $data = null ) { 04751 global $wgContLang; 04752 04753 $this->load(); 04754 04755 if ( $this->mOptionsLoaded ) { 04756 return; 04757 } 04758 04759 $this->mOptions = self::getDefaultOptions(); 04760 04761 if ( !$this->getId() ) { 04762 // For unlogged-in users, load language/variant options from request. 04763 // There's no need to do it for logged-in users: they can set preferences, 04764 // and handling of page content is done by $pageLang->getPreferredVariant() and such, 04765 // so don't override user's choice (especially when the user chooses site default). 04766 $variant = $wgContLang->getDefaultVariant(); 04767 $this->mOptions['variant'] = $variant; 04768 $this->mOptions['language'] = $variant; 04769 $this->mOptionsLoaded = true; 04770 return; 04771 } 04772 04773 // Maybe load from the object 04774 if ( !is_null( $this->mOptionOverrides ) ) { 04775 wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" ); 04776 foreach ( $this->mOptionOverrides as $key => $value ) { 04777 $this->mOptions[$key] = $value; 04778 } 04779 } else { 04780 if ( !is_array( $data ) ) { 04781 wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" ); 04782 // Load from database 04783 $dbr = wfGetDB( DB_SLAVE ); 04784 04785 $res = $dbr->select( 04786 'user_properties', 04787 array( 'up_property', 'up_value' ), 04788 array( 'up_user' => $this->getId() ), 04789 __METHOD__ 04790 ); 04791 04792 $this->mOptionOverrides = array(); 04793 $data = array(); 04794 foreach ( $res as $row ) { 04795 $data[$row->up_property] = $row->up_value; 04796 } 04797 } 04798 foreach ( $data as $property => $value ) { 04799 $this->mOptionOverrides[$property] = $value; 04800 $this->mOptions[$property] = $value; 04801 } 04802 } 04803 04804 $this->mOptionsLoaded = true; 04805 04806 wfRunHooks( 'UserLoadOptions', array( $this, &$this->mOptions ) ); 04807 } 04808 04814 protected function saveOptions() { 04815 $this->loadOptions(); 04816 04817 // Not using getOptions(), to keep hidden preferences in database 04818 $saveOptions = $this->mOptions; 04819 04820 // Allow hooks to abort, for instance to save to a global profile. 04821 // Reset options to default state before saving. 04822 if ( !wfRunHooks( 'UserSaveOptions', array( $this, &$saveOptions ) ) ) { 04823 return; 04824 } 04825 04826 $userId = $this->getId(); 04827 04828 $insert_rows = array(); // all the new preference rows 04829 foreach ( $saveOptions as $key => $value ) { 04830 // Don't bother storing default values 04831 $defaultOption = self::getDefaultOption( $key ); 04832 if ( ( $defaultOption === null && $value !== false && $value !== null ) 04833 || $value != $defaultOption 04834 ) { 04835 $insert_rows[] = array( 04836 'up_user' => $userId, 04837 'up_property' => $key, 04838 'up_value' => $value, 04839 ); 04840 } 04841 } 04842 04843 $dbw = wfGetDB( DB_MASTER ); 04844 04845 $res = $dbw->select( 'user_properties', 04846 array( 'up_property', 'up_value' ), array( 'up_user' => $userId ), __METHOD__ ); 04847 04848 // Find prior rows that need to be removed or updated. These rows will 04849 // all be deleted (the later so that INSERT IGNORE applies the new values). 04850 $keysDelete = array(); 04851 foreach ( $res as $row ) { 04852 if ( !isset( $saveOptions[$row->up_property] ) 04853 || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0 04854 ) { 04855 $keysDelete[] = $row->up_property; 04856 } 04857 } 04858 04859 if ( count( $keysDelete ) ) { 04860 // Do the DELETE by PRIMARY KEY for prior rows. 04861 // In the past a very large portion of calls to this function are for setting 04862 // 'rememberpassword' for new accounts (a preference that has since been removed). 04863 // Doing a blanket per-user DELETE for new accounts with no rows in the table 04864 // caused gap locks on [max user ID,+infinity) which caused high contention since 04865 // updates would pile up on each other as they are for higher (newer) user IDs. 04866 // It might not be necessary these days, but it shouldn't hurt either. 04867 $dbw->delete( 'user_properties', 04868 array( 'up_user' => $userId, 'up_property' => $keysDelete ), __METHOD__ ); 04869 } 04870 // Insert the new preference rows 04871 $dbw->insert( 'user_properties', $insert_rows, __METHOD__, array( 'IGNORE' ) ); 04872 } 04873 04879 public static function getPasswordFactory() { 04880 if ( self::$mPasswordFactory === null ) { 04881 self::$mPasswordFactory = new PasswordFactory(); 04882 self::$mPasswordFactory->init( RequestContext::getMain()->getConfig() ); 04883 } 04884 04885 return self::$mPasswordFactory; 04886 } 04887 04911 public static function passwordChangeInputAttribs() { 04912 global $wgMinimalPasswordLength; 04913 04914 if ( $wgMinimalPasswordLength == 0 ) { 04915 return array(); 04916 } 04917 04918 # Note that the pattern requirement will always be satisfied if the 04919 # input is empty, so we need required in all cases. 04920 # 04921 # @todo FIXME: Bug 23769: This needs to not claim the password is required 04922 # if e-mail confirmation is being used. Since HTML5 input validation 04923 # is b0rked anyway in some browsers, just return nothing. When it's 04924 # re-enabled, fix this code to not output required for e-mail 04925 # registration. 04926 #$ret = array( 'required' ); 04927 $ret = array(); 04928 04929 # We can't actually do this right now, because Opera 9.6 will print out 04930 # the entered password visibly in its error message! When other 04931 # browsers add support for this attribute, or Opera fixes its support, 04932 # we can add support with a version check to avoid doing this on Opera 04933 # versions where it will be a problem. Reported to Opera as 04934 # DSK-262266, but they don't have a public bug tracker for us to follow. 04935 /* 04936 if ( $wgMinimalPasswordLength > 1 ) { 04937 $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}'; 04938 $ret['title'] = wfMessage( 'passwordtooshort' ) 04939 ->numParams( $wgMinimalPasswordLength )->text(); 04940 } 04941 */ 04942 04943 return $ret; 04944 } 04945 04951 public static function selectFields() { 04952 return array( 04953 'user_id', 04954 'user_name', 04955 'user_real_name', 04956 'user_email', 04957 'user_touched', 04958 'user_token', 04959 'user_email_authenticated', 04960 'user_email_token', 04961 'user_email_token_expires', 04962 'user_registration', 04963 'user_editcount', 04964 ); 04965 } 04966 04974 static function newFatalPermissionDeniedStatus( $permission ) { 04975 global $wgLang; 04976 04977 $groups = array_map( 04978 array( 'User', 'makeGroupLinkWiki' ), 04979 User::getGroupsWithPermission( $permission ) 04980 ); 04981 04982 if ( $groups ) { 04983 return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) ); 04984 } else { 04985 return Status::newFatal( 'badaccess-group0' ); 04986 } 04987 } 04988 }